Skip to content

About

Coraza, CRS and Apache/Caddy/Nginx

Topics

Resources

Stars

16 stars

Watchers

4 watching

Forks

Latest commit

 

History

199 Commits

Folders and files

Repository files navigation

Coraza CRS Docker

Docker containers running OWASP Coraza WAF with the OWASP Core Rule Set (CRS).

Three web server variants are available:

Variant Base Image Tag
Caddy Alpine caddy-alpine
nginx Debian (nginx-unprivileged) nginx
Apache Debian (httpd) apache

The containers act as a reverse proxy, inspecting traffic with Coraza WAF and CRS before forwarding to a backend service.

Supported Tags

Stable Tags

Stable tags include the CRS version and a timestamp: <CRS version>-<variant>-<date>.

Examples:

  • 4-caddy-alpine-202509051009
  • 4.25-nginx-202509051009
  • 4.25.0-apache-202509051009

Rolling Tags

Rolling tags are updated on every release and always point to the latest build. They should not be used in production.

Examples:

  • caddy-alpine
  • nginx
  • apache

LTS Tags

LTS (Long-Term Support) tags point to a designated LTS release and are updated less frequently than stable tags.

The LTS tag format is <CRS version>-<variant>-lts.

Examples:

  • 4.25-caddy-alpine-lts
  • 4.25.0-nginx-lts
  • 4.25-apache-lts

Quick Start

Using docker compose:

# Caddy (port 8080)
docker compose --profile caddy up

# nginx (port 8081)
docker compose --profile nginx up

# Apache (port 8082)
docker compose --profile apache up

# All three
docker compose --profile all up

Or run directly:

# nginx (non-root, listens on 8080)
docker run -d -p 8080:8080 -e BACKEND=myapp:8080 ghcr.io/coreruleset/coraza-crs:nginx

# Apache
docker run -d -p 80:80 -e BACKEND=myapp:8080 ghcr.io/coreruleset/coraza-crs:apache

# Caddy
docker run -d -p 8080:8080 -e BACKEND=myapp:8080 ghcr.io/coreruleset/coraza-crs:caddy-alpine

Test that the WAF is working (adjust port to match the variant: 8080 for Caddy, 8081 for nginx, 8082 for Apache):

# Normal request — should return 200
curl http://localhost:8081/

# SQL injection — should return 403
curl "http://localhost:8081/?id=1%20AND%201=1"

Env Variables

Coraza Specific

Variable Default Description
CORAZA_RULE_ENGINE On Enable Coraza. Accepted: On, Off, DetectionOnly
CORAZA_REQ_BODY_ACCESS "On" Allow Coraza to access request bodies
CORAZA_REQ_BODY_LIMIT 13107200 Maximum request body size for buffering
CORAZA_REQ_BODY_NOFILES_LIMIT 524288 Maximum request body size excluding files
CORAZA_REQ_BODY_LIMIT_ACTION "Reject" Action on body limit: Reject, ProcessPartial
CORAZA_REQ_BODY_JSON_DEPTH_LIMIT 1024 Maximum JSON depth
CORAZA_ARGUMENTS_LIMIT 1000 Maximum number of arguments
CORAZA_RESP_BODY_ACCESS "On" Allow Coraza to access response bodies
CORAZA_RESP_BODY_LIMIT 1048576 Maximum response body size for buffering
CORAZA_RESP_BODY_LIMIT_ACTION "ProcessPartial" Action on response body limit
CORAZA_RESP_BODY_MIMETYPE "text/plain text/html text/xml" MIME types to inspect
CORAZA_AUDIT_ENGINE "RelevantOnly" Audit engine mode
CORAZA_AUDIT_LOG /dev/stdout Audit log destination
CORAZA_AUDIT_LOG_FORMAT JSON Audit log format: JSON, Native
CORAZA_AUDIT_LOG_PARTS 'ABIJDEFHZ' Which transaction parts to log
CORAZA_AUDIT_LOG_RELEVANT_STATUS "^(?:5|4[0-9][0-35-9])" HTTP status codes relevant for logging
CORAZA_AUDIT_LOG_TYPE Serial Audit log type
CORAZA_AUDIT_STORAGE_DIR /var/log/coraza/audit/ Audit log storage directory
CORAZA_DATA_DIR /tmp/coraza/data Persistent data directory
CORAZA_TMP_DIR /tmp/coraza Temporary files directory
CORAZA_DEBUG_LOG /dev/null Debug log file
CORAZA_DEBUG_LOGLEVEL 1 Debug log level (1-9)

CRS Specific

