ModSecurity WAF¶
ModSecurity runs inline on every ingress-nginx instance, enforcing the OWASP Core Rule Set (CRS) on all inbound HTTP/S requests. Blocking is on by default (SecRuleEngine On); per-ingress exceptions are applied surgically when needed.
How a Block is Diagnosed¶
flowchart LR
client["Client request"] -->|"HTTP 403"| nginx["ingress-nginx\nModSecurity"]
nginx -->|"logs unique_id"| log["nginx error log"]
log -->|"grep unique_id"| rule["Triggering rule ID\n(not 949110)"]
rule --> fix["Apply exception\nin ingress annotation"]
Rule 949110 is always the final aggregator — the anomaly score exceeded the threshold. The actual triggering rule is found by grepping the same unique_id across all log entries for that request.
# Tail live WAF logs
./modsec.sh
# Find triggering rule from a block
kubectl --context=mdapi-prod -n kube-system logs \
-l app.kubernetes.io/name=rke2-ingress-nginx --tail 2000 \
| grep -i modsec | grep <unique_id>
Common Rules¶
| Rule | Description | Score |
|---|---|---|
| 911100 | HTTP method not allowed | 5 |
| 941100 | XSS via libinjection | 5 |
| 942100 | SQLi via libinjection | 5 |
| 932100–932180 | Unix shell / RCE detection | 5 |
| 949110 | Anomaly score exceeded (aggregator) | — |
Default inbound anomaly threshold: 5 (one critical rule triggers a block).
Applying Exceptions¶
Exceptions live in the ingress annotation nginx.ingress.kubernetes.io/modsecurity-snippet. Two patterns cover most cases:
Allow additional HTTP methods (rule 911100):
Use SecAction with setvar before the Include — the initialization rule only sets the default if the variable is unset, so your value takes precedence:
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecAction "id:900200,phase:1,pass,nolog,setvar:tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE"
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
Disable specific rule IDs (after Include):
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
SecRuleRemoveById 941100 942100
Detection-only mode (no blocking):
Used for apps that legitimately generate high-scoring payloads (e.g. Joplin — users write shell command examples in Markdown notes):
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine DetectionOnly
Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
Critical: No Single Quotes in Snippets¶
Silent cluster-wide failure
nginx-ingress wraps the modsecurity-snippet value in modsecurity_rules '...'. A single quote (') inside the snippet terminates the nginx config string prematurely.
This causes nginx to fail to reload — silently blocking all new ingress configurations and certificate updates cluster-wide. The error only appears in the nginx-ingress-controller pod logs, not in the ingress object events.
Always use double quotes inside snippet content.