Skip to content

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.