AWS WAF Log Format: Field Reference and Example Records
Published
One JSON record per web request that an AWS WAF web ACL evaluated: which rule decided the request, whether it was allowed or blocked, and the client IP, URI, query string and headers WAF saw.
Official documentation: Logging AWS WAF web ACL traffic (AWS WAF Developer Guide) · Log fields (AWS WAF Developer Guide)
Sample record
{"timestamp":1705312800100,"formatVersion":1,"webaclId":"arn:aws:wafv2:us-east-1:123456789012:regional/webacl/MyWebACL/abc123","terminatingRuleId":"Default_Action","terminatingRuleType":"REGULAR","action":"ALLOW","httpSourceName":"ALB","httpSourceId":"123456789012-app/my-alb/50dc6c495c0c9188","ruleGroupList":[],"rateBasedRuleList":[],"nonTerminatingMatchingRules":[],"requestHeadersInserted":null,"responseCodeSent":null,"httpRequest":{"clientIp":"203.0.113.10","country":"US","headers":[{"name":"Host","value":"api.example.com"},{"name":"User-Agent","value":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"},{"name":"Accept","value":"application/json"},{"name":"Authorization","value":"Bearer eyJhbGciOiJSUzI1NiJ9..."}],"uri":"/api/v1/products","args":"category=electronics&page=1","httpVersion":"HTTP/1.1","httpMethod":"GET","requestId":"aaa111bbb222"}} From the LogTriage parser test corpus — synthetic data, no real hosts or users.
Field reference
| Field | Type | Meaning | Why it matters for security |
|---|---|---|---|
| timestampe.g. 1705312800100 | integer (epoch milliseconds) | When AWS WAF evaluated the request. | Milliseconds, not seconds — convert before lining records up with ALB, CloudFront or application logs. |
| webaclIde.g. arn:aws:wafv2:us-east-1:123456789012:regional/webacl/MyWebACL/abc123 | string (ARN) | ARN of the web ACL that evaluated the request. | Identifies the account, Region and scope (regional or CloudFront) when several web ACLs write to one destination. |
| actione.g. BLOCK | string | The action AWS WAF took on the request. | ALLOW with an attack signature in the URI or headers means nothing stopped it — check the application's own logs for that request. |
| terminatingRuleIde.g. AWS-AWSManagedRulesSQLiRuleSet | string | The rule that ended evaluation, or Default_Action when no rule terminated the request. | Groups blocks by the kind of attack (SQLi, known bad inputs, common rule set, custom rate limit). |
| terminatingRuleTypee.g. MANAGED_RULE_GROUP | string | Type of the terminating rule — REGULAR, MANAGED_RULE_GROUP and RATE_BASED appear in these samples. | Separates managed-rule exploit blocks from rate-based throttling, which are different investigations. |
| ruleGroupList.terminatingRule.ruleIde.g. SQLi_BODY | string | The individual rule inside the rule group that terminated the request. | The most specific label you get — SQLi_BODY, Log4JRCE and CrossSiteScripting_BODY say exactly what was detected. |
| nonTerminatingMatchingRules | array | Rules that matched the request but did not end evaluation, such as rules running in Count mode. | A request that was allowed can still have matched attack rules here; filtering only on action BLOCK hides it. |
| rateBasedRuleList.maxRateAllowede.g. 100 | integer | The configured rate limit of the rate-based rule that applied. | Puts a block in context — how much traffic the source had to send before WAF reacted. |
| responseCodeSente.g. 429 | integer or null | The response code sent when a custom response is configured; null otherwise. | Tells you what the client actually saw, which helps match the event to the attacker's own retry behaviour. |
| httpSourceNamee.g. ALB | string | Type of protected resource the request came through. | Says which front door was probed when one web ACL protects several resource types. |
| httpRequest.clientIpe.g. 203.0.113.10 | string (IPv4/IPv6) | Source address of the request as AWS WAF saw it. | The pivot for everything — count blocks, rules and URIs per clientIp to separate one scanner from background noise. |
| httpRequest.countrye.g. US | string (two-letter code) | Country derived from the client IP. | Useful for grouping, but VPNs, Tor and cloud egress make it weak evidence on its own. |
| httpRequest.urie.g. /api/v1/products | string | Path portion of the request. | Shows what was targeted; a single source touching many unrelated paths is enumeration. |
| httpRequest.argse.g. category=electronics&page=1 | string | Query string of the request, URL-encoded as the client sent it. | Carries injection payloads — decode before matching on keywords such as UNION or SELECT. |
| httpRequest.headers | array of name/value objects | Request headers AWS WAF captured, each as a separate object with name and value. | Where scanner User-Agents and header-based payloads show up; it is a list, so you must search by header name rather than index. |
| httpRequest.requestIde.g. aaa111bbb222 | string | Identifier AWS WAF recorded for the request. | Anchor for correlating one request across WAF and downstream logs. |
What it records
Each line is a JSON object describing one HTTP request that an AWS WAF web ACL evaluated. The record says what WAF decided (action), which rule made that decision (terminatingRuleId, terminatingRuleType and, for managed rule groups, the ruleId inside ruleGroupList), and what the request looked like: httpRequest.clientIp, country, httpMethod, uri, args, httpVersion and the captured headers.
The malicious sample shows the three shapes you most often triage: a managed rule group block (AWS-AWSManagedRulesSQLiRuleSet / SQLi_BODY), a known-bad-inputs block (Log4JRCE) where the payload sits in headers rather than the URI, and a rate-based block that returned responseCodeSent 429. The benign sample shows the baseline — terminatingRuleId Default_Action with action ALLOW.
Where to get it
Enable logging on the web ACL and pick a destination: a CloudWatch Logs log group, an Amazon S3 bucket, or an Amazon Data Firehose delivery stream. The name of the destination must begin with aws-waf-logs-. CloudWatch Logs is convenient for short-term queries; S3 is the cheap option for long retention and for feeding a SIEM in bulk.
Two settings change what you receive, so check them before trusting a quiet log: logging filters, which decide which requests are written at all, and field redaction, which blanks named fields such as an authorization header. httpSourceId identifies the specific protected resource — in these samples the ALB 123456789012-app/my-alb/50dc6c495c0c9188.
Parsing pitfalls
- Milliseconds.
timestampis epoch milliseconds. Dividing by 1000 is the usual fix when WAF events land fifty years in the future next to your ALB logs. - Headers are a list, not a map.
httpRequest.headersis an array of{"name": ..., "value": ...}objects in whatever order they arrived. Look upUser-Agentby name; never by position. - Count mode hides in a different field. Rules that matched without terminating appear in
nonTerminatingMatchingRules(and in the per-groupnonTerminatingMatchingRules), on records whoseactionisALLOW. A query filtered onaction == "BLOCK"misses every rule you deployed in Count mode. - Percent-encoding.
argsis stored as sent —id=1%20UNION%20SELECT...in the malicious sample. Decode before keyword matching, or your SQLi searches quietly return nothing. responseCodeSentis usually null. It is populated when a custom response is configured; the rate-based block in the sample shows 429, the managed-rule blocks show null.clientIpis who WAF saw. If another CDN or proxy sits in front of the protected resource, the originating client is in a forwarded header instead, andcountryis derived from whatever address WAF observed.- One JSON object per line. The files are newline-delimited JSON, not a JSON array; parsers that expect a top-level array read zero records.
Detection ideas
- One source, several rule groups: the same
httpRequest.clientIpblocked by SQLi, known-bad-inputs and common-rule-set rules within minutes is an automated scan working through a payload list — the basis of the AWS WAF managed rule exploit block rule. - Tooling in the headers: User-Agent values such as
sqlmap/1.7.8#stable (https://sqlmap.org)or a bareMozilla/5.0 (compatible; scanner), even on records withactionALLOW. - Payloads outside the URI:
${jndi:appearing in any header value, as theLog4JRCErecord shows, where theuriitself is the innocuous/api/v1/health. - Rate-based blocks on sensitive paths:
terminatingRuleTypeRATE_BASEDagainst authentication or account endpoints with a scripted agent likepython-requests/2.28.0points at credential stuffing rather than a noisy customer. - What got through: for every blocked source, list its
ALLOWrecords in the same window. Those are the requests your application actually handled.
The free browser analyzer runs these checks on an exported WAF log file locally, without uploading it.
Detections and guides for AWS WAF
Frequently Asked Questions
- Does a BLOCK record mean the request never reached my application?
- A blocked request is stopped at the web ACL and answered by AWS WAF instead of being forwarded to the protected resource, so the application never processes it. The record is still evidence of intent — the same source usually tries other paths and payloads that no rule matches.
- What does terminatingRuleId Default_Action mean?
- No rule in the web ACL terminated evaluation, so the web ACL's default action decided the request. In the benign sample those records pair Default_Action with action ALLOW, which is what ordinary traffic looks like in these logs.
- Why do some attack requests not appear in the log at all?
- Logging is configured per web ACL, and logging filters can limit which requests are written. Redaction can also blank individual fields. Treat a gap as a configuration question first, and confirm what the web ACL was set to log during the time window before concluding nothing happened.
- Is the request body in the log?
- No. The httpRequest object carries the method, URI, query string, HTTP version and headers. Body-inspecting rules such as SQLi_BODY can fire without the body itself being written, so the ruleId is often the only description of what matched.
Analyze AWS WAF logs
LogTriage auto-detects this format, enriches every IP, user agent and hash with threat intelligence, and writes an incident report mapped to MITRE ATT&CK.