Variable Default Description
PARANOIA 1 CRS Paranoia Level for logging
ANOMALY_INBOUND 5 Inbound anomaly score threshold
ANOMALY_OUTBOUND 4 Outbound anomaly score threshold
BLOCKING_PARANOIA 1 CRS Paranoia Level for blocking

Web Server Specific

These variables apply to all variants (nginx, Apache, Caddy). Not every setting translates perfectly across servers — when a variable doesn't map to something meaningful, it's ignored on that variant.

Variable Default Description nginx Apache Caddy
BACKEND localhost:80 Backend host:port to proxy to ✅ ✅ ✅
PORT 8080 Listen port ✅ ✅ ✅
SERVER_NAME localhost Server name ✅ ✅ —
SERVER_TOKENS off (nginx/caddy), Prod (apache) Hide server version info ✅ ✅ —
WORKER_CONNECTIONS 1024 Max concurrent connections per worker ✅ ✅ —
KEEPALIVE_TIMEOUT 60s Client keepalive timeout ✅ ✅ ✅
PROXY_TIMEOUT 60s Backend proxy timeout ✅ ✅ ✅
LOGLEVEL warn Server error log level ✅ ✅ ✅
ACCESSLOG /dev/stdout Access log destination ✅ ✅ ✅
ERRORLOG /dev/stderr Error log destination ✅ ✅ —
SSL_PORT 8443 HTTPS listen port ✅ ✅ —
SSL_CERT_FILE (auto-generated) TLS certificate path ✅ ✅ —
SSL_CERT_KEY_FILE (auto-generated) TLS private key path ✅ ✅ —
SSL_PROTOCOLS nginx: TLSv1.2 TLSv1.3, apache: all -SSLv3 -TLSv1 -TLSv1.1 Allowed TLS protocols (syntax differs per server) ✅ ✅ —
SSL_CIPHERS Mozilla intermediate TLS cipher suites ✅ ✅ —
SSL_PREFER_CIPHERS off Prefer server ciphers ✅ — —
SSL_DH_BITS 2048 DH parameter size (2048/4096) ✅ — —
SSL_OCSP_STAPLING off OCSP stapling ✅ — —
SSL_VERIFY off Client certificate verification ✅ — —
SSL_VERIFY_DEPTH 1 Client cert chain depth ✅ — —
ALWAYS_TLS_REDIRECT off Redirect HTTP to HTTPS ✅ ✅ —
HTTP2 on Enable HTTP/2 ✅ — —
H2_PROTOCOLS h2 h2c http/1.1 HTTP/2 protocols — ✅ —
SET_REAL_IP_FROM 127.0.0.1 Trusted proxy for real IP ✅ — —
REAL_IP_HEADER X-Real-IP Header containing real client IP ✅ — —
REAL_IP_RECURSIVE on Recursive real IP lookup ✅ — —
REMOTEIP_HEADER X-Forwarded-For Header containing real client IP — ✅ —
REMOTEIP_INT_PROXY 10.1.0.0/16 Trusted internal proxy range — ✅ —

A self-signed certificate is generated automatically at startup if no certificate is mounted. To use your own certificate, mount it at the SSL_CERT_FILE and SSL_CERT_KEY_FILE paths. Caddy handles TLS automatically via its built-in ACME support.

Caddy-only

Variable Default Description
CORAZA_TAG coraza No longer applied. See below.

CORAZA_TAG used to be interpolated into caddy's CORAZA_DEFAULT_PHASE1_ACTION/CORAZA_DEFAULT_PHASE2_ACTION, which put tag:'coraza' on every phase 1 and phase 2 CRS rule; nginx and Apache never used it. Recent Coraza rejects metadata actions in SecDefaultAction (SecDefaultAction must not contain metadata actions), so the tag had to go and the variable is now inert on all three variants. To tag rules, set the tag action on the rules themselves — for example with an override in /opt/coraza/overrides — or filter logs on the rule ids instead.

Important Notes

  • Caddy runs as a non-root user by default. The cap_net_bind_service capability is added to allow binding on ports < 1024. Default port is 8080.
  • nginx uses nginxinc/nginx-unprivileged (Debian-based, non-root). Apache uses httpd (Debian-based). Both build libcoraza and connector modules from source. Platforms: linux/amd64 and linux/arm64.
  • The audit log defaults to /dev/stdout (visible via docker logs).

Configuration Files/Directories

The following configuration paths are shared across all variants:

