OWASP Coraza Caddy Module provides Web Application Firewall capabilities for Caddy.
OWASP Coraza WAF is 100% compatible with OWASP Coreruleset and Modsecurity syntax.
If you’re looking for installation guidance, tuning, false-positive handling, or deployment best practices for CRS, refer to the official OWASP CRS documentation. https://coreruleset.org/docs/ This repository focuses on Coraza integration and runtime behavior rather than maintaining CRS rule documentation.
go run mage.go -l lists all the available commands:
▶ go run mage.go -l
Targets:
buildCaddy builds the plugin.
buildCaddyLinux builds the plugin with GOOS=linux.
buildExample builds the example deployment.
check runs lint and tests.
coverage runs tests with coverage and race detector enabled.
doc runs godoc, access at http://localhost:6060
e2e runs e2e tests with a built plugin against the example deployment.
format formats code in this repository.
ftw runs CRS regressions tests.
lint verifies code quality.
precommit installs a git hook to run check when committing
reloadExample reload the test environment.
runExample spins up the test environment, access at http://localhost:8080.
teardownExample tears down the test environment.
test runs all tests.coraza_waf {
directives `
Include /path/to/config.conf
SecAction "id:1,pass,log"
`
}Sample usage:
Important: order coraza_waf first must be always included in your Caddyfile for Coraza module to work
{
order coraza_waf first
}
http://127.0.0.1:8080 {
coraza_waf {
directives `
SecAction "id:1,pass,log"
SecRule REQUEST_URI "/test5" "id:2, deny, log, phase:1"
SecRule REQUEST_URI "/test6" "id:4, deny, log, phase:3"
Include file1.conf
Include file2.conf
Include /some/path/*.conf
`
}
reverse_proxy http://192.168.1.15:8080
}Run:
xcaddy build --with github.com/corazawaf/coraza-caddy/v2You may run the test suite by executing:
go run mage.go testYou can load OWASP CRS by passing the field load_owasp_crs and then load the CRS files in the directives as described in the coraza-coreruleset documentation.
The @-prefixed paths are not files on disk. They are served from a ruleset
that is compiled into the binary, and load_owasp_crs is what makes them
resolvable. This has two consequences:
- You do not need to download CRS, mount a volume, or ship rule files alongside
the binary.
xcaddy build --with github.com/corazawaf/coraza-caddy/v2already contains them. - Do not put a filesystem path in front of an
@path.Include /etc/caddy/rules/@owasp_crs/*.confwill not work; the@prefix must start the path.
If you use an @ path without load_owasp_crs, Caddy fails at startup with:
invalid WAF config from string: failed to readfile:
open @coraza.conf-recommended: no such file or directory
Adding load_owasp_crs to the coraza_waf block resolves it.
:8080 {
coraza_waf {
load_owasp_crs
directives `
Include @coraza.conf-recommended
Include @crs-setup.conf.example
Include @owasp_crs/*.conf
SecRuleEngine On
`
}
reverse_proxy httpbin:8081
}You can specify the transaction ID by setting the field tx_id_req_header and then pass the value for that request header.
This is useful when running coraza behind another HTTP server and using the request ID issued by that server.
Important: tx_id_req_header must be set or overwritten only by a trusted upstream proxy, and that proxy must strip any client-provided value for the same header before it reaches coraza. Otherwise, clients can spoof the transaction ID (and any downstream consumers of it). Configure your front-end proxy to remove client-sent values and inject a trusted request ID instead.
:8080 {
coraza_waf {
load_owasp_crs
directives `
Include @coraza.conf-recommended
Include @crs-setup.conf.example
Include @owasp_crs/*.conf
SecRuleEngine On
`
tx_id_req_header "X-Transaction-ID"
}
reverse_proxy httpbin:8081
}Coraza memoizes the expensive parts of rule compilation (regex and aho-corasick pattern compilation) so identical patterns are not recompiled when several WAF instances in the same process share the same rules. This matters most when one Caddy process serves many sites that each load CRS.
Memoization is enabled by default. There is no flag to turn it on. The
coraza.no_memoize build tag exists only to turn it off:
| Build tag | Behaviour |
|---|---|
| (none) | Memoization enabled (default) |
coraza.no_memoize |
Disabled — every pattern compiles fresh |
Older guidance recommends building with -tags memoize_builders. That tag was
how memoization was opted into up to Coraza v3.4.0, when the default was off. It
was removed in v3.5.0 once memoization became the default. Go ignores unknown
build tags silently, so passing it today neither fails nor does anything — drop
it from your build.
The cache is global to the process, so entries are released when a WAF is
destroyed. This module already does that: the pooled WAF is closed through
caddy.Destructor when the last reference is released, which is what lets
memory be reclaimed across config reloads.
go run mage.go buildExample runExample
curl -i localhost:8080/# in terminal 1
go run github.com/mccutchen/go-httpbin/v2/cmd/go-httpbin@v2.9.0 -port 8081
# in terminal 2
go run mage.go buildCaddy
./build/caddy run --config example/Caddyfile --adapter caddyfile
# in terminal 3
curl -i localhost:8080/In order to respond with a custom message or HTML page, you can take advantage of handle_errors directive:
handle_errors 403 {
header X-Blocked "true"
respond "Your request was blocked. Request ID: {http.request.header.x-request-id}"
}or
handle_errors {
@block_codes `{err.status_code} in [403]`
handle @block_codes {
root * /path/to/html/dir
rewrite * /{err.status_code}.html
file_server
}
}It is possible to use the templates directive to render data dynamically. Take a look at example/403.html file.