Developmi/caddy-waf

Hardened Caddy + Coraza WAF container with OWASP CRS. Non-root execution, automatic TLS, rate limiting, Cosign-signed images, and SLSA provenance

โ˜… 1Forks 0ShellGitHub โ†—Compare
application-securityauto-tlsbare-metalcaddycaddy-servercontainer-securitycorazacoraza-wafcosigndevsecopsdockerowasp-crsowasp-top-10rate-limitingreverse-proxysecurity-hardeningsefl-hostedslsawafzero-trust

README

Caddy WAF logo

Caddy WAF | Developmi

Hardened Caddy web server distribution with Coraza WAF and OWASP CRS v4 - tested with 122 integration cases, signed supply chain (Cosign/SLSA), and safe DetectionOnly staged rollout.

Tech Docker CI Supply Chain Status License OpenSSF Best Practices Maintainer

Curated by Miguel Lozano โ€ข GitHub โ€ข Container Registry


Table of contents


๐ŸŽฏ Overview

Problem: Deploying a web application firewall typically requires weeks of tuning, dedicated appliances, and specialized security expertise. Most WAF solutions block legitimate traffic on day one, disrupting your users and forcing you to disable protections you just deployed.

This project solves that. It packages Caddy - the web server with automatic TLS - with Coraza WAF and the OWASP Core Rule Set into a single hardened container. The WAF defaults to DetectionOnly mode, giving you a safe observation window before enforcement. It comes preconfigured with OWASP CRS v4.29.0 covering SQL injection, XSS, command injection, and OWASP Top 10 vectors across 12 rule families - allowing you to baseline legitimate application traffic and tune exclusions during observation before enforcing active blocking.

Starting from v3.0.0, the image ships with updated Caddy 2.11.4, official upstream rate limiting and DNS plugins, and native security header support - giving the project full control over maintenance cadence, security patches, and feature development.

โœจ Features

๐Ÿ”’ Security First

  • Non-root execution: Runs as caddy user (UID 1337) - no root privileges
  • Supply chain security: Pinned versions, SHA256 verification of OWASP CRS rules, Cosign-signed images, SBOM attestations
  • Multi-stage builds: Minimal attack surface, optimized layers
  • Health monitoring: Process verification healthcheck at both image and compose level
  • Structured logging: JSON logs for SIEM integration

๐Ÿ›ก๏ธ WAF Capabilities

  • Coraza WAF v2.6.1: Modern, high-performance web application firewall engine
  • OWASP CRS v4.29.0: Core Rule Set covering SQLi, XSS, RCE, and protocol violations across 12 rule families
  • DetectionOnly by default: Mitigates false-positive operational risk during initial rollout
  • Audit logging: JSON audit logs to stdout for easy monitoring
  • Rate limiting: Built-in rate limiting via mholt/caddy-ratelimit
  • Security headers: Automated security header injection via Caddy's native header directive

๐Ÿš€ Production Ready

  • Optimized Alpine base: Small footprint (~45MB compressed)
  • TLS by default: Automatic Let's Encrypt integration
  • Multi-architecture: Supports linux/amd64 and linux/arm64
  • Cloud-native: Perfect for Kubernetes, Docker Swarm, and standalone Docker
  • Bare-metal ready: Systemd service file included for non-containerized deployments

โšก Quick Start

Prerequisites

  • Docker 24.x+ and Docker Compose v2.x+

1. Pull the Image

docker pull ghcr.io/developmi/caddy-waf:v3.5.5

2. Create Environment File

cp .env.example .env
# Edit .env with your domain/backend/image values

3. Create Runtime Caddyfile From Template

cp Caddyfile.example Caddyfile
# Edit Caddyfile for your domain and upstreams

4. Build Your Custom Image (Recommended for your own distribution)

docker build -t your-registry/your-caddy-waf:custom \
  --build-arg CORAZA_CADDY_REF=v2.6.1 \
  --build-arg CADDY_RATELIMIT_REF=5625512 \
  --build-arg CADDY_DNS_CLOUDFLARE_REF=v0.2.4 \
  .

Then set CADDY_WAF_IMAGE=your-registry/your-caddy-waf:custom in .env.

5. Basic Caddyfile Configuration

{
    order coraza_waf first
}

yourdomain.com {
    respond "Caddy with Coraza WAF is running" 200
}

