Microsoft Sentinel charges by the gigabyte for most data you ingest into the analytics tier. To reduce Microsoft Sentinel ingest cost, drop events you never query, remove fields you don’t use, and route high-volume, low-value logs to the cheaper data lake tier. This article walks through how to do each of these with NXLog Agent configurations and KQL you can adapt for your environment.
| Lever | Where it runs | What it saves | Trade-off |
|---|---|---|---|
Drop noisy events |
NXLog Agent on the host |
Analytics-tier GB, bandwidth, collector load |
Check every event ID against active detections |
Trim unused fields |
NXLog Agent on the host |
Billed record size |
Some keyword rules need the text fields |
Second-line filter |
DCR transformation in Azure |
Anything that slips past the agent |
Runs after Azure receives the data |
Tier by value |
Agent routes to two DCR streams |
About 96% lower ingest rate on lake-tier data |
No real-time analytics on lake-tier tables |
Why Microsoft Sentinel ingest cost is a data-engineering problem
Microsoft Sentinel pricing depends on the tier your data lands in. Pay-as-you-go bills the amount of data stored, measured in GB, plus retention beyond 90 days. You pay for whatever your estate sends.
Microsoft publishes list prices through its Azure Retail Prices API. For the East US region, checked on September 11, 2026:
-
Analytics tier, pay-as-you-go: $4.30 per GB
-
100 GB/day commitment tier: $296/day, an effective $2.96/GB
-
500 GB/day commitment tier: $1,265/day, an effective $2.53/GB
-
Data lake tier, direct ingestion: $0.05 per GB ingested plus $0.10 per GB data processing, then $0.026 per compressed GB per month for storage and $0.005 per GB scanned by queries
Commitment tiers reward volume you ingest every day. They don’t remove waste. Microsoft’s billing guidance states that usage above your commitment is billed at the tier’s discounted rate, so a bloated feed costs you at every tier.
In 2025, Microsoft added a second storage tier. The data lake overview describes an analytics tier for detections, hunting, and incidents, and a data lake tier for long-term storage and Python-based analytics. If you use Auxiliary Logs today, Microsoft’s billing documentation states that once you onboard to the lake, usage is billed through the data lake meters rather than the existing long-term retention, search, or auxiliary logs ingestion meters.
Every gigabyte you stop on the host is a gigabyte Microsoft never bills.
Step 1: Measure what you pay for
Measure before you filter.
Microsoft’s usage analysis guide documents the Usage table and the _BilledSize and _IsBillable columns you need.
Billable volume by table over the last 31 days:
Usage
| where TimeGenerated > ago(32d)
| where StartTime >= startofday(ago(31d)) and EndTime < startofday(now())
| where IsBillable == true
| summarize BillableDataGB = sum(Quantity) / 1000. by DataType
| sort by BillableDataGB desc
Which Windows event IDs dominate SecurityEvent:
SecurityEvent
| where TimeGenerated > ago(7d) and _IsBillable == true
| summarize Events = count(), GB = sum(_BilledSize) / 1e9 by EventID
| sort by GB desc
| take 25
Which hosts send the most data.
Run this one sparingly, because find scans every table:
find where TimeGenerated between(startofday(ago(1d))..startofday(now()))
project _BilledSize, _IsBillable, Computer, Type
| where _IsBillable == true and Type != "Usage"
| extend host = tolower(tostring(split(Computer, '.')[0]))
| summarize GB = sum(_BilledSize) / 1e9 by host
| sort by GB desc
Run these before and after every change. The output is a savings figure your finance team will accept instead of an estimate.
In the environments we see, a handful of Windows Security events account for a large share of volume: Windows Filtering Platform connections (5156, 5158), object access (4663), handle events (4658, 4690), and verbose DNS or firewall "allow" logs.
Your SecurityEvent breakdown shows whether that holds for you.
|
Step 2: Filter events at the source with NXLog Agent
Microsoft gives you two places to filter in Azure. DCR transformations run after Azure receives the data. A client-side transformation for Azure Monitor Agent exists, but the DCR overview still marks it as Preview. Neither stops the data from crossing your network.
Filtering on the collecting agent does. Every event you drop on the host is one you don’t pay to transmit, buffer, or ingest.
Suppress at the Windows API level
The Event Log for Windows input module accepts an XPath query in its QueryXML block directive, allowing you to paste a query directly from Event Viewer.
Microsoft’s event query schema includes a <Suppress> element that tells the Event Log API to withhold matching events.
NXLog Agent never reads them, making this the cheapest filter available.
<Input security_events>
Module im_msvistalog
<QueryXML>
<QueryList>
<Query Id="0">
<Select Path="Security">*</Select>
<!-- Filtering Platform permit chatter and handle noise -->
<Suppress Path="Security">
*[System[(EventID=5156 or EventID=5158 or EventID=4658 or EventID=4690)]]
</Suppress>
</Query>
</QueryList>
</QueryXML>
</Input>
The module documentation notes that, because of a limitation in the Windows Event Log API, queries with more than 22 clauses fail.
Group event IDs in parentheses or split them across <Query> blocks if you hit that limit.
Drop on conditions QueryXML can’t express
For rules that depend on field values, use an Exec block and the drop() procedure from the NXLog language:
<Input security_events>
Module im_msvistalog
<QueryXML>
<QueryList>
<Query Id="0">
<Select Path="Security">*</Select>
</Query>
</QueryList>
</QueryXML>
<Exec>
# Object access on scratch paths nobody investigates
if ($EventID == 4663 and $ObjectName =~ /^C:\\Windows\\Temp\\/i) {
drop();
}
# Process creation for known-benign system binaries
if ($EventID == 4688 and $NewProcessName =~ /\\(conhost|dllhost|backgroundTaskHost)\.exe$/i) {
drop();
}
# Service logons by machine accounts
if ($EventID == 4624 and $LogonType == 5 and $TargetUserName =~ /\$$/) {
drop();
}
</Exec>
</Input>
The same pattern works for any input.
For Windows DNS Server analytical events collected with the Event Tracing for Windows input module, you can drop successful lookups for your own internal zones and keep failures and anything pointing outside.
Event ID 257 is RESPONSE_SUCCESS in Microsoft’s DNS analytic events table.
The Event Tracing for Windows module exposes the query name inside the $dns.query field, which holds a JSON object containing dns.query.name, rather than as a standalone field:
<Input dns_etw>
Module im_etw
Provider Microsoft-Windows-DNSServer
<Exec>
# 257 = RESPONSE_SUCCESS. Keep failures (258) and external names.
if ($EventID == 257 and $dns.query =~ /\.corp\.example\.com/i) {
drop();
}
</Exec>
</Input>
Check before you drop. Microsoft’s table settings guidance warns that moving a table from the analytics tier to the data lake tier stops features that rely on analytics data, including alerting, analytics rules, and advanced hunting. A drop rule has the same effect on the events it removes. Review every candidate event ID against your active analytics rules, hunting queries, and any Microsoft content you’ve enabled. If a detection references it, keep it or route it to the data lake tier (Step 5).
Step 3: Trim fields, not just events
Microsoft Sentinel bills the stored record, so a 4 KB event costs about eight times as much as a 500-byte one. Microsoft’s transformation guidance says it directly: omit columns you don’t need to save cost, and note that columns outside the target schema are billed for ingestion even though they aren’t stored.
Windows events carry metadata you rarely query. You have two ways to shed it on the host.
If you forward the record in JSON format, remove fields first using the delete() procedure.
If no rule reads the message field, set CaptureMessage to FALSE to skip rendering it.
The module documentation also lists this as a processing-time saving.
<Extension json>
Module xm_json
</Extension>
<Input security_events>
Module im_msvistalog
CaptureMessage FALSE (1)
<QueryXML>
<QueryList>
<Query Id="0">
<Select Path="Security">*</Select>
</Query>
</QueryList>
</QueryXML>
<Exec> (2)
delete($Keywords);
delete($Opcode);
delete($OpcodeValue);
delete($TaskValue);
delete($Version);
delete($ExecutionThreadID);
delete($EventReceivedTime);
delete($SourceModuleName);
delete($SourceModuleType);
to_json();
</Exec>
</Input>
| 1 | Set this only if no rule reads the $Message field. |
| 2 | Delete metadata fields you rarely query before converting to JSON. |
For a custom table, an allow-list is the better option. Build a payload that contains only the fields you want, with names that match your table schema one-for-one, and serialize it with the JSON extension. Nothing outside the list leaves the host:
<Extension json>
Module xm_json
</Extension>
<Output sentinel>
Module om_azuremonitor
... (1)
<Exec>
$payload = (
'TimeGenerated' => $EventTime,
'Computer' => $Hostname,
'Channel' => $Channel,
'EventID' => $EventID,
'EventLevel' => $SeverityValue,
'EventRecordId' => $RecordNumber,
'Provider' => $SourceName,
'SubjectUser' => $SubjectUserName,
'TargetUser' => $TargetUserName,
'LogonType' => $LogonType,
'Process' => $NewProcessName,
'Message' => $Message
);
$raw_event = to_json($payload);
</Exec>
</Output>
| 1 | Include the connection directives (Step 4). |
Keep the Message field unless you’ve confirmed no keyword-based rule depends on it.
Keep the EventRecordId, Channel, and Computer fields for correlation and chain of custody.
Step 4: Send through the Logs Ingestion API with a lean DCR
NXLog Agent’s Microsoft Azure Logs Ingestion output module sends to Microsoft Sentinel through the Logs Ingestion API. It authenticates with a Microsoft Entra ID application and targets a data collection rule by its immutable ID. Our Microsoft Sentinel integration guide walks through the Azure side: app registration, data collection endpoint, and a DCR template you can import.
<Output sentinel>
Module om_azuremonitor
API LogsIngestion
TenantId <directory-tenant-id>
ClientId <application-client-id>
ClientSecret <client-secret>
URL https://<dce-name>.<region>-1.ingest.monitor.azure.com
DcrImmutableId dcr-<immutable-id>
StreamName Custom-WindowsSecurity-Stream
<Exec>
... (1)
</Exec>
</Output>
<Route security_events_to_sentinel>
Path security_events => sentinel
</Route>
| 1 | Include the payload mapping (Step 3). |
The module also supports certificate authentication.
Replace ClientSecret with CertKeyFile and CertThumbprint as shown in the integration guide.
Then add a second line of defense inside the DCR. The Microsoft Sentinel data ingestion and transformation docs state that when it is activated on the workspace, transformations to Analytics tables carry no filtering charge, however much they drop. Basic tables are different. They incur the charge once a transformation removes more than 50% of incoming data.
source
| where EventID !in (5156, 5158, 4658, 4690)
| project TimeGenerated, Computer, Channel, EventID, EventLevel,
EventRecordId, Provider, SubjectUser, TargetUser,
LogonType, Process, Message
We recommend using both. The agent filter saves bandwidth and ingest. The DCR transformation catches whatever a misconfigured host sends anyway.
Step 5: Route low-value data to the Microsoft Sentinel data lake tier
Microsoft describes the data lake tier as a low-cost "cold" tier. Data there isn’t available for real-time analytics or hunting, but you can reach it through KQL jobs, scheduled KQL or Spark jobs, and summary rules. You can also ingest directly into the lake without routing through the analytics tier.
At East US list prices, direct lake ingestion costs $0.15/GB ($0.05 ingestion plus $0.10 data processing) against $4.30/GB in the analytics tier, about 96% lower. Those two charges apply to tables set to the data lake tier only. Microsoft does not apply them to tables whose retention covers both the analytics and data lake tiers, because that data is mirrored rather than ingested separately, so the saving below assumes a lake-tier-only table. Storage adds $0.026 per GB per month, billed at a 6:1 compression ratio, and queries add $0.005 per GB scanned. Heavy querying erodes the savings. For firewall allows, proxy logs, NetFlow, and DNS lookups you keep for forensics rather than alerting, the lake is the right home.
Make the split once, on the host. Point two outputs at two DCR streams, one feeding an analytics tier table and one feeding a table you’ve set to the data lake tier. Each output keeps only what belongs to it. NXLog Agent supports route branching, so the same input instance can feed both routes at once:
<Output sentinel_hot>
Module om_azuremonitor
API LogsIngestion
... (1)
DcrImmutableId dcr-<hot-rule-id>
StreamName Custom-SecurityHot-Stream
<Exec>
# Keep only detection-grade events in the analytics tier
if ($EventID NOT IN (4624, 4625, 4648, 4672, 4688, 4720, 4726, 4728, 4732, 4756, 4768, 4769, 4776, 1102)) {
drop();
}
... (2)
</Exec>
</Output>
<Output sentinel_lake>
Module om_azuremonitor
API LogsIngestion
... (1)
DcrImmutableId dcr-<lake-rule-id>
StreamName Custom-SecurityCold-Stream
<Exec>
# Everything else goes to the data lake tier table
if ($EventID IN (4624, 4625, 4648, 4672, 4688, 4720, 4726, 4728, 4732, 4756, 4768, 4769, 4776, 1102)) {
drop();
}
... (2)
</Exec>
</Output>
<Route hot>
Path security_events => sentinel_hot
</Route>
<Route cold>
Path security_events => sentinel_lake
</Route>
| 1 | Include the connection directives (Step 4). |
| 2 | Include the payload mapping and JSON conversion (Step 3). |
The event ID list is illustrative. Yours should come from your detection inventory.
Step 6: Roll it out across the fleet with NXLog Platform
A drop rule on one server won’t change your bill. NXLog Platform lets you build a configuration once and assign it to many NXLog Agent instances in one go. You can author configurations in the visual configuration builder or through the API, so your drop rules can live in version control and go through the same review as your detections.
The rollout loop:
-
Measure with the KQL queries in Step 1.
-
Change the configuration and assign it to a pilot group of agent instances.
-
Re-measure. Confirm your analytics rules still fire on test events.
-
Assign to the rest of the fleet, or roll the configuration back.
Start with NXLog Agent to prove the approach on a few hosts. Move to NXLog Platform when you need to manage the change across hundreds or thousands of hosts.
What this looks like in practice: an illustrative model
This is a model, not a measurement. Replace the percentages with the output of your own queries from Step 1. Prices are East US pay-as-you-go list prices from Microsoft’s Retail Prices API on September 11, 2026, before any commitment discount or free data allowance.
| Stage | Daily volume | Analytics tier cost/day | Lake tier cost/day |
|---|---|---|---|
Baseline: 500 GB/day, all analytics |
500 GB |
$2,150 |
n/a |
Drop 25% of events at the agent |
375 GB |
$1,613 |
n/a |
Trim 10% of bytes from what remains |
337.5 GB |
$1,451 |
n/a |
Route 200 GB/day of that to the lake tier |
137.5 GB hot, 200 GB cold |
$591 |
$30 ingestion and processing, plus storage |
Under these assumptions, the daily analytics bill falls from $2,150 to about $590. Lake storage adds roughly $26 per month for each month of lake data you retain (200 GB/day at 6:1 compression, $0.026 per GB-month), plus query charges. Halve every assumption and the analytics bill still falls by more than a third. The three levers multiply: each one shrinks the volume the next one works on, and all three cost less when applied before ingestion.
The model leaves out commitment-tier pricing (which lowers the baseline), retention beyond 90 days, lake query volume, and the free data sources Microsoft lists, such as Office 365 audit logs and Microsoft Defender alerts. Plug in your own numbers.
Five mistakes that undo the savings
- Filtering at query time
-
A
WHEREclause in a workbook filters data you already paid to ingest. - Dropping events your detections need
-
Check rule references first. Route to the data lake tier when in doubt.
- Forgetting the Basic table rule
-
Transformations that drop more than 50% of a Basic table’s data incur the filtering charge. Analytics tables don’t.
- Moving a table to the lake and then hammering it with queries
-
Lake queries bill per GB scanned.
- Committing before you’ve filtered
-
Microsoft allows lowering a commitment tier only every 31 days. Measure the post-filter volume, then commit.
Conclusion
Drop what you never query, trim what you don’t need, and put the rest in the tier that matches how you use it. Do it on the host, before Microsoft Azure sees the data. If you’d like help mapping your own event inventory to this model, we’re happy to talk it through. You can try NXLog Platform free or get in touch with any questions.
FAQ
- What is the cheapest way to reduce Microsoft Sentinel ingest cost?
-
Stop unneeded data before it reaches Microsoft Azure. Filtering on the collecting agent avoids ingest charges, bandwidth, and DCR processing. DCR transformations are a good second layer, but they run after Microsoft Azure has received the data.
- Does filtering with a DCR transformation cost money?
-
According to Microsoft, not for Analytics tables when Microsoft Sentinel is activated on the workspace. For Basic tables, a filtering charge applies once the transformation drops more than 50% of incoming data.
- What’s the difference between Basic, Auxiliary, and the Microsoft Sentinel data lake tier?
-
Microsoft Sentinel now has two tiers: analytics and data lake. When you onboard to the data lake, Auxiliary Logs meters are replaced by data lake meters. Basic Logs remain a Log Analytics plan with its own rate, currently $1.00/GB in East US.
- Can I filter Windows Security events before they reach Microsoft Sentinel?
-
Yes. NXLog Agent’s Event Log for Windows input module accepts Windows
QueryXMLwith<Suppress>elements, and anExecblock lets you drop or rewrite events on any field before sending. - Does NXLog Agent support the Logs Ingestion API and data collection rules?
-
Yes. The Microsoft Azure Logs Ingestion output module targets a DCR by its immutable ID and a named stream, and authenticates with an Entra ID application using a client secret or a certificate.
- Will filtering break Microsoft’s built-in analytics rules?
-
It can, if you drop an event field a rule depends on. Review your rules and hunting queries before each change, pilot on a subset of agents, and re-test detections after deployment.