A Practical Guide to ModSecurity/Coraza SecRule Syntax (and the mistakes that bite)
If you run a WAF backed by ModSecurity or Coraza (the Go rewrite most modern setups use now — nginx, HAProxy via SPOE, Traefik plugins, etc.), sooner or later you write a custom SecRule by hand. The syntax is powerful and genuinely well-designed once it clicks, but the learning curve is real — most people's first custom rule either silently does nothing or blocks traffic they didn't mean to.…
Writing a custom SecRule for a WAF backed by ModSecurity or Coraza can be a powerful and well-designed task, but it does come with a steep learning curve. Most first-time custom rule writers either have no effect or unintentionally block traffic. This guide breaks down the anatomy of a rule and highlights specific mistakes that frequently trip people up.
A SecRule consists of three key components: what to look at, how to match it, and what to do. The parts you'll frequently use include:
- VARIABLE: REQUEST_URI, ARGS, REQUEST_HEADERS, REQUEST_COOKIES, REQUEST_METHOD, REMOTE_ADDR
- @contains: for substring matching, @streq for exact match, @beginsWith / @endsWith, @rx for regex, @ipMatch for IP/CIDR, and built-in @detectSQLi / @detectXSS heuristics.
Every rule requires a unique ID, with phase:2 indicating that it checks after the request body is parsed. This is typically what you want when inspecting ARGS or REQUEST_BODY.
Multiple SecRule lines chained together function as AND, meaning all conditions must match. The most common error is forgetting to add chain to non-final lines, resulting in two independent rules that don't match as intended.
Allow-listing instead of blocking is another crucial aspect of writing WAF rules. Instead of disabling a rule globally when it makes a mistake, scope the exemption as narrowly as possible. For example:
SecRule REQUEST_URI @beginsWith /editor/save id:1000003,phase:2,pass,nolog,ctl:ruleRemoveTargetById=941320;ARGS:content
This removes just one field from one rule's inspection, ensuring the rest of the request continues to be checked.
Rule ID collisions often occur when using the OWASP Core Rule Set (CRS), which has a reserved ID range of 900000–999999. Pick a custom rule ID above 1000000 to avoid silent collisions when CRS updates.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.
This story
This is one outlet's version. Read the fullest account.