6. Start the Container

docker compose up -d

๐Ÿ—๏ธ Architecture

caddy-waf/
โ”œโ”€โ”€ assets/                   # Brand assets (logo)
โ”œโ”€โ”€ deploy/
โ”‚   โ””โ”€โ”€ systemd/              # Systemd service unit for bare-metal
โ”œโ”€โ”€ docs/                     # Operations docs (deployment, IR, compliance)
โ”œโ”€โ”€ .github/workflows/        # CI/CD (build, scan, sign, push)
โ”œโ”€โ”€ metrics/                  # Backend-agnostic Prometheus scrape config
โ”œโ”€โ”€ grafana/                  # Provisioned datasources + dashboards
โ”œโ”€โ”€ Dockerfile                # Multi-stage build with pinned plugins
โ”œโ”€โ”€ docker-compose.yml        # Production-grade compose with security hardening
โ”œโ”€โ”€ Caddyfile                 # Runtime configuration - untracked, generated from Caddyfile.example (WAF + TLS + reverse proxy)
โ”œโ”€โ”€ Caddyfile.example         # Templated configuration with 5 deployment examples
โ”œโ”€โ”€ .env.example              # Environment variable template (3 groups)
โ”œโ”€โ”€ TUNING.md                 # WAF tuning guide per application type
โ”œโ”€โ”€ ROADMAP.md                # Planned enhancements and compliance roadmap
โ”œโ”€โ”€ CHANGELOG.md              # Version history (Keep a Changelog)
โ”œโ”€โ”€ CONTRIBUTING.md           # Contribution guidelines
โ”œโ”€โ”€ SECURITY.md               # Vulnerability disclosure policy
โ””โ”€โ”€ LICENSE                   # MIT License

Data flow

