<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Peakhour.IO - Google Cloud</title><link href="https://www.peakhour.io/" rel="alternate"></link><link href="https://www.peakhour.io/feeds/tag/google-cloud.atom.xml" rel="self"></link><id>https://www.peakhour.io/</id><updated>2026-07-29T10:00:00+10:00</updated><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></feed>