<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Peakhour.IO - False Positives</title><link href="https://www.peakhour.io/" rel="alternate"></link><link href="https://www.peakhour.io/feeds/tag/false-positives.atom.xml" rel="self"></link><id>https://www.peakhour.io/</id><updated>2026-08-01T12:00:00+10:00</updated><entry><title>Privacy Relay, Corporate VPN, or Residential Proxy?</title><link href="https://www.peakhour.io/blog/privacy-relay-vpn-residential-proxy-policy/" rel="alternate"></link><published>2026-08-01T12:00:00+10:00</published><updated>2026-08-01T12:00:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-08-01:/blog/privacy-relay-vpn-residential-proxy-policy/</id><summary type="html">&lt;p&gt;An anonymised connection is not automatically abusive. Security policy should distinguish expected privacy and corporate access from proxy use that appears beside risky behaviour.&lt;/p&gt;</summary><content type="html">&lt;p&gt;An application sees a login from an address that does not match the user's apparent location. The connection belongs to a relay, VPN, or proxy network.&lt;/p&gt;
&lt;p&gt;That is a reason to look closer. It is not a reason to call the user malicious.&lt;/p&gt;
&lt;p&gt;Privacy relays, corporate VPNs, consumer VPNs, carrier-grade NAT, and residential proxies all weaken the old assumption that an IP address describes one person in one place. They do it for different reasons and with different operating models. A security policy that reduces them to &lt;code&gt;anonymous=true&lt;/code&gt; will either miss abuse or punish legitimate users.&lt;/p&gt;
&lt;h2&gt;Privacy Services Deliberately Separate Identity from Destination&lt;/h2&gt;
&lt;p&gt;Apple says iCloud Private Relay uses separate internet relays so no single party can see both who a user is and which sites they visit. It also lets the user preserve a general location or use a broader country-and-time-zone location. Apple's &lt;a href="https://support.apple.com/en-us/102602"&gt;Private Relay overview&lt;/a&gt; explains the scope and the role of the relay addresses.&lt;/p&gt;
&lt;p&gt;That design changes what a website can infer from the source IP. It does not remove the session, account, route, browser, or request behaviour available to the application.&lt;/p&gt;
&lt;p&gt;A corporate VPN creates a different pattern. Many employees may exit through a small set of company gateways. The source can be stable and organisation-owned, but the address still represents a population rather than one device. A consumer VPN may rotate gateways and mix unrelated subscribers. Carrier-grade NAT can make a normal mobile or broadband address shared even when no privacy product is involved; &lt;a href="https://www.rfc-editor.org/rfc/rfc6598"&gt;RFC 6598&lt;/a&gt; documents shared address space reserved for carrier deployments.&lt;/p&gt;
&lt;p&gt;Residential proxy services are different again. A remote operator deliberately routes through consumer connectivity so the destination sees the household or mobile exit. The exit may have joined through an informed participant, an embedded SDK, compromised software, or another supply path.&lt;/p&gt;
&lt;p&gt;The network categories matter. They still do not establish intent.&lt;/p&gt;
&lt;h2&gt;Define the Expected Population&lt;/h2&gt;
&lt;p&gt;Policy should reflect the application and route.&lt;/p&gt;
&lt;p&gt;A workforce portal may know its corporate VPN ranges and require device or certificate evidence. A consumer service should expect mobile networks, privacy relays, travel, and shared households. A country-restricted service may need stronger location verification than an online catalogue. An API used by fixed partners can enforce network expectations that would be unreasonable on a public login page.&lt;/p&gt;
&lt;p&gt;Ask:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is this network type expected for this user population?&lt;/li&gt;
&lt;li&gt;Is the route public, authenticated, sensitive, or expensive?&lt;/li&gt;
&lt;li&gt;Does the account have established device and session history?&lt;/li&gt;
&lt;li&gt;Is the apparent location material to the transaction?&lt;/li&gt;
&lt;li&gt;Can the client provide another form of evidence?&lt;/li&gt;
&lt;li&gt;What does a false rejection cost?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The answer determines whether the network signal should be ignored, logged, challenged, or combined with stronger controls.&lt;/p&gt;
&lt;h2&gt;Use Context to Separate Risk from Privacy&lt;/h2&gt;
&lt;p&gt;&lt;a href="/products/ip-intelligence/"&gt;Peakhour IP Intelligence&lt;/a&gt; and &lt;a href="/products/residential-proxy-detection/"&gt;Residential Proxy Detection&lt;/a&gt; place network context beside credentials, device history, fingerprints, behaviour, route, and recent events.&lt;/p&gt;
&lt;p&gt;Consider two logins through privacy infrastructure.&lt;/p&gt;
&lt;p&gt;The first uses an established session, familiar device history, a normal route sequence, and one successful authentication. The second attempts many accounts, carries an exposed credential, has no normal session history, and repeats the same failure pattern across rotating addresses.&lt;/p&gt;
&lt;p&gt;Both hide or replace the direct source address. Only one presents a strong account-abuse pattern.&lt;/p&gt;
&lt;p&gt;The policy should be able to express that difference. Blocking every relay or VPN is not contextual security. It is a network-category ban.&lt;/p&gt;
&lt;h2&gt;Geography Needs the Same Restraint&lt;/h2&gt;
&lt;p&gt;IP geolocation is an estimate of network presence, not proof of a person's physical location. VPN gateways, relays, mobile routing, satellite networks, corporate egress, and proxy exits can all separate the address from the user.&lt;/p&gt;
&lt;p&gt;Use geography as evidence proportionate to the decision. A content preference may tolerate a coarse estimate. A regulated transaction may require account evidence, verified identity, payment context, or an explicit location control beyond an IP lookup.&lt;/p&gt;
&lt;p&gt;Avoid impossible-travel rules that treat every IP change as physical movement. A session can switch between mobile, Wi-Fi, VPN, and relay paths without the person travelling at all. Look for continuity in the account, device, session, and behaviour before escalating.&lt;/p&gt;
&lt;h2&gt;Choose an Action That Can Be Recovered From&lt;/h2&gt;
&lt;p&gt;The action can then scale with the evidence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allow expected privacy or corporate access with consistent session evidence.&lt;/li&gt;
&lt;li&gt;Log an unfamiliar network category on low-consequence routes.&lt;/li&gt;
&lt;li&gt;Apply tighter rate limits when repeated behaviour spans accounts or rotating exits.&lt;/li&gt;
&lt;li&gt;Challenge an interactive client when network change appears beside account risk.&lt;/li&gt;
&lt;li&gt;Require step-up verification for recovery, payment, or access-control changes.&lt;/li&gt;
&lt;li&gt;Block confirmed automated abuse with bounded route and policy scope.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Challenges are not perfect humanity tests. Automation can complete them, while privacy tools, accessibility software, or broken browser environments can cause legitimate failures. Measure completion and provide a recovery path.&lt;/p&gt;
&lt;h2&gt;Retain the Reason&lt;/h2&gt;
&lt;p&gt;When a policy acts, retain the network category, source, route, contributing evidence, action, and outcome. “Anonymous IP” is too vague for support or incident review.&lt;/p&gt;
&lt;p&gt;The record should show whether the deciding factor was proxy use, an exposed credential, failure velocity, a new device, a route sequence, or several observations agreeing. That evidence lets the team tune the control without adding broad exceptions that erase protection.&lt;/p&gt;
&lt;p&gt;Privacy and security are not opposing verdicts attached to an address. They are requirements the application has to handle at the same time.&lt;/p&gt;</content><category term="Security"></category><category term="Privacy"></category><category term="Residential Proxies"></category><category term="VPN"></category><category term="IP Intelligence"></category><category term="False Positives"></category></entry><entry><title>How to Tune F5 BIG-IP Advanced WAF</title><link href="https://www.peakhour.io/blog/how-to-tune-f5-big-ip-advanced-waf/" rel="alternate"></link><published>2026-07-29T10:15:00+10:00</published><updated>2026-07-29T10:15:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-07-29:/blog/how-to-tune-f5-big-ip-advanced-waf/</id><summary type="html">&lt;p&gt;Run F5 BIG-IP Advanced WAF in Transparent mode, review staged matches and learning suggestions, and limit each exception to the URL, parameter, cookie or content profile that needs it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;An F5 policy has three separate controls between a signature match and a blocked request.&lt;/p&gt;
&lt;p&gt;The policy has an enforcement mode. Each violation has Learn, Alarm and Block settings. Each signature or policy entity can also be in staging.&lt;/p&gt;
&lt;p&gt;Record all three before changing the policy.&lt;/p&gt;
&lt;h2&gt;Start a new policy in Transparent mode&lt;/h2&gt;
&lt;p&gt;Set a new policy, a major policy revision or a newly protected application to &lt;strong&gt;Transparent&lt;/strong&gt; under &lt;strong&gt;Security &amp;gt; Application Security &amp;gt; Policy Building &amp;gt; Learning and Blocking Settings&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Transparent mode allows requests through while the policy records violations. F5 normally generates a learning suggestion when the violation has Learn enabled. Save the change and select &lt;strong&gt;Apply Policy&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Blocking mode behaves differently. A request is blocked only when the policy is in Blocking mode, the violation has Block enabled and the matching entity or signature has left staging. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/configuring-security-policy-blocking.html"&gt;F5 documents the interaction between enforcement mode, violation actions and staging&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;F5 calls the staging window the &lt;strong&gt;Enforcement Readiness Period&lt;/strong&gt;. The default is seven days. During that period, the policy does not enforce staged signatures or entities. Configure learning and logging to capture their matches. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/changing-security-policy-settings.html"&gt;The policy settings guide explains Transparent mode and enforcement readiness&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Seven quiet days prove only that the traffic seen during those days was quiet. Include month-end jobs, imports, account recovery, administration, checkout, API writes and other flows that ordinary browsing misses.&lt;/p&gt;
&lt;p&gt;For an established blocking policy, stage the signatures or entities affected by a change. The rest of the policy can remain in force while those changes collect traffic.&lt;/p&gt;
&lt;h2&gt;Log staged signature matches&lt;/h2&gt;
&lt;p&gt;The default logging profile records illegal requests. A staged attack-signature match can be allowed and therefore needs an explicit logging choice.&lt;/p&gt;
&lt;p&gt;Create or edit an application-security logging profile under &lt;strong&gt;Security &amp;gt; Event Logs &amp;gt; Logging Profiles&lt;/strong&gt;. Set the request filter to &lt;strong&gt;Illegal requests, and requests that include staged attack signatures&lt;/strong&gt;. Associate the profile with the virtual server that runs the security policy.&lt;/p&gt;
&lt;p&gt;F5 supports local and remote logging profiles. Local logging competes for resources on the BIG-IP system; remote logging can send CSV, key-value or CEF records to a reporting system. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/logging-application-security-events.html"&gt;F5 documents the logging destinations, staged-signature filter and virtual-server association&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Keep these fields for each investigation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;security policy and virtual server;&lt;/li&gt;
&lt;li&gt;support ID and event time;&lt;/li&gt;
&lt;li&gt;hostname, URL and method;&lt;/li&gt;
&lt;li&gt;violation name;&lt;/li&gt;
&lt;li&gt;attack-signature ID and attack type;&lt;/li&gt;
&lt;li&gt;parameter, cookie, header or request area that matched;&lt;/li&gt;
&lt;li&gt;matched data;&lt;/li&gt;
&lt;li&gt;violation rating;&lt;/li&gt;
&lt;li&gt;enforcement mode and staging state;&lt;/li&gt;
&lt;li&gt;application response.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The support ID ties a user-facing block page to the stored request. The violation rating runs from 1 to 5. F5 describes 1 as most likely a false positive and 5 as most likely an attack. Use the rating to order the work, then read the request and ask the application owner what the input means. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/working-with-violations.html"&gt;F5 lists the request details and explains violation ratings&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Group events by policy, violation, signature ID, URL, method and named request field. A signature firing in &lt;code&gt;comment&lt;/code&gt; on &lt;code&gt;POST /api/tickets&lt;/code&gt; needs a different review from the same signature firing in an authentication parameter.&lt;/p&gt;
&lt;p&gt;Request logs may contain credentials, personal information and customer text. Mark account numbers, tokens and other secrets as sensitive parameters so F5 masks their stored values. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/adding-entities-to-a-security-policy.html"&gt;F5 documents the sensitive-parameter setting and log masking&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Read the request behind each learning suggestion&lt;/h2&gt;
&lt;p&gt;Traffic Learning turns observed violations and new entities into proposed policy changes. Open &lt;strong&gt;Security &amp;gt; Application Security &amp;gt; Policy Building &amp;gt; Traffic Learning&lt;/strong&gt;, select the policy and inspect the requests attached to each suggestion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Accept Suggestion&lt;/strong&gt; changes the policy. Treat it like any other configuration change.&lt;/p&gt;
&lt;p&gt;Manual learning waits for an operator to accept a suggestion. Automatic learning can apply most suggestions after F5 has seen enough traffic, sessions, time and source addresses. Manual learning keeps each relaxation visible while a new or sensitive application is being tuned. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/changing-how-a-security-policy-is-built.html"&gt;F5 describes Manual, Automatic and Disabled learning modes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A busy suggestion can still describe a busy attack. Confirm that the requests reached the expected application path, parsed successfully and produced the expected business result.&lt;/p&gt;
&lt;p&gt;Ask the developer who owns the endpoint:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what values the field accepts;&lt;/li&gt;
&lt;li&gt;how the application parses them;&lt;/li&gt;
&lt;li&gt;whether the value is stored, rendered or executed;&lt;/li&gt;
&lt;li&gt;which server-side validation protects it;&lt;/li&gt;
&lt;li&gt;whether the field has the same meaning on every URL;&lt;/li&gt;
&lt;li&gt;which test request reproduces the legitimate match.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Accept a suggestion only after its scope matches the application behaviour. Ignore removes a suggestion from the normal Traffic Learning view; it does not repair a false positive. F5 lets operators accept, delete or ignore suggestions and inspect the requests behind them. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/refining-security-policies-with-learning.html"&gt;The Traffic Learning guide documents those actions&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Check how F5 parsed the body&lt;/h2&gt;
&lt;p&gt;A JSON request treated as unparsed content produces different signature targets from a JSON request split into named parameters.&lt;/p&gt;
&lt;p&gt;Check the allowed URL and its &lt;strong&gt;Request Body Handling&lt;/strong&gt; setting. F5 can parse form data, JSON or XML, or scan an unparsed payload with content and value signatures. A JSON or XML setting also names the content profile used for validation. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/adding-entities-to-a-security-policy.html"&gt;F5 lists the request-body handling modes in its allowed-URL documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Match the profile to the content type the application accepts. Associate the profile with the correct URL or parameter. Reproduce malformed bodies with the same bytes because the application and F5 may recover from parser errors differently.&lt;/p&gt;
&lt;p&gt;Use explicit parameters for fields that need their own policy. F5 supports global, URL and flow parameters, and applies the most specific definition first: flow, then URL, then global. A free-text &lt;code&gt;comment&lt;/code&gt; field on one endpoint usually belongs at URL level.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Ignore value&lt;/strong&gt; parameter type removes parameter-based signature checks from that value while leaving other relevant signature checks active. A one-signature override removes less inspection. Reserve &lt;strong&gt;Ignore value&lt;/strong&gt; for data the application genuinely does not interpret.&lt;/p&gt;
&lt;h2&gt;Put the exception on the field or URL that needs it&lt;/h2&gt;
&lt;p&gt;F5 exposes several scopes for an attack-signature override:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;one signature on one URL parameter;&lt;/li&gt;
&lt;li&gt;one signature on one named cookie;&lt;/li&gt;
&lt;li&gt;one signature in a content profile;&lt;/li&gt;
&lt;li&gt;one signature on an allowed URL;&lt;/li&gt;
&lt;li&gt;one signature across the security policy.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Choose the scope that matches the application.&lt;/p&gt;
&lt;p&gt;For a false positive in &lt;code&gt;comment&lt;/code&gt; on &lt;code&gt;/api/tickets&lt;/code&gt;, define &lt;code&gt;comment&lt;/code&gt; as a URL parameter and disable the signature for that parameter. The same signature continues to inspect other parameters and other URLs.&lt;/p&gt;
&lt;p&gt;F5 also allows a signature override on an allowed cookie. Use that control when a named analytics or session-related cookie carries the legitimate value. Cookie names and values have their own configuration, so record which part produced the match.&lt;/p&gt;
&lt;p&gt;A content-profile override applies wherever that JSON, XML or plain-text profile is associated. Check every URL and parameter using the profile before changing it. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/adding-json-support-to-an-existing-security-policy.html"&gt;F5 documents signature overrides and associations for JSON profiles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;An allowed-URL override disables the selected signature for the whole URL. Every request field on that URL receives the exception. F5 describes URL-level signature overrides as a common operation and marks overridden signatures in the policy list. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/adding-entities-to-a-security-policy.html"&gt;The allowed-URL and cookie procedures describe their attack-signature overrides&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Disabling a signature at policy level removes it from the whole application-security policy. Use that scope only when the signature is irrelevant to every request covered by the policy.&lt;/p&gt;
&lt;p&gt;Record the signature ID, signature revision, URL, parameter or cookie, matched examples, application owner and protections that remain.&lt;/p&gt;
&lt;h2&gt;Apply, synchronise and test the policy&lt;/h2&gt;
&lt;p&gt;Saving an edit does not put it into the active policy. Select &lt;strong&gt;Apply Policy&lt;/strong&gt;, wait for the operation to finish and confirm that the virtual server still has the intended application-security and logging profiles.&lt;/p&gt;
&lt;p&gt;Synchronise the change to the peer device when the BIG-IP pair uses manual ConfigSync. F5 can display a sync reminder after policy changes in a high-availability configuration. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/configuring-general-asm-system-options.html"&gt;The Advanced WAF preferences guide describes Apply Policy confirmation, change logging and the ConfigSync reminder&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Replay:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the original legitimate request;&lt;/li&gt;
&lt;li&gt;the same value in another parameter;&lt;/li&gt;
&lt;li&gt;the same value on another URL;&lt;/li&gt;
&lt;li&gt;a safe signature test in the excluded field;&lt;/li&gt;
&lt;li&gt;the safe test in a neighbouring field;&lt;/li&gt;
&lt;li&gt;malformed and oversized versions of the request;&lt;/li&gt;
&lt;li&gt;normal application traffic.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The legitimate request should complete without the false-positive event. The safe test in the excluded scope shows the inspection removed by the exception. Neighbouring fields and URLs should still produce the expected signature event.&lt;/p&gt;
&lt;p&gt;Check the support ID, signature ID, staging state and application response after every replay.&lt;/p&gt;
&lt;h2&gt;Compare and export the policy&lt;/h2&gt;
&lt;p&gt;Use &lt;strong&gt;Security &amp;gt; Application Security &amp;gt; Security Policies &amp;gt; Policy Diff&lt;/strong&gt; to compare the tuned policy with the earlier policy or a staging copy. F5 can compare active, inactive and exported policies.&lt;/p&gt;
&lt;p&gt;Export the policy before a large tuning session, signature update or BIG-IP upgrade. A readable XML export supports review and version control; a binary policy archive preserves a restorable copy.&lt;/p&gt;
&lt;p&gt;Compact XML omits attack-signature staging state and omits some settings that remain at their defaults. Use the full XML format when the review needs to show staging. Export user-defined attack signatures separately because a policy export contains their sets without the signatures themselves. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/maintaining-security-policies.html"&gt;F5 documents Policy Diff and the export-format limits&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Stage signature updates and application releases&lt;/h2&gt;
&lt;p&gt;F5 Live Update places newly added attack signatures in staging. Updated signatures follow the policy's &lt;strong&gt;Updated Signature Enforcement&lt;/strong&gt; setting. Review that setting before installing an attack-signature update. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/updating-security-components.html"&gt;F5 documents the post-update staging behaviour&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Keep signature staging enabled during the observation period. Review each high-volume match, then enforce the signatures that have clean traffic and tested application flows.&lt;/p&gt;
&lt;p&gt;Run the same process after an application release that changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public URLs or methods;&lt;/li&gt;
&lt;li&gt;parameter names or value types;&lt;/li&gt;
&lt;li&gt;JSON, XML, GraphQL or multipart bodies;&lt;/li&gt;
&lt;li&gt;cookies and headers;&lt;/li&gt;
&lt;li&gt;uploads;&lt;/li&gt;
&lt;li&gt;authentication and account flows.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tell the application team which policy changes are waiting in staging. The application team knows whether a new field belongs in the release.&lt;/p&gt;
&lt;h2&gt;Keep rate limiting in the DoS profile&lt;/h2&gt;
&lt;p&gt;F5 application-security policies inspect individual requests. BIG-IP Layer 7 DoS protection measures transaction rates and server latency through a separate DoS profile attached to the virtual server.&lt;/p&gt;
&lt;p&gt;Configure rate limiting under the DoS profile when repeated traffic is the problem. F5 can rate limit suspicious source addresses, countries, URLs or a site under attack. Transparent and Blocking modes for the DoS profile control whether those conditions are reported or mitigated. &lt;a href="https://techdocs.f5.com/en-us/bigip-17-5-0/big-ip-asm-implementations/preventing-dos-attacks-on-applications.html"&gt;F5 documents TPS-based detection, stress-based detection and request rate limiting&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Track WAF signature blocks and DoS rate limits as separate outcomes. A false-positive signature needs a policy exception. The application owner must set an acceptable rate and response for excessive legitimate traffic.&lt;/p&gt;
&lt;h2&gt;Move the policy to Blocking&lt;/h2&gt;
&lt;p&gt;Switch the policy from Transparent to Blocking after:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;normal and infrequent application flows have run;&lt;/li&gt;
&lt;li&gt;every repeated legitimate match has been classified;&lt;/li&gt;
&lt;li&gt;each exception has the smallest supported scope;&lt;/li&gt;
&lt;li&gt;staged entities and signatures have been reviewed;&lt;/li&gt;
&lt;li&gt;Learn, Alarm and Block settings match the intended response;&lt;/li&gt;
&lt;li&gt;the replay tests pass;&lt;/li&gt;
&lt;li&gt;the active and standby devices have the same applied policy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Move a small application or virtual server first when the deployment supports it. Watch blocked requests, application errors and business transactions together.&lt;/p&gt;
&lt;p&gt;The blocking response includes a support ID. Keep the event long enough for support staff to find the exact request without storing customer content in the exception record.&lt;/p&gt;
&lt;p&gt;Review the policy after each application release and signature update. Remove an override when the field, parser or application behaviour that required it has gone.&lt;/p&gt;</content><category term="Application Security"></category><category term="F5"></category><category term="BIG-IP Advanced WAF"></category><category term="WAF"></category><category term="Application Security"></category><category term="False Positives"></category><category term="WAF Tuning"></category></entry><entry><title>How to Tune Google Cloud Armor WAF Rules</title><link href="https://www.peakhour.io/blog/how-to-tune-google-cloud-armor-waf/" rel="alternate"></link><published>2026-07-29T10:00:00+10:00</published><updated>2026-07-29T10:00:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-07-29:/blog/how-to-tune-google-cloud-armor-waf/</id><summary type="html">&lt;p&gt;Tune Cloud Armor preconfigured WAF rules with preview traffic, versioned signatures, sensitivity levels, field exclusions and policy priority.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cloud Armor evaluates security-policy rules by priority. The request log shows the preview rule that matched and the enforced rule that decided the request.&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;allow&lt;/code&gt; rule at priority 100 accepts the request before a SQL injection rule at priority 1000 can inspect it. A WAF rule in preview records the action it would have taken while an enforced rule decides the request.&lt;/p&gt;
&lt;h2&gt;Preview one WAF rule&lt;/h2&gt;
&lt;p&gt;Cloud Armor preview belongs to individual security-policy rules. The configured action is recorded while policy evaluation continues to an enforced rule. Both results appear in the request log when the preview rule takes priority. &lt;a href="https://docs.cloud.google.com/armor/docs/security-policy-overview#preview-mode"&gt;Google documents this evaluation behaviour and the &lt;code&gt;--preview&lt;/code&gt; flag&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The commands below use a global service-level security policy. Add &lt;code&gt;--region=REGION&lt;/code&gt; to each command when working with a regional policy.&lt;/p&gt;
&lt;p&gt;Create or update the WAF rule with a deny action and preview enabled:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gcloud&lt;span class="w"&gt; &lt;/span&gt;compute&lt;span class="w"&gt; &lt;/span&gt;security-policies&lt;span class="w"&gt; &lt;/span&gt;rules&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--security-policy&lt;span class="w"&gt; &lt;/span&gt;app-policy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--expression&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;evaluatePreconfiguredWaf(&amp;#39;sqli-v422-stable&amp;#39;, {&amp;#39;sensitivity&amp;#39;: 1})&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--action&lt;span class="w"&gt; &lt;/span&gt;deny-403&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--preview&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--description&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SQLi v4.22 sensitivity 1&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sensitivity 1 is a sensible starting point for most applications. It enables the higher-confidence signatures and usually produces less noise than higher levels. Google recommends starting new preconfigured WAF rules at sensitivity 1 and validating them in preview. &lt;a href="https://docs.cloud.google.com/armor/docs/best-practices#rule_tuning"&gt;The current Cloud Armor best-practices guide gives both recommendations&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Read both policy results in the request log&lt;/h2&gt;
&lt;p&gt;Cloud Armor adds its policy results to the load balancer’s per-request log. Request logging is disabled by default for new backend services, so enable it before starting the preview. Look for &lt;code&gt;previewSecurityPolicy&lt;/code&gt; to see the preview match and &lt;code&gt;enforcedSecurityPolicy&lt;/code&gt; to see the enforced result. The &lt;code&gt;preconfiguredExprIds&lt;/code&gt; array contains every WAF signature that triggered the matching policy rule. &lt;a href="https://docs.cloud.google.com/armor/docs/request-logging#security_policy_log_entries"&gt;Google lists the logging prerequisite and policy fields in the Cloud Armor request-logging reference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A Logs Explorer query can isolate the preview rule:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;resource.type=&amp;quot;http_load_balancer&amp;quot;
jsonPayload.previewSecurityPolicy.name=&amp;quot;app-policy&amp;quot;
jsonPayload.previewSecurityPolicy.priority=1000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Group the results by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;preconfiguredExprIds&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;request path and method;&lt;/li&gt;
&lt;li&gt;preview rule priority;&lt;/li&gt;
&lt;li&gt;enforced outcome;&lt;/li&gt;
&lt;li&gt;client or application flow;&lt;/li&gt;
&lt;li&gt;time and release version.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use our &lt;a href="/blog/how-to-tune-your-waf/"&gt;general WAF tuning guide&lt;/a&gt; to classify those repeated matches and choose the smallest safe exception.&lt;/p&gt;
&lt;p&gt;Enable verbose logging for a short investigation when the ordinary log shows the signature but omits the input. It adds &lt;code&gt;matchedFieldType&lt;/code&gt;, &lt;code&gt;matchedFieldName&lt;/code&gt; and a short &lt;code&gt;matchedFieldValue&lt;/code&gt; snippet for requests matching preconfigured WAF rules. Those snippets can contain customer or personal data. Google recommends disabling verbose logging during normal operation. &lt;a href="https://docs.cloud.google.com/armor/docs/verbose-logging"&gt;The verbose-logging guide describes the extra fields and privacy risk&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Match the signature ID to the ruleset version&lt;/h2&gt;
&lt;p&gt;The name &lt;code&gt;sqli-v422-stable&lt;/code&gt; identifies the SQL injection ruleset built from CRS 4.22. A signature inside it has the same version in its ID, such as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;owasp-crs-v042200-id942350-sqli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Use the ID written by the policy log. Cloud Armor requires an opted-out signature ID to match the ruleset version, and rejects mismatched versions. &lt;a href="https://docs.cloud.google.com/armor/docs/rule-tuning#opt_out_rule_signatures"&gt;Google shows the versioned naming and requirement in its WAF tuning guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A ruleset upgrade can add or change signatures. Put the new version in preview, compare its matches with the enforced version and promote it after reviewing the result.&lt;/p&gt;
&lt;p&gt;Google also publishes canary rulesets. Its recommended arrangement runs the canary at a higher priority in preview and the stable ruleset at a lower priority in enforcement. The canary records the requests it would deny while the stable rule protects the service. &lt;a href="https://docs.cloud.google.com/armor/docs/best-practices#use_stable_or_canary_rules"&gt;Google describes this stable-and-canary arrangement in its best practices&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Choose the sensitivity level first&lt;/h2&gt;
&lt;p&gt;Sensitivity levels run from 0 to 4. Choosing level 2 enables signatures at levels 1 and 2. Higher levels inspect for more patterns and tend to produce more false positives. Level 0 enables no signatures unless you explicitly opt them in. &lt;a href="https://docs.cloud.google.com/armor/docs/rule-tuning#choose_a_sensitivity_level"&gt;Google maps Cloud Armor sensitivity to CRS paranoia levels in the tuning documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Choose a level that fits the application and the time available to investigate. A public marketing site with conventional inputs may run cleanly at a higher level. An API accepting templates or query fragments may need more work.&lt;/p&gt;
&lt;p&gt;Keep the chosen sensitivity and exclude the named field from the affected signature.&lt;/p&gt;
&lt;h2&gt;Opt out one noisy signature&lt;/h2&gt;
&lt;p&gt;If a signature is irrelevant wherever the policy rule applies, list its versioned ID in &lt;code&gt;opt_out_rule_ids&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;evaluatePreconfiguredWaf(
  &amp;#39;sqli-v422-stable&amp;#39;,
  {
    &amp;#39;sensitivity&amp;#39;: 2,
    &amp;#39;opt_out_rule_ids&amp;#39;: [
      &amp;#39;owasp-crs-v042200-id942350-sqli&amp;#39;
    ]
  }
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The WAF rule runs every SQL injection signature enabled at sensitivity 1 and 2 except the named signature.&lt;/p&gt;
&lt;p&gt;Use an opt-out after reviewing where the policy rule applies. A rule matching every request to a shared backend gives the exception the same reach.&lt;/p&gt;
&lt;h2&gt;Enable selected signatures at sensitivity 0&lt;/h2&gt;
&lt;p&gt;Set sensitivity to 0 when the rule needs only a few named signatures:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;evaluatePreconfiguredWaf(
  &amp;#39;xss-v422-stable&amp;#39;,
  {
    &amp;#39;sensitivity&amp;#39;: 0,
    &amp;#39;opt_in_rule_ids&amp;#39;: [
      &amp;#39;owasp-crs-v042200-id941150-xss&amp;#39;
    ]
  }
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cloud Armor permits &lt;code&gt;opt_in_rule_ids&lt;/code&gt; only at sensitivity 0. Google recommends this approach when you want fewer signatures than you would otherwise opt out. &lt;a href="https://docs.cloud.google.com/armor/docs/configure-waf#configure_waf_rules"&gt;The setup guide documents the constraint and provides current CLI examples&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;An opt-in rule enables only the named signatures for the traffic matched by that policy rule. Record why each signature was selected and when to review the set.&lt;/p&gt;
&lt;h2&gt;Exclude the field that carries legitimate content&lt;/h2&gt;
&lt;p&gt;A request-field exclusion keeps a signature active and removes a selected input from its inspection.&lt;/p&gt;
&lt;p&gt;Cloud Armor can exclude named header values, cookie values, query-parameter values and URIs from an entire preconfigured ruleset or selected signatures. Header, cookie and query-parameter names remain inspected when their values are excluded. URI exclusions remove the matching URI from inspection for the selected target. &lt;a href="https://docs.cloud.google.com/armor/docs/rule-tuning#exclude_request_fields_from_inspection"&gt;Google documents each field’s behaviour and available match operators&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Suppose the cookie &lt;code&gt;analytics_id&lt;/code&gt; repeatedly triggers signature &lt;code&gt;942100&lt;/code&gt;. Add an exclusion to the existing SQLi policy rule:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gcloud&lt;span class="w"&gt; &lt;/span&gt;compute&lt;span class="w"&gt; &lt;/span&gt;security-policies&lt;span class="w"&gt; &lt;/span&gt;rules&lt;span class="w"&gt; &lt;/span&gt;add-preconfig-waf-exclusion&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--security-policy&lt;span class="w"&gt; &lt;/span&gt;app-policy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--target-rule-set&lt;span class="w"&gt; &lt;/span&gt;sqli-v422-stable&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--target-rule-ids&lt;span class="w"&gt; &lt;/span&gt;owasp-crs-v042200-id942100-sqli&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--request-cookie-to-exclude&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;op=EQUALS,val=analytics_id&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The signature continues to inspect the cookie name and every other request field, excluding only the value of &lt;code&gt;analytics_id&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Request query parameters need one extra check. Cloud Armor rebuilds the URI and request line after removing an excluded query parameter. A signature that inspects the entire &lt;code&gt;REQUEST_BODY&lt;/code&gt; continues to inspect it, so the same parameter can trigger a body-wide signature. &lt;a href="https://docs.cloud.google.com/armor/docs/rule-tuning#request_query_parameters"&gt;Google calls out this behaviour in the query-parameter exclusion documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A policy rule with request-field exclusions cannot use the &lt;code&gt;allow&lt;/code&gt; action. Use the WAF’s deny action and let unmatched requests continue through the policy. &lt;a href="https://docs.cloud.google.com/armor/docs/waf-rules#limitations"&gt;This restriction appears in the current preconfigured WAF limitations&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Apply the tuned WAF rule to one route&lt;/h2&gt;
&lt;p&gt;Cloud Armor uses Common Expression Language expressions for advanced matches. Combine the route condition and WAF evaluation in one rule when an exception belongs to one application area:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;request.path.startsWith(&amp;#39;/api/messages&amp;#39;) &amp;amp;&amp;amp;
evaluatePreconfiguredWaf(&amp;#39;sqli-v422-stable&amp;#39;, {&amp;#39;sensitivity&amp;#39;: 1})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Create a separate SQLi rule for the rest of the application and exclude &lt;code&gt;/api/messages&lt;/code&gt; from its expression. The route is then evaluated once under the tuned rule.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;!request.path.startsWith(&amp;#39;/api/messages&amp;#39;) &amp;amp;&amp;amp;
evaluatePreconfiguredWaf(&amp;#39;sqli-v422-stable&amp;#39;, {&amp;#39;sensitivity&amp;#39;: 1})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Priority decides which matching rule takes effect. A smaller number has higher priority. An earlier &lt;code&gt;allow&lt;/code&gt; rule ends evaluation for the matching request, so later WAF rules never see it. &lt;a href="https://docs.cloud.google.com/armor/docs/best-practices#test_your_logic"&gt;Google explains the evaluation order and demonstrates the effect of an early allow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Replay three requests when testing route logic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;a legitimate request containing the known false-positive value;&lt;/li&gt;
&lt;li&gt;an attack string in the excluded field;&lt;/li&gt;
&lt;li&gt;an attack string in another field.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The legitimate request should pass. The attack string in the excluded field shows the exposure created by the exception. The attack string in another field should still trigger the WAF rule.&lt;/p&gt;
&lt;h2&gt;Set the request-body inspection limit&lt;/h2&gt;
&lt;p&gt;Cloud Armor inspects a configurable prefix of the request body: 8, 16, 32, 48 or 64 kB. The limit applies across the preconfigured WAF rules in the policy, and 64 kB is the maximum. Content after the limit can reach the backend without WAF body-signature inspection. &lt;a href="https://docs.cloud.google.com/armor/docs/configure-waf#update_inspection_limit_for_preconfigured_waf_rules"&gt;Google documents the available sizes and provides the policy update command&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Choose the size from the requests the application accepts. Check uploads, GraphQL, JSON APIs and form submissions separately. Google supports parsed URL-encoded and JSON bodies; other content and encodings, including XML, gzip, UTF-16 and multipart form data, receive rules over raw data. &lt;a href="https://docs.cloud.google.com/armor/docs/security-policy-overview#request_body_inspection_limitation"&gt;The security-policy overview describes the current body parsing behaviour&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Verbose logs include &lt;code&gt;inspectedBodySize&lt;/code&gt;, which helps confirm the active limit. Decide how the application handles larger bodies. Google suggests a separate content-length rule when the application can safely reject requests beyond the inspection limit, though &lt;code&gt;Content-Length&lt;/code&gt; deserves the same validation you would apply to any client-supplied header.&lt;/p&gt;
&lt;h2&gt;Identify who manages the policy&lt;/h2&gt;
&lt;p&gt;Service-level backend security policies attach to backend services. Send their false positives to the team that manages the backend policy.&lt;/p&gt;
&lt;p&gt;Hierarchical policies attach at the organization, folder or project level and flow down to projects. Send inherited false positives to the team that manages the hierarchical policy.&lt;/p&gt;
&lt;p&gt;Creating a hierarchical security policy requires Cloud Armor Enterprise Paygo and can enrol the project automatically. &lt;a href="https://docs.cloud.google.com/armor/docs/hierarchical-policies-overview"&gt;Google documents the commercial requirement and ownership model in the hierarchical policy guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Include the matched policy name and priority in each false-positive report. Give the policy team the field, route and application behaviour needed to review the exception.&lt;/p&gt;
&lt;p&gt;Hierarchical and service-level policies support different feature sets. Check the current feature table before moving a tuned rule between them. &lt;a href="https://docs.cloud.google.com/armor/docs/security-policy-overview#types_of_security_policies"&gt;Google maintains the comparison in its security-policy overview&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Disable preview after reviewing the matches&lt;/h2&gt;
&lt;p&gt;Run normal traffic, scheduled jobs and important application flows through the preview rule. Classify each recurring signature and field, then disable preview:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gcloud&lt;span class="w"&gt; &lt;/span&gt;compute&lt;span class="w"&gt; &lt;/span&gt;security-policies&lt;span class="w"&gt; &lt;/span&gt;rules&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--security-policy&lt;span class="w"&gt; &lt;/span&gt;app-policy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--no-preview
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Cloud Armor rule changes usually take several minutes to propagate. Watch the enforced policy outcome after the change and replay the requests used during tuning. &lt;a href="https://docs.cloud.google.com/armor/docs/waf-rules#limitations"&gt;Google lists propagation time among the preconfigured WAF limitations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Keep the previous expression and exclusions in version control. Record the policy, priority, ruleset version, sensitivity, signature IDs, excluded fields and test requests.&lt;/p&gt;</content><category term="Application Security"></category><category term="WAF"></category><category term="Google Cloud Armor"></category><category term="Google Cloud"></category><category term="OWASP CRS"></category><category term="Application Security"></category><category term="False Positives"></category><category term="WAF Tuning"></category></entry><entry><title>How to Tune Cloudflare WAF When Rule 949110 Blocks a Request</title><link href="https://www.peakhour.io/blog/how-to-tune-cloudflare-waf/" rel="alternate"></link><published>2026-07-29T09:45:00+10:00</published><updated>2026-07-29T09:45:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-07-29:/blog/how-to-tune-cloudflare-waf/</id><summary type="html">&lt;p&gt;Trace a Cloudflare OWASP anomaly-score block to the rules that raised the score, then choose the right override or exception.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Rule &lt;code&gt;949110&lt;/code&gt; blocked the request.&lt;/p&gt;
&lt;p&gt;In Cloudflare's OWASP managed ruleset, &lt;code&gt;949110: Inbound Anomaly Score Exceeded&lt;/code&gt; checks the total score produced by earlier detections. The rules that raised the score explain the block.&lt;/p&gt;
&lt;p&gt;Cloudflare puts that answer in Additional logs.&lt;/p&gt;
&lt;h2&gt;Start with the blocked event&lt;/h2&gt;
&lt;p&gt;Open &lt;strong&gt;Security &amp;gt; Events&lt;/strong&gt; in the Cloudflare dashboard and filter to the hostname, path, action and time of the request.&lt;/p&gt;
&lt;p&gt;Security Events shows traffic that Cloudflare acted on or flagged. Its top-level views group events by fields such as action, host, country and service. The sampled log beneath them contains a subset of individual security events, so a busy time range may hide the request you need. Narrowing the time range reduces sampling. &lt;a href="https://developers.cloudflare.com/waf/analytics/security-events/"&gt;Cloudflare documents the Security Events views and sampling behaviour&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Capture the details that identify the request:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ray ID;&lt;/li&gt;
&lt;li&gt;timestamp;&lt;/li&gt;
&lt;li&gt;hostname and path;&lt;/li&gt;
&lt;li&gt;method;&lt;/li&gt;
&lt;li&gt;WAF service and action;&lt;/li&gt;
&lt;li&gt;displayed rule ID and description;&lt;/li&gt;
&lt;li&gt;the application result reported by the user.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One request can produce more than one security event. Keep the Ray ID and timestamp together while you follow the detections.&lt;/p&gt;
&lt;p&gt;If the displayed description is &lt;code&gt;949110: Inbound Anomaly Score Exceeded&lt;/code&gt;, open &lt;strong&gt;Additional logs&lt;/strong&gt;. Cloudflare places the OWASP rules triggered by that request there. &lt;a href="https://developers.cloudflare.com/waf/analytics/security-events/"&gt;The Security Events documentation describes those contributing OWASP records&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Additional logs deserve a manual check in the dashboard. Cloudflare says triggered OWASP rules shown there are absent from exported Security Events JSON. A log export containing only the final event can therefore leave out the detections that built the anomaly score. &lt;a href="https://developers.cloudflare.com/waf/analytics/security-events/"&gt;The Security Events documentation records that export limitation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Find the rule that raised the score&lt;/h2&gt;
&lt;p&gt;Additional logs may show one contributing rule or several. Record each rule's description and Cloudflare rule ID.&lt;/p&gt;
&lt;p&gt;The description may begin with a familiar CRS-style number. Cloudflare's API and exception controls use Cloudflare's opaque rule ID. Use the identifier displayed by Cloudflare when you configure the rule.&lt;/p&gt;
&lt;p&gt;Group repeated matches by rule, field and route, then review each group with the application owner. A single support message containing SQL syntax may be legitimate. Different paths or different rules belong in separate investigations. The &lt;a href="/blog/how-to-tune-your-waf/"&gt;general WAF tuning guide&lt;/a&gt; explains how to classify each group and record an exception.&lt;/p&gt;
&lt;p&gt;Ask the developer who owns the route:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what input the endpoint accepts;&lt;/li&gt;
&lt;li&gt;whether the request was parsed successfully;&lt;/li&gt;
&lt;li&gt;where the matching value is stored or rendered;&lt;/li&gt;
&lt;li&gt;which validation runs at the application;&lt;/li&gt;
&lt;li&gt;whether the field can carry executable syntax by design.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Choose an override or an exception&lt;/h2&gt;
&lt;p&gt;An &lt;strong&gt;override&lt;/strong&gt; changes the configuration of a managed rule or ruleset across that deployment. You can change an action, disable selected rules or adjust settings such as the OWASP anomaly score threshold and paranoia level.&lt;/p&gt;
&lt;p&gt;An &lt;strong&gt;exception&lt;/strong&gt; matches a set of requests and skips selected managed rules for those requests. Its expression can use the host, URI path, method and other Cloudflare Rules language fields.&lt;/p&gt;
&lt;p&gt;Use an override when the rule itself needs a different action across the deployment. Use an exception when a known application route needs different treatment.&lt;/p&gt;
&lt;p&gt;Cloudflare allows exceptions to skip:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;every remaining managed rule;&lt;/li&gt;
&lt;li&gt;one or more managed rulesets;&lt;/li&gt;
&lt;li&gt;one or more rules inside managed rulesets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Select the contributing rule. A whole-ruleset exception removes far more inspection from the matching request. &lt;a href="https://developers.cloudflare.com/waf/managed-rules/waf-exceptions/"&gt;Cloudflare documents the three exception types and their order of precedence&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For a false positive confined to message creation, the expression could be:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;http.host eq &amp;quot;api.example.com&amp;quot;
and http.request.uri.path eq &amp;quot;/v1/messages&amp;quot;
and http.request.method eq &amp;quot;POST&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Choose the Cloudflare rule ID found in Additional logs as the rule to skip.&lt;/p&gt;
&lt;p&gt;Cloudflare skips that rule for the complete matching request. Another field on &lt;code&gt;POST /v1/messages&lt;/code&gt; can trigger the same rule and receive the same exception. Record that consequence.&lt;/p&gt;
&lt;h2&gt;Place the exception before the managed ruleset&lt;/h2&gt;
&lt;p&gt;An exception works only on managed-ruleset execution rules listed after it in the same phase. Put the skip rule before the rule that executes the OWASP managed ruleset. &lt;a href="https://developers.cloudflare.com/waf/managed-rules/waf-exceptions/define-api/"&gt;Cloudflare's API documentation states that skip rules affect only later execute rules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Cloudflare can run managed rulesets at account level on Enterprise plans and at zone level. Account-level rulesets execute before zone-level rulesets. An account-level exception affects account-level rules, while a zone-level exception affects zone-level rules. A zone exception cannot prevent an account-level rule from matching first. &lt;a href="https://developers.cloudflare.com/waf/managed-rules/waf-exceptions/"&gt;Cloudflare documents this scope and evaluation order&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Find the execute rule that produced the event. Add the exception at that level and before that execution. If the OWASP ruleset runs at both levels, inspect both deployments and decide which one needs the exception.&lt;/p&gt;
&lt;p&gt;Exceptions take priority over overrides. &lt;a href="https://developers.cloudflare.com/waf/account/managed-rulesets/"&gt;Cloudflare records that precedence in its managed-ruleset documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Test the exception through Cloudflare&lt;/h2&gt;
&lt;p&gt;Send the original legitimate request through the hostname proxied by Cloudflare. An origin-only replay bypasses the configuration you are testing.&lt;/p&gt;
&lt;p&gt;Use a small test set:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the legitimate request on the exempt route;&lt;/li&gt;
&lt;li&gt;the same input on another route;&lt;/li&gt;
&lt;li&gt;a safe request that triggers the contributing rule on the exempt route;&lt;/li&gt;
&lt;li&gt;the same test outside the exception;&lt;/li&gt;
&lt;li&gt;normal traffic that should pass unchanged.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Check Security Events after each request. Confirm the Ray ID, action, contributing rules and final result.&lt;/p&gt;
&lt;p&gt;The safe trigger on the exempt route shows what the exception allows. The same trigger outside the exception should still raise the contributing rule and reach &lt;code&gt;949110&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Cloudflare evaluates security products and rules in a defined sequence, and a terminating action stops later evaluation. An earlier custom-rule Block or Managed Challenge can determine the result before Managed Rules run. &lt;a href="https://developers.cloudflare.com/waf/concepts/"&gt;Cloudflare's WAF concepts guide describes this rule execution behaviour&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Set the anomaly threshold from real traffic&lt;/h2&gt;
&lt;p&gt;The OWASP anomaly threshold is the cumulative request score at which &lt;code&gt;949110&lt;/code&gt; applies the configured action. Raising it moves the block point for every request covered by that deployment.&lt;/p&gt;
&lt;p&gt;Use Security Events to see which rules contribute to normal requests before changing the threshold. A repeated false positive from one rule and one endpoint points towards an exception. A broad pattern of low-confidence detections may justify a threshold or paranoia-level review.&lt;/p&gt;
&lt;p&gt;Cloudflare supports overrides for the full ruleset, rule tags and individual rules. Tag and ruleset overrides can also affect future rules that Cloudflare adds under those selections. Individual rule overrides target the rule IDs you chose. &lt;a href="https://developers.cloudflare.com/waf/account/managed-rulesets/"&gt;The managed-ruleset documentation explains these override levels&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Test a threshold change with the same care as a rule exception. It changes when &lt;code&gt;949110&lt;/code&gt; acts across all covered traffic.&lt;/p&gt;
&lt;h2&gt;Record the exception and its rule IDs&lt;/h2&gt;
&lt;p&gt;Document:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the Ray IDs and event times reviewed;&lt;/li&gt;
&lt;li&gt;the contributing Cloudflare rule IDs and descriptions;&lt;/li&gt;
&lt;li&gt;the anomaly threshold and paranoia level;&lt;/li&gt;
&lt;li&gt;the exception expression;&lt;/li&gt;
&lt;li&gt;the account or zone deployment it belongs to;&lt;/li&gt;
&lt;li&gt;its position before the managed-ruleset execute rule;&lt;/li&gt;
&lt;li&gt;what the selected rule stops inspecting;&lt;/li&gt;
&lt;li&gt;the developer who confirmed the application behaviour;&lt;/li&gt;
&lt;li&gt;the requests used for verification;&lt;/li&gt;
&lt;li&gt;the review date.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Recheck the exception after application changes and Cloudflare managed-rule updates. A new route can need different conditions. A revised rule can remove the original false positive.&lt;/p&gt;
&lt;p&gt;Rule &lt;code&gt;949110&lt;/code&gt; applies the configured action when the anomaly score crosses the threshold. Tune the rules that supplied the false-positive score.&lt;/p&gt;</content><category term="Application Security"></category><category term="Cloudflare WAF"></category><category term="WAF"></category><category term="OWASP CRS"></category><category term="Application Security"></category><category term="False Positives"></category><category term="WAF Tuning"></category></entry><entry><title>How to Tune Azure Application Gateway WAF</title><link href="https://www.peakhour.io/blog/how-to-tune-azure-application-gateway-waf/" rel="alternate"></link><published>2026-07-29T09:30:00+10:00</published><updated>2026-07-29T09:30:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-07-29:/blog/how-to-tune-azure-application-gateway-waf/</id><summary type="html">&lt;p&gt;Start Azure Application Gateway WAF in Detection mode, find false positives in Log Analytics, narrow each exclusion and move into Prevention mode.&lt;/p&gt;</summary><content type="html">&lt;p&gt;An Azure WAF false positive usually arrives as a 403 and a complaint.&lt;/p&gt;
&lt;p&gt;Treat the 403 as the start of the investigation. The firewall log identifies the rule, the relevant part of the request and the policy that applied.&lt;/p&gt;
&lt;h2&gt;Put new policies into Detection mode&lt;/h2&gt;
&lt;p&gt;Use Detection mode while introducing a WAF policy, upgrading its managed ruleset or placing an existing application behind Application Gateway. The WAF records rule matches while every request continues to the application.&lt;/p&gt;
&lt;p&gt;Microsoft suggests reviewing one to two weeks of traffic before returning the policy to Prevention mode. Plan and supervise that period because attacks also continue to the application. &lt;a href="https://learn.microsoft.com/en-us/troubleshoot/azure/application-gateway/troubleshoot-waf-blocking-legitimate-requests-403"&gt;Microsoft documents this rollout in its Application Gateway WAF troubleshooting guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before changing the mode, save the complete &lt;code&gt;policySettings&lt;/code&gt; block:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;az&lt;span class="w"&gt; &lt;/span&gt;network&lt;span class="w"&gt; &lt;/span&gt;application-gateway&lt;span class="w"&gt; &lt;/span&gt;waf-policy&lt;span class="w"&gt; &lt;/span&gt;show&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$WAF_POLICY_NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--resource-group&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--query&lt;span class="w"&gt; &lt;/span&gt;policySettings&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Azure CLI command &lt;code&gt;az network application-gateway waf-policy policy-setting update&lt;/code&gt; rewrites the policy settings. Microsoft warns that omitting &lt;code&gt;--request-body-check&lt;/code&gt; silently resets the setting to &lt;code&gt;false&lt;/code&gt;. Supply the current value when switching modes, then read the policy back and verify it. &lt;a href="https://learn.microsoft.com/en-us/troubleshoot/azure/application-gateway/troubleshoot-waf-blocking-legitimate-requests-403#resolution-c-temporarily-switch-to-detection-mode"&gt;The warning and corrected commands appear in Microsoft’s current troubleshooting procedure&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For a policy that already inspects request bodies:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;az&lt;span class="w"&gt; &lt;/span&gt;network&lt;span class="w"&gt; &lt;/span&gt;application-gateway&lt;span class="w"&gt; &lt;/span&gt;waf-policy&lt;span class="w"&gt; &lt;/span&gt;policy-setting&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--policy-name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$WAF_POLICY_NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--resource-group&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--state&lt;span class="w"&gt; &lt;/span&gt;Enabled&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--mode&lt;span class="w"&gt; &lt;/span&gt;Detection&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--request-body-check&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Use the value returned by the first command.&lt;/p&gt;
&lt;h2&gt;Send the firewall log to Log Analytics&lt;/h2&gt;
&lt;p&gt;Application Gateway produces access and firewall logs through Azure Monitor. Microsoft recommends the resource-specific Log Analytics tables for new diagnostic settings. WAF events then appear in &lt;code&gt;AGWFirewallLogs&lt;/code&gt;; the older Azure Diagnostics mode writes them to &lt;code&gt;AzureDiagnostics&lt;/code&gt;. &lt;a href="https://learn.microsoft.com/en-us/azure/application-gateway/application-gateway-diagnostics"&gt;Microsoft explains both collection modes and recommends the resource-specific tables&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check which table receives your data before borrowing a query from another subscription.&lt;/p&gt;
&lt;p&gt;For a resource-specific table, start here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;AGWFirewallLogs&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TimeGenerated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Detected&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Matched&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;summarize&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dcount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TransactionId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RequestUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RuleId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PolicyScope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PolicyScopeName&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For a workspace using the older shared table:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;AzureDiagnostics&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ResourceType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;APPLICATIONGATEWAYS&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ApplicationGatewayFirewallLog&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;action_s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Detected&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Matched&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;summarize&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dcount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transactionId_s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Paths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestUri_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ruleId_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ruleGroup_s&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The current &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/agwfirewalllogs"&gt;&lt;code&gt;AGWFirewallLogs&lt;/code&gt; schema&lt;/a&gt; records the policy ID, policy scope and transaction ID. The transaction ID joins several rule events produced by one request.&lt;/p&gt;
&lt;p&gt;Open the highest-volume group first. Inspect several transactions and compare each rule message with the input the application expects.&lt;/p&gt;
&lt;h2&gt;Find the field that caused the match&lt;/h2&gt;
&lt;p&gt;Suppose rule &lt;code&gt;942100&lt;/code&gt; appears on &lt;code&gt;POST /api/messages&lt;/code&gt;. The endpoint accepts customer support text, and messages containing database errors trigger the SQL injection rule.&lt;/p&gt;
&lt;p&gt;The &lt;a href="/blog/how-to-tune-your-waf/"&gt;general WAF tuning guide&lt;/a&gt; explains how to classify the match. The Azure log supplies the rule, field and policy.&lt;/p&gt;
&lt;p&gt;Confirm four things before adding an exception:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the same field causes the repeated matches;&lt;/li&gt;
&lt;li&gt;the application expects free text in that field;&lt;/li&gt;
&lt;li&gt;the requests complete normally in Detection mode;&lt;/li&gt;
&lt;li&gt;other fields on the endpoint still need SQL injection inspection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The development team supplies the application context behind the WAF event. Ask how the field is parsed, validated, stored and rendered. Keep the request examples needed for the investigation, then remove any customer content from the exception record.&lt;/p&gt;
&lt;h2&gt;Exclude one field from one rule&lt;/h2&gt;
&lt;p&gt;Azure exclusion lists remove selected request attributes from managed-rule inspection while the WAF evaluates the rest of the request. Application Gateway v2 can exclude request headers, cookies, form fields, JSON entities and query-string arguments. Exclusions can apply globally or to selected managed rules. &lt;a href="https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/application-gateway-waf-configuration"&gt;Microsoft’s exclusion-list guide describes the supported fields and per-rule configuration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the message example, select &lt;code&gt;RequestArgValues&lt;/code&gt; with the selector &lt;code&gt;message&lt;/code&gt;, then limit the exclusion to rule &lt;code&gt;942100&lt;/code&gt; in the deployed CRS or DRS version. Replay the original request and a second request that places SQL injection text in another field. The support message should pass. The second field should still trigger the rule.&lt;/p&gt;
&lt;p&gt;Azure now skips &lt;code&gt;message&lt;/code&gt; for rule &lt;code&gt;942100&lt;/code&gt; and inspects the other request fields.&lt;/p&gt;
&lt;p&gt;Per-rule exclusions require the next-generation WAF engine with CRS 3.2, DRS 2.1 or a later ruleset. Microsoft currently recommends DRS 2.2. A global exclusion applies across the web application covered by the policy, so reserve it for a field that has the same meaning everywhere. &lt;a href="https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/application-gateway-waf-configuration"&gt;Microsoft lists these version requirements and exclusion behaviour in the Application Gateway WAF documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Give one site or path its own policy&lt;/h2&gt;
&lt;p&gt;A policy-level exclusion may cover more applications than the exception requires.&lt;/p&gt;
&lt;p&gt;Application Gateway can associate WAF policies globally, with a listener, or with a path-based rule. The most specific policy takes precedence. A listener policy can therefore tune one hostname, while a path policy can tune one URL area. &lt;a href="https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/per-site-policies"&gt;Microsoft documents global, per-site and per-URI policy associations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Use that layout when &lt;code&gt;message&lt;/code&gt; is free text on &lt;code&gt;/api/messages&lt;/code&gt; and a structured identifier elsewhere. Put the exclusion in the policy attached to the relevant path. Other listeners and paths keep their existing policy.&lt;/p&gt;
&lt;p&gt;Record the association alongside the exception. A future routing change can move traffic under a different policy without changing the exception itself.&lt;/p&gt;
&lt;h2&gt;Use request exceptions to skip a rule&lt;/h2&gt;
&lt;p&gt;Azure Application Gateway WAF also has request exceptions. They match a request attribute such as a URI, header or client address, then bypass a selected rule, rule group or ruleset for the matching request.&lt;/p&gt;
&lt;p&gt;Use a request exception when one rule cannot inspect any part of a known request safely. The selected rule stops evaluating the matching request, so describe the inspection removed and limit the exception to that rule where possible.&lt;/p&gt;
&lt;p&gt;As of July 2026, Application Gateway WAF request exceptions are in preview. They require Application Gateway v2 and the next-generation engine with CRS 3.2, DRS 2.1 or later. Each WAF policy supports up to 60 exceptions, subject to gateway and per-exception limits. Confirm availability, deployment tooling and support expectations before using them in production. &lt;a href="https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/application-gateway-exceptions"&gt;Microsoft publishes the current status, supported scopes and limits on the request-exceptions page&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;An Allow rule bypasses the managed WAF&lt;/h2&gt;
&lt;p&gt;When that rule matches, Azure skips the managed DRS, CRS and Bot Protection rulesets for the request. HTTP DDoS rules still run. &lt;a href="https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/application-gateway-exceptions#custom-rule-with-an-allow-action"&gt;Microsoft documents the exact consequence of a custom Allow action&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Reserve an &lt;code&gt;Allow&lt;/code&gt; rule for a tightly defined, trusted integration. Every matching request bypasses DRS, CRS and Bot Protection, including requests sent after an allowed client is compromised.&lt;/p&gt;
&lt;p&gt;Write the trust assumption into the rule description and the exception register. Include the owner, source, listener or path, expiry or review date, and the protections that remain.&lt;/p&gt;
&lt;h2&gt;Return to Prevention mode&lt;/h2&gt;
&lt;p&gt;Run the Detection policy until ordinary traffic and important manual flows produce no unexplained matches. Include login, password reset, checkout, file upload, API writes, administration and scheduled integrations.&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;save the current policy settings;&lt;/li&gt;
&lt;li&gt;change the mode to Prevention while preserving &lt;code&gt;requestBodyCheck&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;read the policy back;&lt;/li&gt;
&lt;li&gt;replay the requests used during tuning;&lt;/li&gt;
&lt;li&gt;monitor &lt;code&gt;Blocked&lt;/code&gt; events and 403 responses;&lt;/li&gt;
&lt;li&gt;keep a quick rollback procedure ready.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Enable Prevention in stages when separate listeners or applications have different risk. The first application may be clean while a monthly import on the second has never appeared in the logs.&lt;/p&gt;
&lt;p&gt;Recheck the same Log Analytics groups after application releases and managed-ruleset upgrades. A new field, content type or route changes what the WAF sees. Record the request, excluded field and owning policy for every exception.&lt;/p&gt;</content><category term="Application Security"></category><category term="WAF"></category><category term="Azure"></category><category term="Application Gateway"></category><category term="Application Security"></category><category term="False Positives"></category><category term="WAF Tuning"></category></entry><entry><title>How to Tune AWS WAF with Count, Labels and Rule Priority</title><link href="https://www.peakhour.io/blog/how-to-tune-aws-waf/" rel="alternate"></link><published>2026-07-29T09:15:00+10:00</published><updated>2026-07-29T09:15:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-07-29:/blog/how-to-tune-aws-waf/</id><summary type="html">&lt;p&gt;Use Count overrides, managed-rule labels and rule priority to fix AWS WAF false positives while keeping the rest of the managed rule group in force.&lt;/p&gt;</summary><content type="html">&lt;p&gt;An AWS managed rule blocks a real customer. You find the request, change the entire rule group to Count and promise to revisit it next week.&lt;/p&gt;
&lt;p&gt;Six months later, it is still counting.&lt;/p&gt;
&lt;p&gt;AWS WAF lets you change one rule inside a managed rule group to Count, keep the label it adds to matching requests and add a blocking rule immediately after the group. The other managed rules keep their configured actions throughout the investigation.&lt;/p&gt;
&lt;p&gt;The &lt;a href="/blog/how-to-tune-your-waf/"&gt;general WAF tuning guide&lt;/a&gt; explains how to classify the request and document the exception. The AWS configuration begins with the managed rule that matched.&lt;/p&gt;
&lt;h2&gt;Put the rule in Count&lt;/h2&gt;
&lt;p&gt;Start with the named rule that caused the false positive.&lt;/p&gt;
&lt;p&gt;AWS WAF lets you apply a rule action override to an individual rule inside a managed rule group. Set that rule to &lt;code&gt;Count&lt;/code&gt;. The override changes how that rule handles matches in your web ACL; it leaves the managed rule group itself unchanged. AWS describes Count as a non-terminating action, so evaluation continues through the rules that follow. &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-rule-group-override-options.html"&gt;AWS documents individual rule action overrides and the behaviour of Count&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Use the individual override for this job. The older rule-group return override changes the result returned by the group after its internal evaluation. AWS recommends rule action overrides when you want the rules inside the group to count their matches.&lt;/p&gt;
&lt;p&gt;Keep the change small:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Record the managed rule group, version and rule name.&lt;/li&gt;
&lt;li&gt;Change that rule to Count.&lt;/li&gt;
&lt;li&gt;Leave the remaining rules at their current actions.&lt;/li&gt;
&lt;li&gt;Reproduce the legitimate request.&lt;/li&gt;
&lt;li&gt;Check the Count match and the label added by the managed rule.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;AWS Managed Rules add labels to matching requests, and later rules in the same web ACL can inspect those labels. &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/waf-labels.html"&gt;AWS lists labels as the supported way to create exceptions for managed rule groups&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Read the request that matched&lt;/h2&gt;
&lt;p&gt;The sampled-requests view supports a quick investigation. Full WAF logs provide the history needed to compare matches across users, hosts or endpoints.&lt;/p&gt;
&lt;p&gt;With the rule set to Count, look for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the managed rule name;&lt;/li&gt;
&lt;li&gt;its fully qualified label;&lt;/li&gt;
&lt;li&gt;host, URI and method;&lt;/li&gt;
&lt;li&gt;the matching request component, when AWS supplies it;&lt;/li&gt;
&lt;li&gt;the final action taken on the request;&lt;/li&gt;
&lt;li&gt;the number of clients and routes producing the same match.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AWS records Count matches from managed rule groups under &lt;code&gt;ruleGroupList[].nonTerminatingMatchingRules&lt;/code&gt;. Labels added during evaluation appear in the top-level &lt;code&gt;labels&lt;/code&gt; field. AWS populates &lt;code&gt;ruleMatchDetails&lt;/code&gt; for SQL injection and cross-site scripting match statements. Other managed detections may provide less detail. &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/logging-fields.html"&gt;The AWS WAF log-field reference describes these fields and their limits&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Treat request data as customer data. Query strings, headers and bodies may contain credentials or personal information. Logging redaction applies to the configured log destination. Web ACL data protection masks matching data before it reaches either logs or sampled requests. Disable sampled requests when they could expose a field that data protection does not cover. &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/waf-data-protection-and-logging.html"&gt;AWS explains the distinction in its logging and data-protection guidance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Group repeated matches by rule name, label, host, path and method. Ask the developer who owns the route what the field means and how the application uses it. Investigate a JSON field containing source code separately from a malformed body that the application accepts.&lt;/p&gt;
&lt;h2&gt;Block the managed-rule label in the next rule&lt;/h2&gt;
&lt;p&gt;Suppose a managed SQL injection rule repeatedly matches legitimate messages sent to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;POST api.example.com/v1/messages
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The managed rule now runs in Count and adds its documented label. Add a custom rule after the managed rule group. That custom rule should match:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;the managed-rule label
AND NOT (
    host = api.example.com
    AND path = /v1/messages
    AND method = POST
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Give the custom rule a Block action.&lt;/p&gt;
&lt;p&gt;Requests that match the managed rule continue to be blocked everywhere except the route you reviewed. On that route, this particular managed-rule match can pass to the rules that follow and eventually to the web ACL's default action.&lt;/p&gt;
&lt;p&gt;The order creates the behaviour. AWS WAF evaluates rules from the lowest numeric priority upwards. A later rule can see a label only after the managed rule has added it. Put the managed rule group first and the label-matching rule directly after it. &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-processing-order.html"&gt;AWS documents both priority order&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-label-overview.html"&gt;when labels become available to later rules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check the rules that come before the group as well. An earlier &lt;code&gt;Allow&lt;/code&gt; or &lt;code&gt;Block&lt;/code&gt; action ends evaluation before the managed rule runs. AWS documents &lt;code&gt;Allow&lt;/code&gt; and &lt;code&gt;Block&lt;/code&gt; as terminating actions, while Count continues to later rules. &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-rule-actions.html"&gt;The rule-action guide explains how actions and priority interact&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The exception covers every field on the exempt route&lt;/h2&gt;
&lt;p&gt;The label tells you that the managed rule matched. AWS applies the later rule action to that match for the whole request.&lt;/p&gt;
&lt;p&gt;On the exempt route, the later Block rule ignores that label regardless of which request field caused the managed-rule match. A different field can trigger the same rule and receive the same exception.&lt;/p&gt;
&lt;p&gt;Keep the condition tied to the smallest stable part of the application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;exact host;&lt;/li&gt;
&lt;li&gt;exact path or a reviewed path family;&lt;/li&gt;
&lt;li&gt;HTTP method;&lt;/li&gt;
&lt;li&gt;another server-controlled distinction, when one exists.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A query parameter, header or cookie supplied by the client can narrow which requests match the exception. Confirm the matched request component from the event investigation.&lt;/p&gt;
&lt;p&gt;AWS also supports a scope-down statement on a managed rule group. The statement selects which requests enter the group for inspection; every other request bypasses the whole group. Reserve that option for traffic the whole rule group should exclude. AWS describes this evaluation behaviour in its &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-scope-down-statements.html"&gt;scope-down statement documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Test the complete rule path&lt;/h2&gt;
&lt;p&gt;Replay this test set:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the original legitimate request on the exempt route;&lt;/li&gt;
&lt;li&gt;the same harmless input on another route;&lt;/li&gt;
&lt;li&gt;a safe security test that triggers the managed rule on the exempt route;&lt;/li&gt;
&lt;li&gt;the same test on another route;&lt;/li&gt;
&lt;li&gt;requests that match earlier terminating rules;&lt;/li&gt;
&lt;li&gt;normal requests that should pass unchanged.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then check the WAF logs. Confirm that the managed rule still counts and labels the request, the later rule blocks the label outside the exception, and the final action is the one you intended.&lt;/p&gt;
&lt;p&gt;Run the configuration against production traffic in Count while you establish its normal match rate. AWS recommends monitoring metrics, sampled requests and logs, then repeating the tuning cycle until the web ACL behaves as intended. AWS also recommends completing the final stage with production traffic. &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-testing-activities.html"&gt;Its testing guidance lays out that process&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Restore enforcement&lt;/h2&gt;
&lt;p&gt;The label rule can remain as the enforcement point when the application genuinely needs a route-specific exception. Document:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the managed rule group and version;&lt;/li&gt;
&lt;li&gt;the original rule action;&lt;/li&gt;
&lt;li&gt;the overridden rule name and label;&lt;/li&gt;
&lt;li&gt;the hosts, paths and methods allowed through;&lt;/li&gt;
&lt;li&gt;the application owner who confirmed the behaviour;&lt;/li&gt;
&lt;li&gt;the requests used to test the change;&lt;/li&gt;
&lt;li&gt;the logs and metrics that will reveal a regression;&lt;/li&gt;
&lt;li&gt;the next review date.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the development team removes the input that caused the false positive, remove the custom exception and return the managed rule to its original action. Test that change in Count first, then restore enforcement.&lt;/p&gt;
&lt;p&gt;Review the exception after managed rule group updates and application releases. AWS managed groups can use versioned releases, and the label, traffic shape or rule behaviour you investigated may change with the deployed version. &lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html"&gt;AWS publishes the available managed groups, their rules and their labels&lt;/a&gt;.&lt;/p&gt;</content><category term="Application Security"></category><category term="AWS WAF"></category><category term="WAF"></category><category term="Application Security"></category><category term="Managed Rules"></category><category term="False Positives"></category><category term="WAF Tuning"></category></entry><entry><title>How to Tune Coraza on Caddy</title><link href="https://www.peakhour.io/blog/how-to-tune-coraza-waf/" rel="alternate"></link><published>2026-07-29T09:00:00+10:00</published><updated>2026-07-29T09:00:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-07-29:/blog/how-to-tune-coraza-waf/</id><summary type="html">&lt;p&gt;Run Coraza and OWASP CRS on Caddy in DetectionOnly, read the audit log and limit each exclusion to the field and route that need it.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Coraza sits inside another server. The connector presents each request to Coraza, Coraza runs the rules and the connector applies the result.&lt;/p&gt;
&lt;p&gt;Handler order decides whether Coraza sees the request in time. Body handling decides which fields the rules can inspect. The logging phase decides what you can investigate later.&lt;/p&gt;
&lt;p&gt;The example deployment runs Coraza as Caddy middleware with OWASP Core Rule Set. The same CRS tuning principles apply to other Coraza connectors. Their installation, request handling and logging need their own tests.&lt;/p&gt;
&lt;h2&gt;Put Coraza in the request path&lt;/h2&gt;
&lt;p&gt;The official Caddy module is compiled into Caddy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;xcaddy&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--with&lt;span class="w"&gt; &lt;/span&gt;github.com/corazawaf/coraza-caddy/v2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The module documentation requires &lt;code&gt;order coraza_waf first&lt;/code&gt; in the global options block, which places Coraza at the start of Caddy's HTTP handler order.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;coraza_waf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;coraza_waf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;load_owasp_crs&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;directives&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;coraza&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;recommended&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;SecRuleEngine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DetectionOnly&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;SecAuditEngine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RelevantOnly&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;SecAuditLog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;coraza&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;audit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;SecAuditLogFormat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;SecAuditLogType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Serial&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;SecAuditLogParts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ABFHZ&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;crs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;owasp_crs&lt;/span&gt;&lt;span class="o"&gt;/*.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;reverse_proxy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Caddy connector's &lt;a href="https://github.com/corazawaf/coraza-caddy#using-owasp-core-ruleset"&gt;&lt;code&gt;load_owasp_crs&lt;/code&gt; option exposes the embedded Coraza CRS files&lt;/a&gt;. The three &lt;code&gt;Include&lt;/code&gt; lines then load the recommended Coraza configuration, the CRS setup and the rules.&lt;/p&gt;
&lt;p&gt;Coraza also supports &lt;a href="https://www.coraza.io/docs/tutorials/coreruleset/"&gt;CRS files installed on disk&lt;/a&gt;. A file-based installation loads the Coraza configuration first, &lt;code&gt;crs-setup.conf&lt;/code&gt; second and the CRS rule files last. Choose one source for CRS and record its version so you can compare rule changes during an upgrade.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.coraza.io/docs/seclang/directives/#secruleengine"&gt;&lt;code&gt;SecRuleEngine DetectionOnly&lt;/code&gt;&lt;/a&gt; runs the rules and suppresses disruptive actions such as &lt;code&gt;deny&lt;/code&gt;, &lt;code&gt;drop&lt;/code&gt; and &lt;code&gt;redirect&lt;/code&gt;. Legitimate requests can complete while you collect matches. Coraza sets &lt;a href="https://www.coraza.io/docs/seclang/directives/#secrequestbodylimitaction"&gt;&lt;code&gt;SecRequestBodyLimitAction&lt;/code&gt; to &lt;code&gt;ProcessPartial&lt;/code&gt;&lt;/a&gt; in this mode. Watch &lt;a href="https://www.coraza.io/docs/seclang/variables/#inbound_data_error"&gt;&lt;code&gt;INBOUND_DATA_ERROR&lt;/code&gt;&lt;/a&gt; because part of an oversized body may remain uninspected.&lt;/p&gt;
&lt;p&gt;Test the Caddy configuration before reloading it. Then send a safe request that triggers a known CRS rule through the public listener. Confirm that the Coraza event appears and the request reaches the upstream in &lt;code&gt;DetectionOnly&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Configure the Coraza audit log&lt;/h2&gt;
&lt;p&gt;The Caddy access log tells you that a request happened. The Coraza audit log tells you which WAF rule matched.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.coraza.io/docs/seclang/directives/#secauditengine"&gt;&lt;code&gt;SecAuditEngine RelevantOnly&lt;/code&gt;&lt;/a&gt; records transactions that produced a warning or error, along with response statuses selected by &lt;code&gt;SecAuditLogRelevantStatus&lt;/code&gt;. CRS rules normally carry the &lt;code&gt;auditlog&lt;/code&gt; action, so their matches can enter the audit log.&lt;/p&gt;
&lt;p&gt;The example uses &lt;a href="https://www.coraza.io/docs/seclang/directives/#secauditlogformat"&gt;Coraza's JSON audit format&lt;/a&gt; and leaves request-body part &lt;code&gt;C&lt;/code&gt; out of &lt;code&gt;SecAuditLogParts&lt;/code&gt;. Request bodies often contain passwords, personal information and customer text. Add body logging only for a controlled investigation, then remove it.&lt;/p&gt;
&lt;p&gt;The serial audit log in the example is convenient during initial tuning. &lt;a href="https://www.coraza.io/docs/seclang/directives/#secauditlogtype"&gt;Coraza warns that serial logging can slow the server&lt;/a&gt; because transactions share one file.&lt;/p&gt;
&lt;p&gt;Concurrent, HTTPS and syslog outputs suit deployments that need another destination. Send the records to a protected log system with rotation, retention limits and restricted access.&lt;/p&gt;
&lt;p&gt;Start each investigation with these fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;request time and transaction ID;&lt;/li&gt;
&lt;li&gt;hostname, method and URL;&lt;/li&gt;
&lt;li&gt;rule ID and message;&lt;/li&gt;
&lt;li&gt;CRS tags such as &lt;code&gt;attack-sqli&lt;/code&gt; or &lt;code&gt;attack-xss&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;matched variable name;&lt;/li&gt;
&lt;li&gt;matched data;&lt;/li&gt;
&lt;li&gt;request and response status.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The variable name identifies the input to investigate. &lt;code&gt;ARGS:message&lt;/code&gt; identifies an application field. &lt;code&gt;REQUEST_COOKIES:campaign&lt;/code&gt; identifies a cookie.&lt;/p&gt;
&lt;p&gt;Handle matched data as customer data. Keep enough to reproduce the result, then retain the rule ID, field name and explanation in the permanent exception record.&lt;/p&gt;
&lt;h2&gt;Check what Coraza parsed&lt;/h2&gt;
&lt;p&gt;CRS can inspect only the fields Coraza extracted from the request.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.coraza.io/docs/seclang/directives/#secrequestbodyaccess"&gt;&lt;code&gt;SecRequestBodyAccess On&lt;/code&gt;&lt;/a&gt; enables body buffering and processing. The selected processor determines which collections phase 2 rules can inspect. URL-encoded forms, multipart forms, JSON and XML produce different variables.&lt;/p&gt;
&lt;p&gt;For an unexpected body match, record:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the request &lt;code&gt;Content-Type&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REQBODY_PROCESSOR&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REQBODY_ERROR&lt;/code&gt; and &lt;code&gt;REQBODY_ERROR_MSG&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INBOUND_DATA_ERROR&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the matched variable name.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://www.coraza.io/docs/reference/body-processing/"&gt;Coraza's current body-processing documentation&lt;/a&gt; shows processor selection by content type. The &lt;a href="https://www.coraza.io/docs/seclang/actions/#ctl"&gt;&lt;code&gt;ctl:requestBodyProcessor&lt;/code&gt; reference&lt;/a&gt; still describes explicit phase 1 selection for JSON and XML. Check &lt;code&gt;REQBODY_PROCESSOR&lt;/code&gt; on the Coraza version and connector you run, then select the processor explicitly when the expected processor is missing.&lt;/p&gt;
&lt;p&gt;A parsing error deserves its own investigation. Coraza documents the risk plainly: the application may accept a body that Coraza's parser rejected. Compare the application's parser with Coraza using the same bytes. Fix a wrong content type or processor selection before writing a rule exclusion.&lt;/p&gt;
&lt;p&gt;Body limits need the same care. &lt;a href="https://www.coraza.io/docs/seclang/directives/#secrequestbodylimitaction"&gt;&lt;code&gt;SecRequestBodyLimitAction ProcessPartial&lt;/code&gt;&lt;/a&gt; inspects the bytes that fit within the limit and sets &lt;a href="https://www.coraza.io/docs/seclang/variables/#inbound_data_error"&gt;&lt;code&gt;INBOUND_DATA_ERROR&lt;/code&gt;&lt;/a&gt;. Raise a limit when the application legitimately accepts larger bodies and the deployment can buffer them safely. Keep an alert for the limit condition so partial inspection remains visible.&lt;/p&gt;
&lt;h2&gt;Load exclusions in the correct order&lt;/h2&gt;
&lt;p&gt;CRS provides two places for local exclusions.&lt;/p&gt;
&lt;p&gt;Use the &lt;a href="/blog/how-to-tune-your-waf/"&gt;general WAF tuning guide&lt;/a&gt; to classify the match and define the exception. Load that exception according to the CRS rule order below.&lt;/p&gt;
&lt;p&gt;A runtime exclusion runs for matching transactions. Load it before CRS so it can change the transaction before the affected rule executes. CRS includes &lt;code&gt;REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example&lt;/code&gt; for this purpose.&lt;/p&gt;
&lt;p&gt;A configure-time exclusion changes a loaded rule. Load it after CRS so the rule already exists. CRS includes &lt;code&gt;RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example&lt;/code&gt; for these changes.&lt;/p&gt;
&lt;p&gt;Keep both files outside the CRS distribution. An upgrade can then replace CRS without replacing your application decisions.&lt;/p&gt;
&lt;p&gt;The order in a Caddy deployment looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;directives&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;@coraza&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;recommended&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SecRuleEngine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DetectionOnly&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;@crs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;coraza&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;REQUEST&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EXCLUSION&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;RULES&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;BEFORE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;CRS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;@owasp_crs&lt;/span&gt;&lt;span class="cm"&gt;/*.conf&lt;/span&gt;
&lt;span class="cm"&gt;    Include /etc/coraza/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf&lt;/span&gt;
&lt;span class="cm"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With &lt;code&gt;load_owasp_crs&lt;/code&gt; enabled, the &lt;a href="https://github.com/corazawaf/coraza-caddy/blob/82f09c076b75f0096b8c9ebc58e1ce53e2947198/coraza.go#L106-L108"&gt;Caddy connector merges the embedded CRS filesystem with the operating-system filesystem&lt;/a&gt;. The configuration can include embedded &lt;code&gt;@&lt;/code&gt; files and local &lt;code&gt;/etc/coraza&lt;/code&gt; exclusion files.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://coreruleset.org/docs/2-how-crs-works/2-3-false-positives-and-tuning/"&gt;CRS tuning guide explains the before-and-after placement&lt;/a&gt; and the available exclusion actions.&lt;/p&gt;
&lt;h2&gt;Exclude the field that caused the match&lt;/h2&gt;
&lt;p&gt;Suppose &lt;code&gt;POST /api/jobs&lt;/code&gt; accepts a JSON array. Rule 932125 repeatedly matches legitimate text in each &lt;code&gt;jobDescription&lt;/code&gt; field. In this example, the audit log reports variable names such as &lt;code&gt;ARGS:json.0.jobdescription&lt;/code&gt; and &lt;code&gt;ARGS:json.1.jobdescription&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Coraza accepts a regular expression for the collection key in &lt;code&gt;ctl:ruleRemoveTargetById&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf&lt;/span&gt;
&lt;span class="c"&gt;# Local rule ID 10010 must be unique in this deployment.&lt;/span&gt;
&lt;span class="nb"&gt;SecRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;REQUEST_FILENAME&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@streq /api/jobs&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id:10010,phase:1,pass,nolog,t:none,chain&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;SecRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;REQUEST_METHOD&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@streq POST&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;t:none,ctl:ruleRemoveTargetById=932125;ARGS:/^json\.\d+\.jobdescription$/&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This local rule runs only for &lt;code&gt;POST /api/jobs&lt;/code&gt;. Rule 932125 continues to inspect every other field in those requests and every field on other routes.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;/pattern/&lt;/code&gt; form is a Coraza feature. The &lt;a href="https://www.coraza.io/docs/seclang/actions/#ctl"&gt;Coraza action reference&lt;/a&gt; states that collection-key matching is case-insensitive because Coraza lowercases variable names. Use an exact key such as &lt;code&gt;ARGS:message&lt;/code&gt; when the application has one fixed field. A regular expression earns its place when the application produces a documented family of field names.&lt;/p&gt;
&lt;p&gt;A field can trigger several rules in one attack class. Coraza also supports &lt;code&gt;ctl:ruleRemoveTargetByTag&lt;/code&gt;, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;ctl&lt;/span&gt;:ruleRemoveTargetByTag=attack-sqli;ARGS:message
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The exception removes the named field from every rule carrying the literal &lt;code&gt;attack-sqli&lt;/code&gt; tag. Record that scope and confirm that the application protects the field through parameterised queries and suitable validation.&lt;/p&gt;
&lt;p&gt;Configure-time exclusions suit fields with the same meaning everywhere:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf&lt;/span&gt;
&lt;span class="nb"&gt;SecRuleUpdateTargetById&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;942100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;!REQUEST_COOKIES:campaign&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This removes one named cookie from rule 942100 throughout the configuration. A route-specific runtime exclusion is the better starting point for an application field.&lt;/p&gt;
&lt;h2&gt;Check where a chained rule reads the field&lt;/h2&gt;
&lt;p&gt;Some CRS detections are chained rules. The first rule starts the chain and later rules add conditions.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://coreruleset.org/docs/2-how-crs-works/2-3-false-positives-and-tuning/"&gt;CRS tuning documentation&lt;/a&gt; states that target exclusions by ID affect only the first rule in a chain. An exclusion will have no effect when the field is inspected by a later chain member. Removing the complete rule may be the available answer.&lt;/p&gt;
&lt;p&gt;Find the rule in the CRS version you deployed. Check where the target appears in the chain. Then replay the request against that exact version. The rule source and replay result settle the question.&lt;/p&gt;
&lt;h2&gt;Replay the request before and after&lt;/h2&gt;
&lt;p&gt;Save the legitimate request that produced the false positive. Preserve its method, path, query string, content type, important headers and body bytes. Replace credentials and personal data with values that reproduce the same match.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;--show-error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--dump-header&lt;span class="w"&gt; &lt;/span&gt;before.headers&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;before.body&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--request&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--header&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--data-binary&lt;span class="w"&gt; &lt;/span&gt;@false-positive.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;https://api.example.com/api/jobs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Run it once in &lt;code&gt;DetectionOnly&lt;/code&gt; before adding the exclusion. Confirm that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the application returns its normal response;&lt;/li&gt;
&lt;li&gt;the audit log contains the expected rule ID;&lt;/li&gt;
&lt;li&gt;the log names the expected field;&lt;/li&gt;
&lt;li&gt;the request body has no parser or size error.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add the exclusion, validate the Caddy configuration and reload it. Send the same request bytes again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;--show-error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--dump-header&lt;span class="w"&gt; &lt;/span&gt;after.headers&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;after.body&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--request&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--header&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--data-binary&lt;span class="w"&gt; &lt;/span&gt;@false-positive.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;https://api.example.com/api/jobs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The application response should stay the same. The false-positive match should disappear for the excluded field.&lt;/p&gt;
&lt;p&gt;Now send a control request that places a known CRS test value in another field on the same route. Rule 932125 should still inspect that field. Send the original test value to another route as well. The rule should still inspect it there.&lt;/p&gt;
&lt;p&gt;Expect these results:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the legitimate request works;&lt;/li&gt;
&lt;li&gt;the rule still reads neighbouring fields;&lt;/li&gt;
&lt;li&gt;the rule still runs on other routes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Keep the request fixtures in a protected test repository. Run them after Coraza, CRS, Caddy or application upgrades.&lt;/p&gt;
&lt;h2&gt;Switch Coraza from DetectionOnly to On&lt;/h2&gt;
&lt;p&gt;Switch &lt;code&gt;SecRuleEngine&lt;/code&gt; to &lt;code&gt;On&lt;/code&gt; in a staging environment after the common legitimate flows run without unexplained matches. Repeat the false-positive replay, the neighbouring-field control and the application's critical user journeys.&lt;/p&gt;
&lt;p&gt;Enable blocking for a limited production audience or hostname when the deployment allows it. Watch WAF responses, upstream errors and business transactions together. A clean audit log has little value when checkout or login has stopped working.&lt;/p&gt;
&lt;p&gt;Document every exception beside the configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;who approved it;&lt;/li&gt;
&lt;li&gt;the affected hostname, method and route;&lt;/li&gt;
&lt;li&gt;the rule ID or tag;&lt;/li&gt;
&lt;li&gt;the excluded field;&lt;/li&gt;
&lt;li&gt;the request that reproduced the match;&lt;/li&gt;
&lt;li&gt;the controls that still inspect or validate the field;&lt;/li&gt;
&lt;li&gt;the date and CRS version tested.&lt;/li&gt;
&lt;/ul&gt;</content><category term="Application Security"></category><category term="Coraza"></category><category term="Caddy"></category><category term="OWASP CRS"></category><category term="WAF"></category><category term="Application Security"></category><category term="False Positives"></category><category term="WAF Tuning"></category></entry><entry><title>How to Tune Your WAF Without Turning It Off</title><link href="https://www.peakhour.io/blog/how-to-tune-your-waf/" rel="alternate"></link><published>2026-07-28T10:30:00+10:00</published><updated>2026-07-28T10:30:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-07-28:/blog/how-to-tune-your-waf/</id><summary type="html">&lt;p&gt;A practical process for running a CRS-based or managed WAF in detection mode, finding repeatable false positives and writing narrow exceptions that survive real application traffic.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The OWASP Core Rule Set has never seen your application.&lt;/p&gt;
&lt;p&gt;Generic patterns let it recognise common signs of SQL injection, cross-site scripting and protocol attacks. Tuning teaches it that your application stores a search expression in JSON, accepts links in a message field or receives an analytics cookie full of punctuation.&lt;/p&gt;
&lt;p&gt;So a new deployment will be noisy.&lt;/p&gt;
&lt;p&gt;Faced with that noise, teams often leave the WAF in log-only mode forever or disable each rule that causes a complaint. Teams need a repeatable route from noisy alerts to safe blocking.&lt;/p&gt;
&lt;p&gt;The work is repetitive and fairly unglamorous: observe real traffic, group related matches, understand the application field involved, write the narrowest safe exception and repeat until legitimate traffic no longer triggers unexplained alerts.&lt;/p&gt;
&lt;h2&gt;Start in detection-only mode&lt;/h2&gt;
&lt;p&gt;Introduce the full OWASP CRS to an existing production application in detection-only mode.&lt;/p&gt;
&lt;p&gt;On ModSecurity or Coraza, set &lt;code&gt;SecRuleEngine DetectionOnly&lt;/code&gt;. The engine records what the rules would have denied, dropped or redirected while allowing the requests through.&lt;/p&gt;
&lt;p&gt;Both engines support this rollout. The &lt;a href="https://github.com/owasp-modsecurity/ModSecurity/wiki/ModSecurity-Frequently-Asked-Questions-%28FAQ%29"&gt;ModSecurity FAQ recommends detection-only mode for a new ruleset&lt;/a&gt;, and &lt;a href="https://www.coraza.io/docs/seclang/directives/#secruleengine"&gt;Coraza implements the same setting&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Run it long enough to see the application's real shape. Allow at least a full week, and often one to two weeks, so the sample includes business days, a weekend and any less frequent jobs, imports or campaigns.&lt;/p&gt;
&lt;p&gt;Exercise authenticated actions, checkout, password reset, messaging, file upload, API writes and administrative tools manually because ordinary browsing may never reach them.&lt;/p&gt;
&lt;p&gt;Detection mode records rule matches while passing every request through, including attacks. Decide in advance what needs to be true before you enable blocking.&lt;/p&gt;
&lt;h2&gt;What changes between WAF products&lt;/h2&gt;
&lt;p&gt;The examples below use SecLang and OWASP CRS because they expose the rule ID, attack class and request field that caused a match. Managed WAFs record much the same information under different names, with varying precision for exceptions.&lt;/p&gt;
&lt;p&gt;Coraza also runs CRS 4. It is a Go implementation of SecLang, and the CRS project recommends it for new CRS 4 deployments. The &lt;a href="/blog/how-to-tune-coraza-waf/"&gt;Coraza tuning guide&lt;/a&gt; covers its audit logging, connectors and runtime exclusions in more detail.&lt;/p&gt;
&lt;p&gt;Body processors and chained rules can behave differently between engines and web-server connectors. Replay the requests that caused the false positive through the engine and connector that handle production traffic.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tuning job&lt;/th&gt;
&lt;th&gt;SecLang / CRS&lt;/th&gt;
&lt;th&gt;Managed WAF examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Observe without blocking&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SecRuleEngine DetectionOnly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Azure Detection mode, AWS rule-action override to Count, Google Cloud Armor preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identify the detection&lt;/td&gt;
&lt;td&gt;numeric rule ID and message&lt;/td&gt;
&lt;td&gt;managed rule ID or name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identify the class&lt;/td&gt;
&lt;td&gt;CRS tag such as &lt;code&gt;attack-sqli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rule group, category or emitted label&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identify the input&lt;/td&gt;
&lt;td&gt;&lt;code&gt;REQUEST_COOKIES:name&lt;/code&gt;, &lt;code&gt;ARGS:field&lt;/code&gt;, header or body target&lt;/td&gt;
&lt;td&gt;matched request component or exclusion selector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Narrow the exception&lt;/td&gt;
&lt;td&gt;target removal by ID or tag, optionally limited to a route&lt;/td&gt;
&lt;td&gt;field exclusion, per-rule exception, label-based follow-on rule or rule skip limited by request conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The last row is where the products differ most. ModSecurity and Coraza can stop one rule from inspecting one field on one route. Some managed WAFs can only skip the whole rule for requests matching that route. If that is the smallest exception available, write down exactly what the WAF will stop inspecting and decide whether the route is safe without that rule.&lt;/p&gt;
&lt;h2&gt;Group alerts before reviewing them&lt;/h2&gt;
&lt;p&gt;Reviewing WAF alerts one at a time hides repeated false positives.&lt;/p&gt;
&lt;p&gt;The same harmless field may trigger the same rule across thousands of requests. Conversely, two alerts with the same rule ID may come from completely different application behaviours and need different decisions.&lt;/p&gt;
&lt;p&gt;Group events using at least:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hostname and URL;&lt;/li&gt;
&lt;li&gt;HTTP method;&lt;/li&gt;
&lt;li&gt;CRS rule ID;&lt;/li&gt;
&lt;li&gt;rule class or attack tag, such as &lt;code&gt;attack-sqli&lt;/code&gt; or &lt;code&gt;attack-xss&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;matched-data variable, including its collection and exact name;&lt;/li&gt;
&lt;li&gt;WAF action and response status;&lt;/li&gt;
&lt;li&gt;event count, distinct clients and spread over time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, &lt;code&gt;REQUEST_COOKIES:analytics_id&lt;/code&gt; is much more useful than “SQL injection rule fired”. So is &lt;code&gt;ARGS:message&lt;/code&gt; on &lt;code&gt;POST /api/messages&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The rule ID tells you which detection fired. The attack class tells you which protection an exception would weaken. The matched variable identifies the request field that caused it. The URL and method show where the application uses that field.&lt;/p&gt;
&lt;p&gt;Keep a few examples of the matched values long enough to investigate them, but handle them carefully. Request fields can contain credentials, personal information and customer content. The exception record usually needs the variable name and an explanation, not a permanent copy of the raw value.&lt;/p&gt;
&lt;h2&gt;Expect false positives in a few places&lt;/h2&gt;
&lt;p&gt;A few kinds of input generate most legitimate matches.&lt;/p&gt;
&lt;h3&gt;Request cookies&lt;/h3&gt;
&lt;p&gt;Marketing, attribution, experimentation and client-state cookies often contain encoded or structured values. A generic SQLi, PHP or local-file-inclusion rule may find a suspicious fragment even though the application never treats that cookie as executable input.&lt;/p&gt;
&lt;p&gt;Keep inspection active for the &lt;code&gt;REQUEST_COOKIES&lt;/code&gt; collection and exclude only the cookie that caused the false positive.&lt;/p&gt;
&lt;p&gt;Identify the cookie name, the rules or attack classes it triggers, the routes where it appears, how often it occurs and whether those requests complete normally. If &lt;code&gt;REQUEST_COOKIES:analytics_id&lt;/code&gt; repeatedly triggers the same attack class for legitimate requests, consider an exception for that cookie alone.&lt;/p&gt;
&lt;p&gt;Cookie names need separate treatment. A rule matching &lt;code&gt;REQUEST_COOKIES_NAMES&lt;/code&gt; inspects the names. Capture the exact name safely before writing the exclusion.&lt;/p&gt;
&lt;h3&gt;Unparsed or malformed request bodies&lt;/h3&gt;
&lt;p&gt;Parser errors deserve their own investigation.&lt;/p&gt;
&lt;p&gt;When a selected body processor cannot parse a body, ModSecurity and Coraza set &lt;code&gt;REQBODY_ERROR&lt;/code&gt;. Record the &lt;code&gt;Content-Type&lt;/code&gt; and observed &lt;code&gt;REQBODY_PROCESSOR&lt;/code&gt; because JSON and XML processor selection varies by engine, connector and version. Confirm that a processor ran before relying on the error flag.&lt;/p&gt;
&lt;p&gt;A body that exceeds the configured inspection limit is a different case. It sets &lt;code&gt;INBOUND_DATA_ERROR&lt;/code&gt; and may be inspected only partially. In either case, part of the body may go uninspected, and an attacker can deliberately probe that behaviour.&lt;/p&gt;
&lt;p&gt;Ask the developer who owns the endpoint what it is meant to accept. Fix incorrect content types and body-processor configuration, reproduce the request, then check whether the application and WAF parse it the same way.&lt;/p&gt;
&lt;p&gt;Keep request-body inspection active for the application. Limit any parser exception to the host, URL, method and body behaviour you investigated.&lt;/p&gt;
&lt;h3&gt;Free-text fields&lt;/h3&gt;
&lt;p&gt;Messages, descriptions, comments, support tickets and search boxes are expected to contain language that resembles attack syntax. A customer can discuss SQL without attempting SQL injection. A marketplace listing can contain shell characters. A support message can include a URL.&lt;/p&gt;
&lt;p&gt;Exclude the named field on the affected route and keep inspection active for the rest of the request body.&lt;/p&gt;
&lt;p&gt;For example, the SecLang rule below removes &lt;code&gt;ARGS:message&lt;/code&gt; from every rule tagged &lt;code&gt;attack-sqli&lt;/code&gt; on the messaging endpoint. That is appropriate only if you have decided that SQLi inspection is too noisy for this field across the whole class. Every other argument still receives SQLi inspection, and scam, malicious-link or other attack classes can continue inspecting the message.&lt;/p&gt;
&lt;p&gt;Bring in the developer who owns the endpoint. They can confirm whether the field is genuinely free text, where it is stored or rendered, what server-side validation exists and which security checks still need to see it.&lt;/p&gt;
&lt;h3&gt;Application-specific structured fields&lt;/h3&gt;
&lt;p&gt;Some applications accept query languages, filters, templates, product metadata or nested JSON that naturally looks like code. The syntax may be legitimate, but these fields still deserve careful review before they are excluded.&lt;/p&gt;
&lt;p&gt;Confirm the schema and business use before creating an exception. Check that &lt;code&gt;MATCHED_VAR_NAME&lt;/code&gt; identifies one field, then use that exact named target. Nested JSON naming varies between engines and integrations. Keep SQLi inspection active for the rest of the API when one structured field needs an exception.&lt;/p&gt;
&lt;h2&gt;Make the smallest exception that works&lt;/h2&gt;
&lt;p&gt;Work down this list and stop as soon as the known false positive is fixed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;host, exact URL, method, rule ID or attack class, and exact named variable;&lt;/li&gt;
&lt;li&gt;host, reviewed URL family, method, class, and named variable;&lt;/li&gt;
&lt;li&gt;host, exact URL and rule ID;&lt;/li&gt;
&lt;li&gt;host, rule ID;&lt;/li&gt;
&lt;li&gt;global named-variable exclusion.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Start with the first option. Move further down only when the logs and the application design justify a wider exception.&lt;/p&gt;
&lt;p&gt;A global exception can make sense for a named cookie or header that has no security meaning anywhere in the application. Before adding one, make sure the false positive appears across several routes and clients, and that those requests complete normally. Ask the development team to confirm that no backend interprets the field.&lt;/p&gt;
&lt;p&gt;Even then, remove only that named field from the relevant rule or attack class. An exception for &lt;code&gt;REQUEST_COOKIES:analytics_id&lt;/code&gt; removes one known input from SQLi inspection. An exception for the whole cookie collection removes every cookie.&lt;/p&gt;
&lt;p&gt;Reserve global exceptions for logs that identify the matched field. Unknown body fields, unresolved cookie names and parser failures need manual investigation.&lt;/p&gt;
&lt;h2&gt;Keep your exceptions outside CRS&lt;/h2&gt;
&lt;p&gt;Put application-specific exceptions in local configuration and leave the Core Rule Set files unchanged. This keeps CRS upgrades separate from changes made for your application.&lt;/p&gt;
&lt;p&gt;To stop one rule inspecting a named field everywhere, ModSecurity and Coraza support:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;SecRuleUpdateTargetById&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;942100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;!REQUEST_COOKIES:analytics_id&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Load this directive after the CRS rules because the engine must load a rule before updating it.&lt;/p&gt;
&lt;p&gt;For a route-specific exception, use a runtime control before the CRS rule executes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# Load before CRS, normally from REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.&lt;/span&gt;
&lt;span class="c"&gt;# Use this rule only for api.example.com.&lt;/span&gt;
&lt;span class="c"&gt;# Confirm that local rule ID 10001 is unused.&lt;/span&gt;
&lt;span class="nb"&gt;SecRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;REQUEST_FILENAME&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@streq /api/messages&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id:10001,phase:1,pass,nolog,t:none,chain&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;SecRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;REQUEST_METHOD&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;@streq POST&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;\
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;t:none,ctl:ruleRemoveTargetByTag=attack-sqli;ARGS:message&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With this exception, SQLi rules stop reading &lt;code&gt;ARGS:message&lt;/code&gt; only on &lt;code&gt;POST /api/messages&lt;/code&gt;. They still inspect other fields and routes, while other attack classes can continue inspecting the message.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://coreruleset.org/docs/2-how-crs-works/2-3-false-positives-and-tuning/"&gt;CRS false-positive tuning guide&lt;/a&gt; documents configure-time and runtime exclusions by rule ID or tag. The &lt;code&gt;attack-sqli&lt;/code&gt; selector above is a literal tag that works with both ModSecurity and Coraza. ModSecurity runtime target selectors accept literal collection keys. &lt;a href="https://www.coraza.io/docs/seclang/actions/#ctl"&gt;Coraza also accepts regular expressions for collection keys&lt;/a&gt;, using &lt;code&gt;/pattern/&lt;/code&gt; syntax.&lt;/p&gt;
&lt;p&gt;A target exclusion may affect only the first rule in a chained CRS detection. Test the exact rules and requests on your deployed engine before relying on the exception.&lt;/p&gt;
&lt;p&gt;Use rule IDs or exact attack-class tags. Rule messages can change and may contain request data.&lt;/p&gt;
&lt;h2&gt;Managed WAFs expose different controls&lt;/h2&gt;
&lt;p&gt;Each managed provider expresses the exception differently.&lt;/p&gt;
&lt;p&gt;AWS Managed Rules can emit a label while an offending rule runs in &lt;code&gt;Count&lt;/code&gt; mode. A later custom rule can block that label everywhere except the route that produced the false positive. The exception affects enforcement for the whole managed rule on that route. &lt;a href="/blog/how-to-tune-aws-waf/"&gt;How to Tune AWS WAF&lt;/a&gt; follows that evaluation path from the first counted request to restored blocking.&lt;/p&gt;
&lt;p&gt;Azure Application Gateway WAF can exclude a named cookie, header or argument from a particular managed rule. Its request exceptions can instead skip a selected rule for matching traffic, and that feature is currently in preview. A custom &lt;code&gt;Allow&lt;/code&gt; rule bypasses the managed CRS, DRS and bot rules for the request. &lt;a href="/blog/how-to-tune-azure-application-gateway-waf/"&gt;How to Tune Azure Application Gateway WAF&lt;/a&gt; shows how to choose among those controls.&lt;/p&gt;
&lt;p&gt;Cloudflare exceptions skip a selected managed rule for the whole matching request. Security Events may show rule 949110 as the blocking event because it checks the final anomaly score; Additional logs identify the rules that added that score. &lt;a href="/blog/how-to-tune-cloudflare-waf/"&gt;How to Tune Cloudflare WAF&lt;/a&gt; covers that investigation and the ordering of account-level and zone-level exceptions.&lt;/p&gt;
&lt;p&gt;Google Cloud Armor can exclude a named request field from a versioned OWASP signature. Route-specific tuning also depends on the security-policy expression and rule priority, because a general WAF rule may inspect the same request later. &lt;a href="/blog/how-to-tune-google-cloud-armor-waf/"&gt;How to Tune Google Cloud Armor&lt;/a&gt; covers preview, signature selection and field exclusions.&lt;/p&gt;
&lt;p&gt;F5 BIG-IP Advanced WAF separates policy enforcement mode, violation actions and signature or entity staging. Its overrides can target a URL parameter, cookie, content profile, URL or the whole policy. &lt;a href="/blog/how-to-tune-f5-big-ip-advanced-waf/"&gt;How to Tune F5 BIG-IP Advanced WAF&lt;/a&gt; follows those controls from Transparent mode to tested blocking.&lt;/p&gt;
&lt;p&gt;Provider interfaces, ruleset versions and preview features change. Record the rule name or versioned signature, the field or request being exempted and what the WAF will still inspect. Test the requests that led to the exception before enabling it.&lt;/p&gt;
&lt;h2&gt;Iterate until the false positives are gone&lt;/h2&gt;
&lt;p&gt;After adding an exception, group the alerts again. Check whether that false-positive pattern disappeared, whether a related rule started matching the same field and whether another route was affected. Look for legitimate flows that the first observation window missed.&lt;/p&gt;
&lt;p&gt;Repeat the cycle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;collect detection-only traffic that covers normal application use;&lt;/li&gt;
&lt;li&gt;group by route, method, rule ID, rule class and named variable;&lt;/li&gt;
&lt;li&gt;classify abusive traffic separately from legitimate matches;&lt;/li&gt;
&lt;li&gt;create or narrow exceptions;&lt;/li&gt;
&lt;li&gt;rerun automated and manual application flows;&lt;/li&gt;
&lt;li&gt;collect another window.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Aim for zero known false positives in the traffic you observed and the critical flows you tested. A new endpoint, cookie, client or release can produce fresh false positives tomorrow.&lt;/p&gt;
&lt;p&gt;Use a higher anomaly threshold only during tuning because it can allow attacks that the normal threshold would have blocked. Write an exception for the rule and field causing the false positive, then return to the intended threshold.&lt;/p&gt;
&lt;h2&gt;Document every exception&lt;/h2&gt;
&lt;p&gt;Give every exception an owner, a reason and a review date.&lt;/p&gt;
&lt;p&gt;Record:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hostname, URL or URL family, and HTTP method;&lt;/li&gt;
&lt;li&gt;CRS version, rule IDs and attack-class tags;&lt;/li&gt;
&lt;li&gt;matched collection and exact variable name;&lt;/li&gt;
&lt;li&gt;sample event IDs and observation window;&lt;/li&gt;
&lt;li&gt;why the traffic is legitimate;&lt;/li&gt;
&lt;li&gt;which customer or application flow depends on it;&lt;/li&gt;
&lt;li&gt;what protection is removed and what remains active;&lt;/li&gt;
&lt;li&gt;developer and security approvers;&lt;/li&gt;
&lt;li&gt;date introduced and next review date;&lt;/li&gt;
&lt;li&gt;the release, incident or ticket associated with the change.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep the exception under version control. Give it a description that another engineer can understand without finding the original chat thread. Review it after CRS upgrades because rule IDs, paranoia levels and rule behaviour can change. The CRS project specifically recommends returning to detection mode long enough to exercise normal traffic after a major migration.&lt;/p&gt;
&lt;p&gt;Remove an exception when the application no longer needs it. If the application changes its cookie, parser or validation, restore inspection.&lt;/p&gt;
&lt;h2&gt;Decide what a WAF hit should do&lt;/h2&gt;
&lt;p&gt;Security and application owners must decide which matches to log, block, rate limit, challenge or review.&lt;/p&gt;
&lt;p&gt;With CRS anomaly scoring, an individual rule normally adds to a transaction score. The WAF blocks when the inbound score crosses the configured threshold, allowing several weak signals to add up before a customer is blocked.&lt;/p&gt;
&lt;p&gt;Decide which outcomes you need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Log:&lt;/strong&gt; useful during tuning and for low-confidence signals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block:&lt;/strong&gt; appropriate when several signals point to an attack and the affected flow has been tested.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rate limit:&lt;/strong&gt; useful for repeated behaviour across several requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Challenge or step up:&lt;/strong&gt; where the product supports it, useful for browser and account flows where a hard block would be unnecessarily disruptive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review:&lt;/strong&gt; appropriate for high-value actions where context matters more than a generic signature.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Respond to a single SQLi alert at the request level. Shared networks, mobile carriers and proxies make IP identity unreliable. Repeated WAF hits on the same route can inform a rate limit, but the limit should use the best available client, session or account key and have a clear window.&lt;/p&gt;
&lt;p&gt;Track WAF blocks and rate limits separately. A ModSecurity or Coraza block commonly returns &lt;code&gt;403&lt;/code&gt;; a rate limit commonly returns &lt;code&gt;429&lt;/code&gt;. If the logs say the WAF blocked a request but the origin returned success, find out why before trusting the enforcement reports.&lt;/p&gt;
&lt;h2&gt;Recheck the WAF after application releases&lt;/h2&gt;
&lt;p&gt;A release may add an endpoint, rename a field, introduce GraphQL, change JSON shape, add a marketing cookie or move validation between services. Any of those changes can make an old exception too broad or cause a new legitimate field to trigger CRS.&lt;/p&gt;
&lt;p&gt;Include the development team in the release process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;list new or changed public routes and methods;&lt;/li&gt;
&lt;li&gt;identify free-text, upload and structured fields;&lt;/li&gt;
&lt;li&gt;run staging traffic through the same CRS configuration;&lt;/li&gt;
&lt;li&gt;test authenticated and high-value journeys;&lt;/li&gt;
&lt;li&gt;watch the grouped detections during the production rollout;&lt;/li&gt;
&lt;li&gt;give new or substantially changed routes an observation period before enforcing new rules on them;&lt;/li&gt;
&lt;li&gt;review whether old exceptions can be removed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For major CRS upgrades, treat the ruleset itself as a release. A ruleset upgrade can change rule IDs and anomaly-score behaviour while the application stays the same.&lt;/p&gt;
&lt;h2&gt;Know when tuning is complete&lt;/h2&gt;
&lt;p&gt;A traditional WAF is ready to enforce when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;normal production traffic, including the less common application flows, has been reviewed;&lt;/li&gt;
&lt;li&gt;critical application flows have been tested with developers;&lt;/li&gt;
&lt;li&gt;every high-volume legitimate match has a documented, narrow exception;&lt;/li&gt;
&lt;li&gt;parser failures and unresolved matched variables have named owners and specific resolutions;&lt;/li&gt;
&lt;li&gt;WAF blocks produce the expected response;&lt;/li&gt;
&lt;li&gt;rate limiting is handled separately from payload detection;&lt;/li&gt;
&lt;li&gt;exceptions are versioned, approved and reviewable;&lt;/li&gt;
&lt;li&gt;new rules and releases can be observed before they start blocking.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once those checks pass, enable blocking in stages and keep watching the same groups of alerts. Revisit the exceptions whenever the application or ruleset changes.&lt;/p&gt;
&lt;p&gt;For the broader decision about where a WAF helps and where it does not, see &lt;a href="/blog/when-and-why-to-use-a-waf/"&gt;When and Why to Use a WAF&lt;/a&gt;. You can also read about Peakhour's &lt;a href="/products/waf/"&gt;web application firewall&lt;/a&gt;.&lt;/p&gt;</content><category term="Application Security"></category><category term="WAF"></category><category term="OWASP CRS"></category><category term="ModSecurity"></category><category term="Coraza"></category><category term="Application Security"></category><category term="False Positives"></category><category term="WAF Tuning"></category></entry></feed>