flowchart LR
    Client[Client] -->|HTTPS :443| Caddy[Caddy v2.11.4]
    Caddy -->|WAF layer| Coraza[Coraza WAF v2.6.1]
    Coraza -->|OWASP CRS v4.29.0| Rules[CRS v4 Rules]
    Coraza -->|Decision| Action{Allow?}
    Action -->|Yes| Backend[Upstream Backend]
    Action -->|No| Block[Block + Audit Log]
    Block -->|JSON| SIEM[SIEM / Log Aggregator]
    Caddy -->|Auto TLS| LE[Let's Encrypt]
Loading

๐Ÿ“– Configuration Guide

WAF Modes

The WAF operates in three modes (configured in Caddyfile):

  1. DetectionOnly (Default): Logs attacks without blocking - perfect for initial deployment
  2. On: Active protection - blocks malicious requests
  3. Off: Disables WAF completely

Recommended rollout: Keep SecRuleEngine DetectionOnly for a 7โ€“14 day observation window. Review audit logs, tune CRS exclusions, then switch to SecRuleEngine On only after establishing a stable false-positive baseline.

Example Caddyfile with WAF

{
    email [email protected]
    order coraza_waf first

    # JSON logging for observability
    log {
        output stdout
        format json
    }
}

(waf) {
    coraza_waf {
        directives `
            Include /etc/caddy/coraza.conf
            Include /etc/caddy/owasp-crs/crs-setup.conf
            Include /etc/caddy/owasp-crs/rules/*.conf

            # Start with DetectionOnly, change to On after tuning
            SecRuleEngine DetectionOnly

            # Audit logging
            SecAuditEngine RelevantOnly
            SecAuditLog /dev/stdout
            SecAuditLogFormat JSON
        `
    }
}

# Your site configuration
example.com {
    import waf
    reverse_proxy backend:8080
}

Advanced Configuration

For detailed WAF tuning, rule exceptions, and performance optimization, see the complete TUNING GUIDE.

Project roadmap and planned security integrations are tracked in ROADMAP.md.

Using Custom OWASP CRS Rules

Mount your custom rules directory:

volumes:
  - ./custom-crs:/etc/caddy/owasp-crs

Environment Variables

Variable Default Description
CADDY_WAF_IMAGE ghcr.io/developmi/caddy-waf:v3.5.5 Caddy WAF image reference
EXAMPLE_APP_IMAGE containous/whoami:latest Demo backend image
SITE_ADDRESS localhost Site address/server name used by Caddy
BACKEND_UPSTREAM example-app:80 Reverse proxy backend upstream
ACME_EMAIL (empty) Email for Let's Encrypt certificates

Plugins Included

Note: caddy-ratelimit development is active but releases are not tagged beyond v0.1.0. This image pins the module by commit SHA (5625512) to include post-tag fixes. See Dockerfile for the pinned reference.

Security headers are handled via Caddy's native header directive - no external plugin needed.


๐Ÿณ Docker Deployment

Default compose stack

# Start with example backend
cp .env.example .env
cp Caddyfile.example Caddyfile
docker compose up -d

Build from source

docker build \
  --build-arg CORAZA_CADDY_REF=v2.6.1 \
  --build-arg CADDY_RATELIMIT_REF=5625512 \
  --build-arg CADDY_DNS_CLOUDFLARE_REF=v0.2.4 \
  -t caddy-waf:custom .

Systemd deployment (bare-metal)

The unit runs on the host network namespace, so it uses the zero-trust config deploy/systemd/Caddyfile.systemd - the admin API is bound to loopback only (admin localhost:2019). Never use 0.0.0.0:2019 on a host network: the admin API accepts config POSTs.

Prerequisite: caddy binary with Coraza and /etc/caddy/coraza.conf + /etc/caddy/owasp-crs installed on the host (see pre-flight checklist in docs/deployment-checklist.md).

sudo cp deploy/systemd/caddy-waf.service /etc/systemd/system/
sudo useradd -r -s /usr/sbin/nologin caddy-waf
sudo install -D -o caddy-waf -g caddy-waf -m 0640 deploy/systemd/Caddyfile.systemd /etc/caddy/Caddyfile
sudo -u caddy-waf caddy validate --config /etc/caddy/Caddyfile
sudo systemctl daemon-reload
sudo systemctl enable --now caddy-waf

Supply chain verification

Verify the image signature before pulling in production:

cosign verify \
  --certificate-identity "https://github.com/developmi/caddy-waf/.github/workflows/docker-build-scan-sign.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/developmi/caddy-waf@sha256:<digest>

Tip: Use immutable image digests (@sha256:...) instead of version tags in production for deterministic deployments.


๐Ÿงช Testing & Validation

Verify Installation

# Check container health
docker ps --filter "name=caddy-waf"

# View logs
docker logs caddy-waf

# Test WAF is working
curl -I https://yourdomain.com

Security Scanning

# Scan image with Trivy
docker run --rm aquasec/trivy image ghcr.io/developmi/caddy-waf:v3.5.5

# Scan with Docker Scout
docker scout quickview ghcr.io/developmi/caddy-waf:v3.5.5

Integration Tests (go-ftw & Perimeter Hardening)

The WAF and perimeter defenses are rigorously verified using the go-ftw framework and automated HTTP assertion gates across 122 automated integration tests structured under Screaming Architecture:

# General test suite (runs all linters + 122 WAF tests + live security headers + bare-boot regression)
make test

# Granular suites (run in milliseconds for fast feedback loops)
make test-waf        # Runs all 122 integration tests + live security header verification
make test-baseline   # Runs 00-baseline (12 tests: clean traffic + false-positive resilience)
make test-evasion    # Runs 01-evasion (6 tests: double-encoding, fullwidth, bypass vectors)
make test-hardening  # Runs 02-hardening (10 tests: .env, .git, .aws, Docker, .sql, .bak + headers)
make test-crs        # Runs crs/ (94 tests across all 12 OWASP CRS v4.29.0 rule families)
make test-suite SUITE=crs/942-attack-sqli  # Target any specific family or subdirectory
make test-boot       # Runs bare-boot regression gate (proves baked container boots as UID 1337)

Suite Coverage Matrix (122 Tests)

Suite Domain Tests Focus & Attack Vectors Covered
00-baseline 12 Container health, clean GET/POST, and false-positive immunity against complex JSON, GraphQL, UTF-8 Spanish accents, Markdown, JWT Bearer tokens, and UUIDs.
01-evasion 6 Anti-evasion against double URL encoding, fullwidth unicode scripts, header-based payload splitting, and body obfuscation.
02-hardening 10 Perimeter defense-in-depth: native blocking of .env, .git, .aws/credentials, .docker/config.json, .htpasswd, .sql dumps, .bak, and .conf (active even in DetectionOnly mode). Automated verification of Server and X-Powered-By banner suppression + required security headers (Permissions-Policy, HSTS, CSP/Cross-Domain).
crs/911-method 4 HTTP method enforcement (blocks TRACE, CONNECT, WebDAV arbitrary methods).
crs/913-scanner 6 Security scanner detection (Nikto, sqlmap, Havij, Acunetix, Arachni).
crs/920-protocol 6 HTTP protocol enforcement (missing Host, missing Accept, Range anomalies, URL limits).
crs/921-attack-proto 4 Protocol-level attacks (HTTP Request Smuggling, response splitting, CRLF).
crs/922-multipart 4 Multipart form/file upload validation (boundary evasion, header corruption).
crs/930-attack-lfi 8 Local File Inclusion (dot-dot-slash traversal, absolute path probes, OS files).
crs/931-attack-rfi 4 Remote File Inclusion (PHP wrappers, data://, SMB UNC paths, off-site inclusions).
crs/932-attack-rce 10 Remote Code Execution & Unix/Windows shell injection (pipes, command chaining, backticks).
crs/933-attack-php 6 PHP injection attacks (php://input, system(), eval(), open tags).
crs/934-attack-generic 10 Application generic attacks: Cloud metadata SSRF (AWS IMDSv1, GCP, Kube API), Prototype Pollution (__proto__, constructor.prototype), SSTI ({{...}}), and Node.js/JS execution (require('child_process'), eval(), fs.readFileSync(), ReDoS).
crs/941-attack-xss 10 Cross-Site Scripting (HTML tags, SVG vectors, event handlers, JavaScript URIs).
crs/942-attack-sqli 12 SQL Injection (tautologies, UNION SELECT, blind boolean/sleep, comment syntax, MSSQL xp_cmdshell).
crs/943-session-fix 4 Session fixation (session ID in URL parameters, Set-Cookie manipulation).
crs/944-attack-java 6 Java attacks (Log4Shell JNDI injection, Remote Method Invocation, serialized payloads).

๐Ÿ“ˆ Monitoring & Observability

Log Structure

{
  "level": "info",
  "ts": 1678901234.567,
  "logger": "http.log.access",
  "msg": "handled request",
  "request": {
    "method": "GET",
    "uri": "/test",
    "proto": "HTTP/2",
    "remote_ip": "192.168.1.100"
  },
  "waf_action": "detected",
  "waf_rule_id": "941100"
}

WAF Metrics to Monitor

The Caddy admin endpoint (:2019/metrics) exposes real Prometheus metrics:

  • caddy_http_requests_total - Total requests (labels: server, handler, method)
  • caddy_http_request_duration_seconds - Request latency histogram (labels include code)
  • caddy_http_requests_in_flight - Concurrent requests gauge
  • caddy_reverse_proxy_upstreams_healthy - Backend upstream health (0/1)
  • caddy_config_last_reload_successful - Config reload status

Note: coraza-caddy (up to v2.6.1) does not export coraza_waf_* metrics (upstream limitation tracked in coraza-caddy#82). WAF rule IDs and decisions are available in the JSON audit log on stdout, not on /metrics.


๐Ÿ“Š Observability (metrics + dashboards)

Dual-backend observability: the same metrics/prometheus.yml scrape config works with both VictoriaMetrics and Prometheus (identical Prometheus exposition format), so Caddy's /metrics output needs no changes. Pick one profile:

# VictoriaMetrics + Grafana (Grafana on http://localhost:3000)
docker compose --profile observability-vm up -d

# Prometheus + Grafana (Grafana on http://localhost:3001)
docker compose --profile observability-prom up -d

Profile services are not started by plain docker compose up - default behavior is unchanged. Grafana is bound to loopback only (127.0.0.1), with anonymous read access so the dashboard opens without login (admin UI: admin / admin, override with GRAFANA_ADMIN_USER / GRAFANA_ADMIN_PASSWORD).

How it works

  • metrics/prometheus.yml - single canonical scrape config, mounted read-only: VictoriaMetrics consumes it via -promscrape.config, Prometheus via --config.file. Targets the internal caddy-waf:2019.
  • Caddyfile.example exposes the admin endpoint (/metrics) on 0.0.0.0:2019 and enables caddy_http_* metric collection.
  • Each profile provisions its own Grafana datasource (VictoriaMetrics or Prometheus) pointing at the backend, and loads the same dashboard (grafana/dashboards/caddy-waf.json): request rate, status codes, p95 latency, in-flight requests, backend health, throughput, request errors, plus a WAF mode note.

Security

Port 2019 (Caddy admin API) is never published to the host - the admin API can accept config POSTs, so it stays strictly inside the internal Docker network. Scraping happens over the caddy-network bridge only. Backends (VictoriaMetrics:8428, Prometheus:9090) are also internal-only.

Tear down

docker compose --profile observability-vm down -v
docker compose --profile observability-prom down -v

See TUNING.md for metric details.


๐Ÿงญ Operations Documentation

Operational runbooks live in docs/:

Document Purpose
Deployment checklist Step-by-step deploy, verify, and rollback for Docker Compose and systemd (bare-metal)
Incident response Runbook for WAF false positives and generic incidents, with severity table
SOC 2 mappings Trust Services Criteria mapped to implemented controls, with file:line evidence and honest gaps
SLSA compliance Supply-chain level assessment of the build pipeline (current: L2, partial L3)

๐Ÿ”’ Security

This project follows a coordinated disclosure policy. If you discover a vulnerability, do not open a public issue. See SECURITY.md for:

  • Supported versions
  • Reporting instructions (GitHub Advisory + email)
  • Response timelines (48h acknowledgment, 30-day fix target)
  • Supply chain verification (Cosign + Trivy)

Supported versions

Version Supported
3.5.x โœ… Yes (current)
2.0.x โŒ No
1.0.x โŒ No

๐Ÿ“‹ Security Advisory

Security advisories and resolved CVEs are documented in SECURITY.md.


๐Ÿ“‹ Changelog

See CHANGELOG.md for the full version history. The project follows Keep a Changelog and Semantic Versioning.

Version Date Highlights
3.5.5 2026-09-23 122-test integration matrix (12 CRS families), granular test runners, native Caddy perimeter hardening & banner suppression, CI test gate
3.5.4 2026-09-21 Coraza WAF 2.6.1, grpc v1.83.2, CVE-2026-84304 & CVE-2026-84445 resolved, empty .trivyignore
3.5.2 2026-09-09 coraza-caddy v2.6.0 (WebSocket+WAF fixes, grpc v1.82.1), digest-pinned base, CI/CD hardening
3.4.0 2026-08-24 OWASP CRS 4.29.0, Trivy v0.74.0, 20-case integration suite (OWASP Top 10 2025), Actions bumps
3.3.2 2026-08-14 WAF default active (DetectionOnly), dual-arch scanning, boot regression gate, systemd variant tracked
3.3.1 2026-08-11 HEALTHCHECK via admin /metrics (curl), resource limits (512m/1 CPU), JSON-file log rotation, version alignment
3.3.0 2026-08-11 Tool bumps (hadolint 2.15.1, go-ftw 2.5.0, Trivy v0.73.0), full apk upgrade, HEALTHCHECK JSON, version alignment
3.0.0 2026-07-01 Caddy 2.11.4 upgrade, Official upstream plugins, CVE fixes, security headers plugin
2.0.0 2026-03-14 Security hardening, systemd, OCI labels, CI updates
1.0.0 2026-02-09 Initial release with Coraza WAF + OWASP CRS

๐Ÿค Contributing

Contributions are welcome. Please read CONTRIBUTING.md before opening a pull request. This project follows Conventional Commits and the Developmi engineering standard.

Quick links

Commercial Support

For commercial support, custom configurations, or security consulting:


๐Ÿ“„ License

Copyright ยฉ 2026 Miguel Lozano | Developmi. All rights reserved. Licensed under the MIT License.

๐Ÿ™ Acknowledgments


๐Ÿค Contact & Support

Maintained by: Miguel Lozano | Developmi

  • Role: Cloud & Infrastructure Engineer | FinOps & Bare Metal Specialist | AI Sovereignty Strategist under NIST/DORA Standards
  • Philosophy: Security is not a feature; it is the baseline.
  • Website: developmi.com
  • GitHub: developmi
  • LinkedIn: Miguel Lozano

ยฉ 2026 Miguel Lozano | Developmi. All rights reserved.

Contributors

Miguel-DevOpsdependabot[bot]

Issues