Web Application Firewall

Use this module when you want to inspect requests for common web attacks before they reach your application, without adding a separate WAF appliance or reverse proxy.

When to use this module

  • You want SQL injection and XSS detection at the edge, before traffic reaches your application servers.
  • You need a detection-only mode to evaluate WAF rules before enabling enforcement.
  • You want to write custom security rules using a ModSecurity-like syntax in a plain text file.
  • You need request body inspection for POST, PUT, and PATCH endpoints.
  • You want temporary IP bans or score-based blocking for clients that trigger repeated WAF events.
  • You need visibility into WAF decisions through exported nginx variables.

nginx.conf synthesis

Block SQLi and XSS with body inspection

location /api {
    waf on;
    waf_mode block;
    waf_sqli on;
    waf_xss on;
    waf_check_body on;

    proxy_pass http://backend;
}

Detection-only mode

Log what would be blocked without disrupting traffic.

location /legacy {
    waf on;
    waf_mode detect;

    proxy_pass http://legacy-backend;
}

Custom rules file

Disable the built-in patterns and use your own rule set.

location /api {
    waf on;
    waf_mode block;
    waf_sqli off;
    waf_xss off;
    waf_rules_file /etc/nginz/waf/custom.rules;
}

Example rule file:

SecRule ARGS "@contains union select" "id:1001,phase:1,msg:'SQLi needle'"
SecRule REQUEST_BODY "@contains <script" "id:1002,phase:2,msg:'body XSS needle'"
SecRule REQUEST_HEADERS:User-Agent "@contains bot" "id:1003,phase:1,msg:'blocked user agent'"
SecRule ARGS:attempts "@ge 5" "id:1004,phase:1,msg:'too many attempts'"

Directive reference

waf

  • Contexts: location
  • Default: off

Enables the WAF for this location. When turned on, the module inspects requests using built-in patterns and any custom rules you provide.

waf_mode

  • Contexts: location
  • Default: block

Controls whether the WAF enforces or only observes. block returns a 403 with a JSON error body when an attack is detected. detect logs the detection but lets the request pass through.

waf_sqli and waf_xss

  • Contexts: location
  • Default: on (both)

Toggle SQL injection and cross-site scripting detection independently. The module URL-decodes input, then runs libinjection, and falls back to native substring signatures. Turn off either check if your application uses patterns that trigger false positives.

waf_check_body

  • Contexts: location
  • Default: off

Enables request body inspection for POST, PUT, and PATCH requests. Body inspection is limited to the first 8 KB of content for performance.

waf_rules_file

  • Contexts: location
  • Default: none

Loads a custom rule file at configuration time. The file contains one SecRule per line using a ModSecurity-like syntax. Supported targets include REQUEST_URI, ARGS, QUERY_STRING, REQUEST_BODY, REQUEST_HEADERS, REQUEST_COOKIES, REQUEST_METHOD, REMOTE_ADDR, and more. Operators include @contains, @rx, @pm, @beginsWith, @endsWith, @streq, @ipMatch, @libinjection_sqli, @libinjection_xss, and others.

Each rule can specify actions like id:, phase:, msg:, score:, deny, pass, log, status:, and transformations like t:lowercase and t:urlDecode.

waf_ban_threshold, waf_ban_window, and waf_ban_duration

  • Contexts: location
  • Defaults: 0, 60, 300

Enable temporary IP bans after a threshold of WAF detections. waf_ban_window sets the counting window in seconds. waf_ban_duration sets how long the ban lasts. Repeat offenders receive escalating ban durations.

waf_ban_threshold 5;
waf_ban_window    60;
waf_ban_duration  300;

waf_score_threshold and waf_score_decay_window

  • Contexts: location
  • Defaults: 0, 60

Enable score-based banning. Each WAF detection increments the client’s accumulated score. When the score reaches waf_score_threshold, the client is temporarily banned. The score decays during quiet periods based on waf_score_decay_window.

waf_score_threshold 10;
waf_score_decay_window 120;

Exported variables

VariableDescription
$waf_resultallowed, denied, or dryrun
$waf_rule_idNumeric ID of the matched custom rule (empty for built-in matches)
$waf_scoreCurrent accumulated threat score for the client IP
$waf_categorysqli, xss, ban, rule, or empty

These variables let you log WAF decisions, inject them into upstream headers, or compose them with other modules for unified access policies.

Works well with

  • Stock nginx deny and allow — combine IP-level access control with content-level attack detection.
  • Stock nginx limit_except — restrict HTTP methods alongside WAF body inspection.
  • JWT Authentication for layered API security.
  • OpenID Connect when authenticated applications also need attack detection.
  • nftables IP Policy for kernel-level IP blocking of repeat WAF offenders.
  • Rate Limiting for traffic volume controls alongside content inspection.