<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Peakhour.IO - WAF</title><link href="https://www.peakhour.io/" rel="alternate"></link><link href="https://www.peakhour.io/feeds/tag/waf.atom.xml" rel="self"></link><id>https://www.peakhour.io/</id><updated>2026-07-29T10:15:00+10:00</updated><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><entry><title>When and Why to Use a WAF: A Guide for WAF Sceptics</title><link href="https://www.peakhour.io/blog/when-and-why-to-use-a-waf/" rel="alternate"></link><published>2026-07-28T10:00:00+10:00</published><updated>2026-07-28T10:00:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-07-28:/blog/when-and-why-to-use-a-waf/</id><summary type="html">&lt;p&gt;WAFs are neither useless nor a security force field. Here is what they are good at, where they fail, and when putting one in front of an application makes sense.&lt;/p&gt;</summary><content type="html">&lt;p&gt;There are two popular opinions about web application firewalls.&lt;/p&gt;
&lt;p&gt;The first is that every public application needs one. Turn it on, enable the managed rules and enjoy protection from the OWASP Top 10, zero-days, bots and DDoS attacks.&lt;/p&gt;
&lt;p&gt;The second is that WAFs are expensive regex machines. They block customers, miss clever attacks and give management a green tick while the application remains vulnerable.&lt;/p&gt;
&lt;p&gt;The second group has better jokes. Neither group has the whole answer.&lt;/p&gt;
&lt;p&gt;A WAF is useful when it is given a job it can actually perform. It is disappointing when it is expected to understand an application better than the people who built it.&lt;/p&gt;
&lt;p&gt;The practical question is not “do WAFs work?” It is: which risks can this WAF reduce, what will it never understand, and who will operate it when the application changes?&lt;/p&gt;
&lt;h2&gt;What a WAF can see&lt;/h2&gt;
&lt;p&gt;A traditional WAF sits in the HTTP request path. It inspects requests and applies managed or custom rules before passing allowed traffic to the application.&lt;/p&gt;
&lt;p&gt;That position gives it a useful but limited view.&lt;/p&gt;
&lt;p&gt;It can see paths, methods, headers, cookies, query strings and supported request bodies. Depending on the product, it may also have access to IP reputation, network characteristics, bot signals, rate history and API schemas.&lt;/p&gt;
&lt;p&gt;It cannot naturally see the full business meaning of the request.&lt;/p&gt;
&lt;p&gt;The WAF may know that a customer requested &lt;code&gt;/api/invoices/123&lt;/code&gt;. The application knows whether that customer owns invoice 123. The WAF may see 200 valid checkout requests. The application knows whether they represent a wholesaler, a scalper or a broken integration.&lt;/p&gt;
&lt;p&gt;That boundary explains both the value and the frustration.&lt;/p&gt;
&lt;h2&gt;The criticism is mostly fair&lt;/h2&gt;
&lt;p&gt;WAF sceptics are right about several things.&lt;/p&gt;
&lt;p&gt;A determined attacker can bypass generic detection. Differences in encoding and HTTP parsing can cause a WAF and an application to interpret the same request differently. A 2024 study found &lt;a href="https://netsec.ccert.edu.cn/publications/sp24-wall"&gt;311 protocol-level evasion cases across 14 WAFs and 20 web frameworks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;False positives are real too. Legitimate HTML, JSON, support messages or product descriptions may resemble an attack signature. Microsoft recommends introducing a WAF in detection mode, reviewing logs and tuning it before blocking. It says &lt;a href="https://learn.microsoft.com/en-us/azure/web-application-firewall/afds/waf-faq"&gt;this process may take several weeks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A WAF also cannot fix insecure application logic. It cannot repair broken object-level authorisation, make password recovery safe or decide whether a valid-looking transaction is fraudulent. Those controls belong in the application.&lt;/p&gt;
&lt;p&gt;And, yes, putting anything inline creates another dependency. A poor rule can block real customers. A provider can have an outage. A self-hosted appliance can fail open or fail closed.&lt;/p&gt;
&lt;p&gt;These are reasons to be precise about the job. They are not reasons to send every internet request directly to the origin.&lt;/p&gt;
&lt;h2&gt;Buying time during a patch window&lt;/h2&gt;
&lt;p&gt;The best reason to have a WAF is rarely mentioned on the product page. It buys time.&lt;/p&gt;
&lt;p&gt;When a serious vulnerability appears, the correct response is to fix the affected software. But finding every instance, testing the patch and deploying it safely may take hours or days. Third-party and legacy applications can take longer.&lt;/p&gt;
&lt;p&gt;A virtual patch can narrow that exposure window. A custom rule may block the vulnerable path, reject a malicious parameter or detect the request pattern associated with the exploit while the real patch is prepared.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Virtual_Patching_Cheat_Sheet.html"&gt;OWASP's virtual patching guidance&lt;/a&gt; is clear about the boundary: correcting the source is the preferred remediation. The WAF rule is a compensating control, not permission to leave the vulnerability in place.&lt;/p&gt;
&lt;p&gt;That still makes it valuable. During an incident, ten minutes and two days are very different amounts of exposure.&lt;/p&gt;
&lt;h2&gt;Stopping expensive requests earlier&lt;/h2&gt;
&lt;p&gt;Applications are expensive places to reject bad traffic.&lt;/p&gt;
&lt;p&gt;By the time a request reaches application code it may have consumed a connection, invoked authentication, queried a database, called another API or triggered a paid service. A request does not need to contain an obvious exploit to create cost. It may simply repeat an expensive operation far more often than a legitimate user would.&lt;/p&gt;
&lt;p&gt;An edge control can reject common exploit traffic and excessive requests before that work happens.&lt;/p&gt;
&lt;p&gt;This is especially useful for login, search, registration, password reset, checkout and expensive API routes. Rate limits and bot controls can respond differently based on route, session, account outcome and other available evidence instead of treating every request from an IP address as the same actor.&lt;/p&gt;
&lt;p&gt;NIST recommends applying general WAF, bot and denial-of-service controls early in the API serving stack to protect upstream systems from malicious payloads and unrestricted resource consumption. It also &lt;a href="https://csrc.nist.gov/pubs/sp/800/228/upd1/final"&gt;treats those as separate capabilities&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That distinction matters. A classic WAF does not automatically provide sophisticated bot management or network-scale DDoS protection. A broader WAAP service may bundle them, but buyers should check the controls rather than trust the acronym.&lt;/p&gt;
&lt;h2&gt;A control you can reach during an incident&lt;/h2&gt;
&lt;p&gt;Security incidents create awkward gaps between knowing there is a problem and having the final fix ready.&lt;/p&gt;
&lt;p&gt;Perhaps one endpoint is being abused. A new exploit is spreading. Login failures are climbing. A scraper is rotating through residential proxies. An old hostname that should have disappeared is suddenly receiving traffic.&lt;/p&gt;
&lt;p&gt;A centrally managed WAF gives the operations team somewhere to make a narrow, reversible change without waiting for several application releases. They can restrict a route, add a virtual patch, change a rate limit, challenge suspicious traffic or collect more detailed events while the investigation continues.&lt;/p&gt;
&lt;p&gt;That operational separation is valuable. The application team can work on the root cause while the security or operations team reduces immediate exposure.&lt;/p&gt;
&lt;p&gt;It also needs discipline. Emergency rules require owners and expiry dates. A temporary exclusion should not survive for years. Changes should be observed before blocking where the situation allows it.&lt;/p&gt;
&lt;h2&gt;Evidence before the origin&lt;/h2&gt;
&lt;p&gt;A WAF is often discussed only as a blocking device. Its position in the request path can be just as useful for understanding what is happening.&lt;/p&gt;
&lt;p&gt;Good events can show which route was targeted, which rule matched, what action was taken and whether the request reached the origin. Rate, bot and API signals can add context. Combined with application logs, this helps teams distinguish an attack from a customer problem, a broken integration or a badly tuned rule.&lt;/p&gt;
&lt;p&gt;The same visibility can expose weaknesses in the deployment itself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a forgotten API version receiving traffic;&lt;/li&gt;
&lt;li&gt;a hostname missing the expected policy;&lt;/li&gt;
&lt;li&gt;an origin that is still publicly reachable;&lt;/li&gt;
&lt;li&gt;an endpoint whose cost makes it an easy denial-of-service target;&lt;/li&gt;
&lt;li&gt;an exclusion that is much broader than anyone remembered.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This does not require blocking on day one. Observe mode can create value before the team is confident enough to enforce a decision.&lt;/p&gt;
&lt;h2&gt;When the case for a WAF is strong&lt;/h2&gt;
&lt;p&gt;A WAF is usually worth serious consideration when the application has several of these characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;customer accounts, payments or personal information;&lt;/li&gt;
&lt;li&gt;public APIs or expensive dynamic routes;&lt;/li&gt;
&lt;li&gt;third-party, legacy or slow-to-patch software;&lt;/li&gt;
&lt;li&gt;regular scanning, credential stuffing, scraping or automated abuse;&lt;/li&gt;
&lt;li&gt;multiple applications that need consistent emergency controls;&lt;/li&gt;
&lt;li&gt;compliance requirements for runtime web protection;&lt;/li&gt;
&lt;li&gt;an operations team able to tune rules and investigate events.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PCI DSS provides one concrete example. Since 31 March 2025, Requirement 6.4.2 requires an automated technical solution in front of applicable public-facing web applications to continually detect and prevent web attacks. The &lt;a href="https://www.pcisecuritystandards.org/faqs/1593/"&gt;PCI Security Standards Council describes the requirement&lt;/a&gt; without requiring a particular product called a WAF.&lt;/p&gt;
&lt;p&gt;Compliance is a legitimate reason to deploy the control. It is not evidence that the deployment works.&lt;/p&gt;
&lt;h2&gt;When the case is weak&lt;/h2&gt;
&lt;p&gt;Not every website needs a large WAAP contract.&lt;/p&gt;
&lt;p&gt;A genuinely static, low-value site with no accounts, API or meaningful dynamic input presents a smaller application attack surface. Basic CDN protection, origin restriction, good patching and sensible security headers may be enough.&lt;/p&gt;
&lt;p&gt;The case is also weak when nobody will own the WAF. An ignored ruleset eventually becomes noisy, stale or full of broad exclusions. A dashboard that nobody checks is not defence in depth.&lt;/p&gt;
&lt;p&gt;Be cautious when the proposed product does not match the actual problem. A signature WAF is not the complete answer to sophisticated bots. An on-premises appliance cannot absorb a volumetric attack larger than its internet connection. Edge rules cannot repair business logic. A provider's “AI-powered” label does not establish an acceptable false-positive rate.&lt;/p&gt;
&lt;p&gt;Sometimes the right answer is a smaller control. Sometimes it is a different control. The point is to buy against the risk rather than the category name.&lt;/p&gt;
&lt;h2&gt;How not to become the WAF horror story&lt;/h2&gt;
&lt;p&gt;Most bad WAF experiences begin with the same mistake: enabling everything and immediately blocking production traffic.&lt;/p&gt;
&lt;p&gt;A safer deployment looks less exciting:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define the job. Decide which applications, routes and abuse cases the WAF is expected to cover.&lt;/li&gt;
&lt;li&gt;Put all relevant traffic through it and restrict direct access to the origin.&lt;/li&gt;
&lt;li&gt;Start managed rules and new controls in observe or count mode.&lt;/li&gt;
&lt;li&gt;Review real production-shaped traffic and tune false positives narrowly.&lt;/li&gt;
&lt;li&gt;Test login, checkout, uploads, APIs and other important customer journeys before enforcement.&lt;/li&gt;
&lt;li&gt;Give every custom rule and virtual patch an owner, reason and review date.&lt;/li&gt;
&lt;li&gt;Monitor allowed as well as blocked traffic so false negatives do not disappear from view.&lt;/li&gt;
&lt;li&gt;Keep application-level authorisation, validation and rate controls in place.&lt;/li&gt;
&lt;li&gt;Test what happens when the WAF, agent or provider is unavailable.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That is the difference between owning a control and merely paying for one.&lt;/p&gt;
&lt;h2&gt;Where this leaves us&lt;/h2&gt;
&lt;p&gt;A WAF is neither useless nor sufficient.&lt;/p&gt;
&lt;p&gt;It cannot understand every valid-looking abuse, prevent every bypass or rescue an application that ignores secure design. It introduces cost, tuning and another inline dependency.&lt;/p&gt;
&lt;p&gt;It can still stop a great deal of cheap hostile traffic, buy time during a vulnerability, prevent excessive work from reaching the origin, give operators a fast response point and leave evidence that helps explain an incident.&lt;/p&gt;
&lt;p&gt;Our two companion articles make each side of the case directly: &lt;a href="/blog/everyone-says-a-waf-is-useless/"&gt;Everyone Says a WAF Is Useless. Are They Right?&lt;/a&gt; and &lt;a href="/blog/your-application-needs-a-waf/"&gt;Your Application Needs a WAF. Just Not for the Reason You Think&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The conclusion is less catchy than either headline. Use a &lt;a href="/products/waf/"&gt;web application firewall&lt;/a&gt; when the application has something worth protecting, the WAF has a defined job and someone is responsible for operating it. Then keep fixing the code behind it.&lt;/p&gt;</content><category term="Application Security"></category><category term="WAF"></category><category term="WAAP"></category><category term="Application Security"></category><category term="API Security"></category><category term="Virtual Patching"></category><category term="Bot Management"></category></entry><entry><title>Your Application Needs a WAF. Just Not for the Reason You Think</title><link href="https://www.peakhour.io/blog/your-application-needs-a-waf/" rel="alternate"></link><published>2026-07-28T09:30:00+10:00</published><updated>2026-07-28T09:30:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-07-28:/blog/your-application-needs-a-waf/</id><summary type="html">&lt;p&gt;A web application firewall will not fix insecure code. It can still give applications a valuable layer of protection when bugs, bots and attacks reach the front door.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A web application firewall will not make bad code good.&lt;/p&gt;
&lt;p&gt;It will not repair broken access control. It will not redesign an unsafe checkout. It cannot decide whether a customer should be allowed to see a particular invoice, transfer money from an account or buy the last 200 tickets to a concert.&lt;/p&gt;
&lt;p&gt;If your public application has accounts, valuable APIs or expensive dynamic routes, you probably still need one.&lt;/p&gt;
&lt;p&gt;The case has nothing to do with an invisible shield or a promise to “solve the OWASP Top 10”. Public applications meet hostile traffic every day, software is imperfect and fixing the right thing often takes longer than attacking it.&lt;/p&gt;
&lt;p&gt;That is what a WAF is for.&lt;/p&gt;
&lt;h2&gt;The internet does not wait for your next release&lt;/h2&gt;
&lt;p&gt;Every public application receives traffic it did not ask for.&lt;/p&gt;
&lt;p&gt;Some of it is harmless background noise: crawlers looking for software that is not installed, scripts requesting old WordPress paths, scanners testing yesterday's vulnerability against every address they can find.&lt;/p&gt;
&lt;p&gt;Some of it is aimed directly at the application: SQL injection attempts, malicious uploads, path traversal, unusual encodings and requests crafted to trigger a known vulnerability.&lt;/p&gt;
&lt;p&gt;The application can reject those requests itself. Ideally, it will. But allowing every request to reach application code, authentication services, databases and third-party APIs is an expensive way to discover that something is hostile.&lt;/p&gt;
&lt;p&gt;A WAF creates an earlier decision point. It can discard common exploit traffic before the request reaches the part of the system that holds customer data or performs expensive work.&lt;/p&gt;
&lt;p&gt;That does not make the application secure. It means the application no longer has to face every cheap attack on its own.&lt;/p&gt;
&lt;h2&gt;Patches take time. WAF rules take less&lt;/h2&gt;
&lt;p&gt;The clearest reason to run a WAF is virtual patching.&lt;/p&gt;
&lt;p&gt;Suppose a serious vulnerability appears in a framework, CMS plugin or third-party product. The responsible answer is to patch it. But first the team has to identify every affected system, test the update, work around compatibility problems and arrange a safe deployment.&lt;/p&gt;
&lt;p&gt;The attacker does not have to do any of that.&lt;/p&gt;
&lt;p&gt;A targeted WAF rule can reduce the exposure while the real patch is prepared. It may block a particular path, parameter, payload or request shape associated with the exploit. It is not the final repair, but it can turn an open window into a much narrower one.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Virtual_Patching_Cheat_Sheet.html"&gt;OWASP's virtual patching guidance&lt;/a&gt; is refreshingly practical about this. Fixing the source is the number-one technical remediation. A virtual patch is useful when that fix cannot happen immediately. The two controls are complementary, not rivals.&lt;/p&gt;
&lt;p&gt;This matters most for legacy applications, third-party software and systems with slow release cycles. Those are exactly the places where “just fix the code” can be correct advice and an inadequate incident response.&lt;/p&gt;
&lt;h2&gt;Your application should not absorb unlimited work&lt;/h2&gt;
&lt;p&gt;Not every damaging request contains an obvious attack string.&lt;/p&gt;
&lt;p&gt;A search request may be perfectly valid but expensive. A login attempt may fail normally while being one of a million credential-stuffing attempts. A scraper may request public pages exactly as a browser would, just much faster. A customer-facing API may trigger email, SMS, payment or database costs every time it is called.&lt;/p&gt;
&lt;p&gt;This is where a modern WAAP service goes beyond a traditional signature WAF. Rate limiting, bot management and application-layer DDoS controls can make decisions based on the route, request rate, account outcome, browser or network evidence, and the work the origin is being asked to perform.&lt;/p&gt;
&lt;p&gt;NIST recommends placing general WAF, bot and denial-of-service controls early in the API serving stack so malicious payloads and unrestricted resource consumption do not exhaust upstream systems. Importantly, &lt;a href="https://csrc.nist.gov/pubs/sp/800/228/upd1/final"&gt;NIST treats these as related but separate capabilities&lt;/a&gt;. Buying something called a WAF does not automatically mean all three are present.&lt;/p&gt;
&lt;p&gt;Reject bad or excessive work before it becomes expensive work.&lt;/p&gt;
&lt;h2&gt;Put the checks in different places&lt;/h2&gt;
&lt;p&gt;Good application security is built in layers because each layer knows something different.&lt;/p&gt;
&lt;p&gt;The application knows who owns an object, whether an account may perform an action and what a valid transaction looks like. It should remain authoritative for those decisions.&lt;/p&gt;
&lt;p&gt;The edge sees different evidence. It can compare traffic across routes and hosts, recognise widespread exploit patterns, apply an emergency rule across several applications and notice that one client is creating an unusual amount of work before the request reaches the origin.&lt;/p&gt;
&lt;p&gt;Neither view is complete.&lt;/p&gt;
&lt;p&gt;A WAF is valuable because it is independent of the code it protects. If an application developer makes a mistake, the WAF may still reject the exploit. If a WAF misses a clever request, the application should still validate it. One control can fail without immediately making the failure fatal.&lt;/p&gt;
&lt;p&gt;If either layer makes a mistake, the other still gets a chance to respond. That is the useful part of defence in depth.&lt;/p&gt;
&lt;h2&gt;It gives you somewhere to respond&lt;/h2&gt;
&lt;p&gt;During an incident, speed matters.&lt;/p&gt;
&lt;p&gt;Teams often know enough to reduce the risk before they know enough to ship the final fix. They may need to restrict a vulnerable endpoint, block an exploit pattern, lower a rate limit, challenge suspicious login traffic or temporarily exclude a region where an attack is concentrated.&lt;/p&gt;
&lt;p&gt;Doing that through application releases can be slow, especially when several services are affected. A centrally managed WAF gives operators somewhere to make a narrow, reversible change while the application teams investigate.&lt;/p&gt;
&lt;p&gt;The words “narrow” and “reversible” matter. Emergency blocks need owners and expiry dates. Rules should be tested in observe mode where time permits. Broad exclusions created during the incident should not quietly become permanent architecture.&lt;/p&gt;
&lt;p&gt;The WAF is useful here because it is an operational control, not because it knows more about the application than its developers do.&lt;/p&gt;
&lt;h2&gt;It leaves evidence at the front door&lt;/h2&gt;
&lt;p&gt;Prevention gets most of the attention, but visibility is one of the better reasons to have a WAF.&lt;/p&gt;
&lt;p&gt;When an application behaves strangely, the team needs to know what arrived before the failure. Which routes were targeted? Were requests blocked or allowed? Did the traffic share a network, client pattern or payload? Was a rate limit crossed? Did the attacker reach the origin directly?&lt;/p&gt;
&lt;p&gt;Useful WAF events can answer part of that story. Combined with origin and application logs, they help distinguish a real attack from a broken integration, a customer mistake or a badly tuned rule.&lt;/p&gt;
&lt;p&gt;They can also reveal traffic that nobody knew was public: an old API version, forgotten hostname, administrative route or direct-origin path.&lt;/p&gt;
&lt;p&gt;Logs are not automatically useful. A million unexplained rule matches can be worse than a smaller set of events tied to route, action and outcome. But without evidence at the entry point, incident response starts with a large blind spot.&lt;/p&gt;
&lt;h2&gt;Compliance is a reason, but not the best one&lt;/h2&gt;
&lt;p&gt;For organisations handling payment-card data, a runtime application control may not be optional. Since 31 March 2025, PCI DSS Requirement 6.4.2 requires an automated technical solution in front of applicable public-facing web applications to continually detect and prevent web attacks.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://www.pcisecuritystandards.org/faqs/1593/"&gt;PCI Security Standards Council describes the requirement&lt;/a&gt; without mandating a particular product called a WAF.&lt;/p&gt;
&lt;p&gt;That distinction matters. Compliance can justify the control, but it does not prove the control is effective. A WAF in log-only mode with nobody reading the alerts is not much of a defence. Neither is one with a public origin that attackers can reach directly.&lt;/p&gt;
&lt;p&gt;Passing an audit is a business requirement. Blocking useful attacks without blocking customers is the security requirement.&lt;/p&gt;
&lt;h2&gt;A WAF needs to be operated, not merely purchased&lt;/h2&gt;
&lt;p&gt;The case for a WAF comes with a condition: somebody has to own it.&lt;/p&gt;
&lt;p&gt;Managed rules need to be introduced carefully. Legitimate traffic needs to be observed. False positives need narrow corrections rather than sweeping allowlists. Important customer journeys need tests. Virtual patches need to be removed after the underlying vulnerability is fixed. Origins need to be locked down so the edge cannot be bypassed.&lt;/p&gt;
&lt;p&gt;The job is not finished when the DNS changes.&lt;/p&gt;
&lt;p&gt;The right first step is often detection rather than immediate blocking. Learn what normal traffic looks like, identify the noisy rules, find missing hostnames and understand which requests are expensive. Then move well-understood controls into enforcement.&lt;/p&gt;
&lt;p&gt;A badly operated WAF can block customers, miss attacks and create false confidence. That is a reason to operate it properly, not a reason to give the application no independent protection at all.&lt;/p&gt;
&lt;h2&gt;What a WAF is actually for&lt;/h2&gt;
&lt;p&gt;A WAF should make exploitation harder, give operators a faster response and reduce what reaches the origin. In practice that means stopping familiar attack traffic, buying time during a vulnerability, putting limits in front of expensive routes and recording enough evidence to explain what arrived.&lt;/p&gt;
&lt;p&gt;Developers still own secure design. The WAF gives them another layer, not an exemption.&lt;/p&gt;
&lt;p&gt;Our companion article, &lt;a href="/blog/everyone-says-a-waf-is-useless/"&gt;Everyone Says a WAF Is Useless. Are They Right?&lt;/a&gt;, takes the sceptical case seriously. WAFs can be bypassed. They can cause false positives. They create cost and another inline dependency.&lt;/p&gt;
&lt;p&gt;Those are real constraints. They define how a &lt;a href="/products/waf/"&gt;web application firewall&lt;/a&gt; should be deployed and judged.&lt;/p&gt;
&lt;p&gt;They do not change the basic reality: bugs happen, patches take time and bots are cheap. It is sensible to make hostile traffic cross one more independent decision point before it reaches the application.&lt;/p&gt;</content><category term="Application Security"></category><category term="WAF"></category><category term="WAAP"></category><category term="Application Security"></category><category term="API Security"></category><category term="Virtual Patching"></category><category term="Bot Management"></category></entry><entry><title>Everyone Says a WAF Is Useless. Are They Right?</title><link href="https://www.peakhour.io/blog/everyone-says-a-waf-is-useless/" rel="alternate"></link><published>2026-07-28T09:00:00+10:00</published><updated>2026-07-28T09:00:00+10:00</updated><author><name>AC</name></author><id>tag:www.peakhour.io,2026-07-28:/blog/everyone-says-a-waf-is-useless/</id><summary type="html">&lt;p&gt;Ten good reasons not to trust a web application firewall—and why most of them are arguments for using one properly, not going without one.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Spend long enough around application security and someone will tell you that web application firewalls are useless.&lt;/p&gt;
&lt;p&gt;They are easy to bypass. They block real customers. They cannot fix bad code. They cost money, need constant tuning and create one more thing that can fail.&lt;/p&gt;
&lt;p&gt;The annoying part is that none of this is entirely wrong.&lt;/p&gt;
&lt;p&gt;The mistake is jumping from “a WAF is imperfect” to “a WAF has no useful job”. A lock can be picked. We still lock the door. Security controls are allowed to solve a bounded problem.&lt;/p&gt;
&lt;p&gt;So here are ten excellent reasons not to use a WAF—and what they really tell us.&lt;/p&gt;
&lt;h2&gt;1. “We write secure code”&lt;/h2&gt;
&lt;p&gt;Good. Keep doing that.&lt;/p&gt;
&lt;p&gt;A WAF cannot repair broken access control, fix an insecure password reset flow or stop an application from selling the same scarce ticket twice. Those rules belong in the application because the application understands the customer, the object and the transaction.&lt;/p&gt;
&lt;p&gt;But even good teams ship bugs. Dependencies develop vulnerabilities. A vendor patch can take days to test. A legacy application may not have a team at all.&lt;/p&gt;
&lt;p&gt;This is where virtual patching earns its keep. A narrowly written WAF rule can block a known exploit while the real fix is prepared. &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Virtual_Patching_Cheat_Sheet.html"&gt;OWASP describes the source-code fix as the preferred answer&lt;/a&gt;, while also recognising that an enforcement layer can reduce the exposure window.&lt;/p&gt;
&lt;p&gt;Secure code and a WAF are not competing ideas. One fixes the weakness. The other can make it harder to exploit in the meantime.&lt;/p&gt;
&lt;h2&gt;2. “Attackers can bypass it”&lt;/h2&gt;
&lt;p&gt;They can.&lt;/p&gt;
&lt;p&gt;Researchers have found WAF bypasses using alternative encodings, duplicated parameters and differences between the way a firewall and an application parse the same request. One 2024 study found &lt;a href="https://netsec.ccert.edu.cn/publications/sp24-wall"&gt;311 protocol-level evasion cases across 14 WAFs and 20 web frameworks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That finding should end any talk of a WAF as a force field. It should not convince us to pass every request straight to the application.&lt;/p&gt;
&lt;p&gt;Most hostile internet traffic is not a handcrafted masterpiece. It is scanning, probing and replaying familiar attacks across thousands of sites. Filtering that traffic does not make the application invulnerable. It removes a large amount of cheap, automated opportunity.&lt;/p&gt;
&lt;p&gt;A determined burglar may pick a lock. We still lock the door.&lt;/p&gt;
&lt;h2&gt;3. “WAFs block legitimate customers”&lt;/h2&gt;
&lt;p&gt;An untuned WAF certainly can.&lt;/p&gt;
&lt;p&gt;A product description, support ticket or API request may contain HTML, SQL-like text or unusual characters for perfectly innocent reasons. Turn every managed rule on, switch immediately to blocking mode and the first security incident may be your own checkout failing.&lt;/p&gt;
&lt;p&gt;Microsoft recommends starting a new WAF in detection mode, reviewing the logs and tuning it before enforcement. It also says &lt;a href="https://learn.microsoft.com/en-us/azure/web-application-firewall/afds/waf-faq"&gt;the process may take several weeks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What this rules out is the Friday-afternoon deployment: every managed rule enabled, blocking turned on and nobody watching checkout.&lt;/p&gt;
&lt;p&gt;Start by observing. Find the rules that match real traffic. Make exclusions as narrow as possible. Test important customer journeys. Then block what you understand.&lt;/p&gt;
&lt;h2&gt;4. “It cannot understand our business”&lt;/h2&gt;
&lt;p&gt;Correct. This may be the most important criticism on the list.&lt;/p&gt;
&lt;p&gt;A generic WAF cannot know that customer A should never see customer B's invoice. It cannot know whether buying 400 concert tickets is normal wholesale activity or scalping. It cannot decide whether a bank transfer fits the customer's history.&lt;/p&gt;
&lt;p&gt;Many serious API failures involve valid-looking requests used in the wrong context. The &lt;a href="https://owasp.org/API-Security/editions/2023/en/0x11-t10/"&gt;OWASP API Security Top 10&lt;/a&gt; is full of problems involving object ownership, authentication, permissions, resource consumption and sensitive business flows.&lt;/p&gt;
&lt;p&gt;Those controls need application context.&lt;/p&gt;
&lt;p&gt;There is still work suited to the edge: reject malformed requests, enforce broad limits, filter known exploit patterns and record what reached a sensitive route. A wider WAAP service may also add API schemas, bot signals and account-abuse controls. The application remains responsible for the final business decision.&lt;/p&gt;
&lt;h2&gt;5. “We already have a CDN”&lt;/h2&gt;
&lt;p&gt;You may already have the machinery for a WAF. That does not mean it is protecting anything.&lt;/p&gt;
&lt;p&gt;Is the managed ruleset enabled? Is it logging or blocking? Does it cover every public hostname and API version? Can an attacker connect directly to the origin? Are large or compressed request bodies inspected? Has anyone reviewed the exclusions since the last application release?&lt;/p&gt;
&lt;p&gt;A WAF only protects traffic that passes through it. Cloudflare's own troubleshooting guide says it only mitigates proxied traffic. AWS separately documents how to restrict an origin so visitors cannot bypass CloudFront and reach it directly.&lt;/p&gt;
&lt;p&gt;“We have a CDN” is an architecture statement. “All public traffic crosses an active, tested policy” is a security statement.&lt;/p&gt;
&lt;h2&gt;6. “A WAF will not stop a huge DDoS attack”&lt;/h2&gt;
&lt;p&gt;Also true.&lt;/p&gt;
&lt;p&gt;If an attack fills the network connection in front of an on-premises WAF, the appliance may be functioning perfectly while the website remains unreachable. Volumetric attacks need upstream capacity: an ISP, cloud network, CDN or dedicated scrubbing service able to absorb them before they reach the constrained link.&lt;/p&gt;
&lt;p&gt;Application-layer attacks are different. They target expensive searches, login endpoints, uncached pages, GraphQL queries or API operations that consume disproportionate origin resources. Rate limits and request-aware controls can help there.&lt;/p&gt;
&lt;p&gt;This is why the distinction between WAF and WAAP matters. A classic WAF filters HTTP requests. A WAAP bundle may combine that with bot management, API protection and application-layer DDoS controls. The acronym does not tell you which controls are included, licensed or switched on.&lt;/p&gt;
&lt;h2&gt;7. “Bots just rotate their IP addresses”&lt;/h2&gt;
&lt;p&gt;Yes, and an IP blocklist on its own is a fairly tired answer.&lt;/p&gt;
&lt;p&gt;Modern abuse can arrive through residential proxies, mobile networks, cloud platforms and large pools of short-lived addresses. If the only question is “have we seen this IP before?”, the attacker often wins by changing it.&lt;/p&gt;
&lt;p&gt;Useful bot management combines several weaker signals: request rate, route, session behaviour, account outcome, headers, network characteristics and browser evidence. The response can vary too. A suspicious request might be logged, slowed, challenged or given a tighter limit instead of being permanently blocked.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Bot_Management_and_Anti-Automation_Cheat_Sheet.html"&gt;OWASP's bot-management guidance&lt;/a&gt; makes the goal clear: raise the cost of abusive automation while leaving legitimate people and useful bots alone.&lt;/p&gt;
&lt;p&gt;An IP list is easier. It is also further from the problem.&lt;/p&gt;
&lt;h2&gt;8. “It is another expensive security subscription”&lt;/h2&gt;
&lt;p&gt;Sometimes it is. WAF pricing can be a peculiar experience because a business may pay to inspect millions of requests it never wanted.&lt;/p&gt;
&lt;p&gt;The fair comparison is not “WAF bill versus zero”. It is the WAF bill against origin compute, emergency engineering, fraud, scraping, support calls, application downtime and the cost of investigating an incident with poor logs.&lt;/p&gt;
&lt;p&gt;That does not guarantee the WAF wins. A brochure site with no accounts, no API and almost no dynamic input may need little more than sensible CDN protection, origin restriction and good patching. A busy login or checkout path under constant automated abuse has a different calculation.&lt;/p&gt;
&lt;p&gt;The control should match the exposure. Buying the largest WAAP bundle available is not a security strategy.&lt;/p&gt;
&lt;h2&gt;9. “It is not set-and-forget”&lt;/h2&gt;
&lt;p&gt;No useful security control is.&lt;/p&gt;
&lt;p&gt;Applications change. New endpoints appear. Old APIs stay online longer than expected. Managed rules are updated. Attack traffic changes. An exclusion created during an incident quietly survives for three years.&lt;/p&gt;
&lt;p&gt;A WAF needs an owner, observable decisions, staged changes and periodic review. Virtual patches need expiry dates. Important customer journeys need regression tests. Logs need someone who will look at them.&lt;/p&gt;
&lt;p&gt;If nobody will operate the WAF, that is a legitimate reason not to buy an elaborate one. A permanently ignored dashboard is not defence in depth. It is interior decoration.&lt;/p&gt;
&lt;h2&gt;10. “Putting it inline creates another point of failure”&lt;/h2&gt;
&lt;p&gt;It does.&lt;/p&gt;
&lt;p&gt;A bad rule can block a site globally. A provider can have an outage. A self-hosted appliance can fail open or fail closed. In 2019, Cloudflare published a detailed account of how a WAF rule containing a poorly performing regular expression exhausted CPUs across its network.&lt;/p&gt;
&lt;p&gt;Inline controls need the same engineering discipline as the applications behind them: staged rollout, rollback, monitoring, tested failure modes and a clear way to recover.&lt;/p&gt;
&lt;p&gt;The origin, load balancer, DNS provider, identity service and database are points of failure too. We accept those dependencies when the service is worth it, then engineer around their failures. A WAF deserves the same treatment.&lt;/p&gt;
&lt;h2&gt;So, do you need a WAF?&lt;/h2&gt;
&lt;p&gt;Maybe not.&lt;/p&gt;
&lt;p&gt;If the site is genuinely static, low value and already protected by a capable edge, a premium WAAP service may solve very little. If nobody will tune it, test it or respond to its alerts, the product may create more confidence than protection.&lt;/p&gt;
&lt;p&gt;The case becomes much stronger when the application has accounts, payments, personal information, valuable APIs, third-party dependencies, slow patch cycles or a history of automated abuse. It becomes stronger again when the same edge can protect the origin, control bots, limit expensive routes and leave useful evidence for the people investigating incidents.&lt;/p&gt;
&lt;p&gt;A &lt;a href="/products/waf/"&gt;web application firewall&lt;/a&gt; does not make an insecure application secure. The honest claim is smaller: applications have bugs, patches take time and the public internet is noisy. A well-run WAF gives that noise one more place to stop before it becomes your application's problem.&lt;/p&gt;</content><category term="Application Security"></category><category term="WAF"></category><category term="WAAP"></category><category term="Application Security"></category><category term="API Security"></category><category term="Bot Management"></category><category term="DDoS Protection"></category></entry></feed>