The rule-writing language is somewhat unusual and can be very difficult to follow and understand. When writing a new custom rule (to keep rules as clear and maintainable as possible), try to follow these best practices:

  • Comment clearly what the rule is doing and why, for future reference
  • Put each rule action on a separate line for clarity (use a \ at the end of the line to escape the newline character)
  • Indent each rule action for clarity
  • Keep the same order of actions across rules to avoid mistakes (1: id, 2: phase, 3: deny/pass, and so on)
  • Write a clear rule 'msg' message to add to the log line when the rule matches
An example rule is as follows:
#
# -- Validate User-Agent Header
#
# The only legitimate access to our API is by IoT devices that we have deployed
# in the field. These will always identify themselves with a user agent that
# looks like:
#     AcmeWidget/1.2.34-r56
# Deny any request that *does not* (note the ! to negate the match) send a
# legitimate-looking user agent. Make the match case insensitive by using a
# 't:lowercase' transformation.
#
SecRule REQUEST_HEADERS:User-Agent "!@beginsWith acmewidget/" \
   "id:1000,\
   phase:1,\
   deny,\
   t:none,t:lowercase,\
   msg:'Legitimate AcmeWidget User-Agent header required',\
   logdata:'%{MATCHED_VAR}'"

The following rule looks at the request Uniform Resource Identifier (URI) and tries to match the regular expression pattern <script> against it. The double quotes are used because the second parameter contains a space:

SecRule REQUEST_URI "@rx <script>"

To split a long line into two, use a single backslash character, followed by a new line:

SecRule ARGS KEYWORD \
phase:1,t:none,block

Multiple variables can be used in a rule as long as they are separated using the pipe character, for example:

SecRule REQUEST_URI|REQUEST_PROTOCOL "@rx <script>"

The SecDefaultAction directive is used if no actions are defined for a rule. For example, the following rule:

SecRule ARGS "@rx D1"

Is equivalent to:

SecRule ARGS "@rx D1" "phase:2,log,auditlog,pass"