Path Description
/opt/coraza/config/coraza.conf Main Coraza WAF configuration
/opt/coraza/config/coraza-rules.conf Rule include chain (nginx/Apache)
/opt/coraza/config/crs-setup.conf OWASP CRS configuration
/opt/coraza/config.d/*.conf User configuration files
/opt/coraza/owasp-crs/rules/*.conf OWASP CRS rule files
/opt/coraza/owasp-crs/plugins/*.conf CRS plugins
/opt/coraza/plugins/*.conf User plugins
/opt/coraza/rules/*.conf Additional built-in rules
/opt/coraza/rules.d/*.conf User defined rule sets
/opt/coraza/overrides/*.conf Rule/configuration overrides

Build Arguments

Variable Default Description
CRS_VERSION 4.25.2 OWASP CRS release version (LTS)
CADDY_VERSION 2.11.6 Caddy Docker tag (caddy variant)
NGINX_VERSION 1.30.6 nginx image version (nginx variant)
HTTPD_VERSION 2.4 httpd image version (apache variant)
LIBCORAZA_VERSION v1.8.0 libcoraza release (nginx/apache variants)
CORAZA_CADDY_VERSION v2.6.2 coraza-caddy release (caddy variant)
CORAZA_NGINX_VERSION 0.23.0 coraza-nginx release (nginx variant)
CORAZA_APACHE_VERSION 0.22.0 coraza-apache release (apache variant)

Building

We require buildx >= v0.9.1. See official documentation.

# See all build targets
docker buildx bake -f ./docker-bake.hcl --print

# Build all targets
docker buildx create --use --platform linux/amd64,linux/arm64
docker buildx bake -f docker-bake.hcl

# Build a single target
docker buildx bake -f docker-bake.hcl --set "*.platform=linux/amd64" nginx

Testing

tests/run-tests.sh starts the image under test as a reverse proxy in front of a traefik/whoami backend and drives it with curl: legitimate traffic must be proxied through, attack payloads must be blocked by CRS, and the documented environment variables must actually change that behaviour. It is the same script the Verify Images workflow runs on every pull request.

# Build the caddy image and test it
./tests/run-tests.sh -v caddy -b

# Test an image that is already available locally
./tests/run-tests.sh -v nginx -i ghcr.io/coreruleset/coraza-crs:nginx

Options:

Option Description
-v <variant> Variant to test: caddy, nginx or apache (default: caddy). Bake target names such as nginx-lts are accepted too.
-i <image> Image to test (default: coraza-crs-test:<variant>).
-b Build the image with docker buildx bake before running the tests.

The script requires docker and curl, publishes containers on ephemeral localhost ports, and removes everything it started on exit. Container logs are dumped when a test fails. Every check runs; the script exits non-zero if any of them failed.

What is covered:

  • The container starts and keeps running.
  • Legitimate requests (query string, urlencoded body, JSON body) are proxied to the backend and answered with the backend's response.
  • Attacks are blocked with 403: XSS and SQL injection in the query string, path traversal, RCE attempt, scanner user agent, SQL injection in a POST body, XSS in a JSON body. A disallowed HTTP method is rejected too (403 from CRS, or 405 when the web server answers it first).
  • CORAZA_RULE_ENGINE=DetectionOnly and CORAZA_RULE_ENGINE=Off let attacks through.
  • ANOMALY_INBOUND is applied to the rule set by activate-rules.sh.
  • Custom rules mounted into /opt/coraza/rules.d are loaded and enforced, including a phase 4 rule on RESPONSE_BODY — a guard against response body inspection silently not running.
  • Audit logging: default log directories, serial audit log written on attack, concurrent audit log files written on attack, and creation of a custom CORAZA_AUDIT_STORAGE_DIR by the entrypoint.

Advanced Configuration

Supplemental Configuration

Add Coraza configuration without overwriting defaults by mounting *.conf files into:

  • /opt/coraza/config.d — additional configuration
  • /opt/coraza/rules.d — additional rule sets

CRS Plugins

Download and mount plugins to /opt/coraza/plugins:

curl -sSL https://github.com/coreruleset/wordpress-rule-exclusions-plugin/archive/refs/tags/v1.0.0.tar.gz -o wordpress.tar.gz
tar xvf wordpress.tar.gz --strip-components 1 'wordpress-rule-exclusions-plugin*/plugins'
docker run -v $(pwd)/plugins:/opt/coraza/plugins ghcr.io/coreruleset/coraza-crs:nginx

Replacement Configuration

To use your own web server configuration, mount it at:

  • Caddy: /config/caddy/Caddyfile
  • nginx: /config/nginx/nginx.conf
  • Apache: /config/apache/coraza.conf

To replace the Coraza configuration entirely, mount at /config/coraza/coraza.conf.

About

Coraza, CRS and Apache/Caddy/Nginx

Topics

Resources

Stars

16 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages