Target stack: Ubuntu 24.04 LTS, Coraza WAF via the Caddy web server (coraza-caddy plugin) with the OWASP Core Rule Set (CRS).
Coraza is a Go library that implements a ModSecurity-compatible Web Application Firewall engine. It has no standalone daemon of its own — it's embedded into a proxy. The most straightforward production path is Caddy, since the coraza-caddy plugin is actively maintained and Caddy handles TLS automatically. (If you already run Nginx or HAProxy, see the "Alternative integrations" section at the end.)
1. Prerequisites
- A RamNode VPS running Ubuntu 24.04 LTS, with a sudo user configured
- A domain name pointed at the VPS's public IP (A/AAAA record)
- Ports 80 and 443 open
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git ufwSet up basic firewall rules:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable2. Install Go (build dependency)
Caddy with custom plugins is built using xcaddy, which needs Go 1.22+.
cd /tmp
curl -LO https://go.dev/dl/go1.22.6.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.22.6.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> ~/.bashrc
source ~/.bashrc
go version3. Install xcaddy and build Caddy with Coraza
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build \
--with github.com/corazawaf/coraza-caddy/v2
sudo mv caddy /usr/local/bin/caddy
sudo setcap cap_net_bind_service=+ep /usr/local/bin/caddy
caddy versionThis produces a single Caddy binary with the Coraza module compiled in.
4. Create a caddy system user and directories
sudo groupadd --system caddy
sudo useradd --system \
--gid caddy \
--create-home \
--home-dir /var/lib/caddy \
--shell /usr/sbin/nologin \
caddy
sudo mkdir -p /etc/caddy /etc/coraza
sudo chown -R caddy:caddy /var/lib/caddy5. Fetch the OWASP Core Rule Set
cd /etc/coraza
sudo git clone --depth 1 https://github.com/corazawaf/coraza-coreruleset.git crs
sudo cp crs/rules/*.conf-example crs/rules/ 2>/dev/null || trueCoraza's own base config lives in the coraza-coreruleset repo's crs-setup.conf.example file — copy it and adjust as needed:
sudo cp crs/crs-setup.conf.example /etc/coraza/crs-setup.conf6. Write the Caddyfile
sudo tee /etc/caddy/Caddyfile > /dev/null <<'EOF'
example.com {
route {
coraza_waf {
load_owasp_crs
directives `
Include @coraza.conf-recommended
Include @crs-setup.conf.example
Include @owasp_crs/*.conf
SecRuleEngine On
`
}
reverse_proxy localhost:8080
}
}
EOFReplace example.com with your domain and localhost:8080 with your actual backend application. Caddy will automatically obtain and renew a Let's Encrypt certificate for the domain.
SecRuleEngine Onblocks matching requests. UseDetectionOnlyfor a "log but don't block" trial period while you tune false positives.
7. Create a systemd service
sudo tee /etc/systemd/system/caddy.service > /dev/null <<'EOF'
[Unit]
Description=Caddy with Coraza WAF
After=network.target
[Service]
User=caddy
Group=caddy
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
sudo chown -R caddy:caddy /etc/caddy /etc/coraza
sudo systemctl daemon-reload
sudo systemctl enable --now caddy
sudo systemctl status caddy8. Verify
curl -I https://example.com
# Trigger a rule (should be blocked/logged if CRS is active):
curl "https://example.com/?id=1' OR '1'='1"Check the log for a blocked request:
sudo journalctl -u caddy -f9. Tuning false positives
- Run with
SecRuleEngine DetectionOnlyfor a few days under real traffic. - Watch logs for
id:XXXXXXrule hits against legitimate requests. - Add exclusions in a rules file loaded after the CRS, e.g.:
SecRuleUpdateTargetById 942100 "!ARGS:some_free_text_field"- Only switch to
Ononce the noise is manageable.
10. Updating rules
cd /etc/coraza/crs
sudo git pull
sudo systemctl reload caddyAlternative integrations
- Nginx: Coraza has no official Nginx module; the community path is running Coraza behind Nginx as an upstream via the standalone
coraza-caddy/Envoy setups above, or usingmodsecurity-nginx(actual ModSecurity, not Coraza) if you specifically need Nginx-native integration. - Envoy: use the
coraza-proxy-wasmfilter, compiled as a Wasm plugin and loaded via Envoy'shttp_filters. - HAProxy: use
coraza-spoa, a standalone SPOE agent that HAProxy talks to over a Unix socket — install it as its own systemd service and add an SPOE filter block tohaproxy.cfg.
If your stack is Nginx or HAProxy rather than Caddy, let me know and I can write a guide tailored to that.
