Caddy-Warden: Caddy Security Module
Caddy-Warden (github.com/routewarden/caddy-warden) is the official Caddy v2 security module from RouteWarden. It brings high-performance sensitive path defense, anti-evasion normalization, IP allowlisting, and active deception defenses to Caddy web servers.
Key Capabilities
- Zero-Config Sensitive File Shielding: Blocks
.env,.git,.aws,.ssh,.sql, database dumps, and server manifests out-of-the-box (enable_default_patterns). - Anti-Evasion Engine: Normalizes multiple URL encodings (
%252e%252e), semicolon matrix parameters (/;param/.env), Windows backslashes (\), and null bytes (%00) before pattern matching. - IP & CIDR Allowlisting: Exempt trusted corporate subnets, office IPs, or VPNs (
allowed_ips) using client IP detection or upstream proxy headers (X-Forwarded-For,X-Real-IP). - Multi-Action Defense Engine: Respond with JSON errors, branded HTML, Cloudflare Turnstile/hCaptcha verification challenges, silent TCP resets (
silent_drop), or bot-neutralizing Gzip Bombs (gzip_bomb).
Installation & Building Caddy
Caddy uses xcaddy to compile custom builds with plugins:
Using xcaddy (Recommended)
# Install xcaddy if you haven't alreadygo install github.com/caddyserver/xcaddy/cmd/xcaddy@latest# Build Caddy with caddy-wardenxcaddy build \ --with github.com/routewarden/caddy-warden@v1.2.1Using Dockerfile
# DockerfileFROM caddy:2-builder AS builderRUN xcaddy build \ --with github.com/routewarden/caddy-warden@v1.2.1FROM caddy:2-alpineCOPY --from=builder /usr/bin/caddy /usr/bin/caddyUsing Docker Compose
Run Caddy with RouteWarden alongside your upstream web services using a dedicated Dockerfile:
services: caddy: build: context: . dockerfile: Dockerfile ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config depends_on: - webapp restart: unless-stopped webapp: image: nginx:alpine restart: unless-stoppedvolumes: caddy_data: caddy_config:Configuration
You can define RouteWarden security rules using routewarden.json (Recommended Universal Schema) as your single source of truth, directly in your Caddyfile, or via Caddy's dynamic JSON API.
Option 1: routewarden.json (Recommended Universal Schema)
How routewarden.json Works with Caddy
routewarden.json acts as a portable security policy with IDE autocompletion and CI/CD validation. Its properties correspond directly to Caddy's directive blocks and JSON handler objects:
"methods"➔ Caddyfilemethods GET POST/ JSON"methods": ["GET", "POST"]"allowedIps"➔ Caddyfileallowed_ips .../ JSON"allowed_ips": [...]"pathPatterns"➔ Caddyfilepath_patterns .../ JSON"path_patterns": [...]"response"➔ Caddyfileresponse { mode ... }/ JSON"response": { ... }
{ "$schema": "https://routewarden.github.io/cli/schema.json", "enabled": true, "enableDefaultPatterns": true, "enableDefaultAllowPatterns": true, "methods": ["GET", "POST"], "allowedIps": ["10.0.0.0/8", "192.168.1.0/24"], "response": { "mode": "json", "statusCode": 403, "body": "{\"error\":\"Access Denied\",\"security\":\"RouteWarden Shield\"}" }}Using routewarden.json Directly via Generate Pipeline
If you maintain routewarden.json as your single source of truth across Git repositories or multi-gateway environments, use the RouteWarden CLI (rwarden) to validate rules offline and compile directly into Caddyfile directive blocks during your deployment pipeline:
# 1. Validate schema compliance, regex patterns, and CIDRsrwarden validate --config routewarden.json# 2. Compile directly into Caddyfile directive blockrwarden generate --target caddy --config routewarden.json > CaddyfileDirective Ordering
In Caddy, custom HTTP handler modules must be ordered in the middleware chain. Add order route_warden before basicauth or order route_warden before reverse_proxy inside your Caddyfile global options block:
{ order route_warden before basicauth}Option 2: Caddyfile Syntax
route_warden { enabled <true|false> enable_default_patterns <true|false> enable_default_allow_patterns <true|false> check_query <true|false> path_patterns <regex...> allow_patterns <regex...> allowed_ips <ip_or_cidr...> response { mode <json|html|text|xml|redirect|captcha|silent_drop|gzip_bomb|tarpit|fake_success|ratelimit|proxy|infinite_stream> status_code <int> body <string> redirect_url <url> proxy_url <url> gzip_bomb_mb <int> retry_after_seconds <int> tarpit_delay_ms <int> stream_size_mb <int> captcha { provider <turnstile|hcaptcha|recaptcha> site_key <key> } }}Examples
1. Basic Production Shield (JSON 404)
Shield all sensitive paths and return a sterile JSON 404 response:
{ order route_warden before reverse_proxy}example.com { route_warden { enable_default_patterns true response { mode json status_code 404 body "{\"error\":\"Not Found\"}" } } reverse_proxy localhost:8080}2. IP Whitelisting with Safe Admin Exceptions
Allow corporate VPN (10.0.0.0/8) and office IP (192.168.1.100) to access administrative endpoints while blocking external crawlers:
{ order route_warden before reverse_proxy}app.example.com { route_warden { enable_default_patterns true path_patterns "(?i)^/admin(/.*)?$" "(?i)^/metrics$" allow_patterns "(?i)^/admin/health$" allowed_ips "10.0.0.0/8" "192.168.1.100" response { mode json status_code 403 body "{\"error\":\"Access Denied: Internal Network Only\"}" } } reverse_proxy backend:3000}3. Active Defense: Gzip Bomb Decompression Trap
When automated scrapers scan for .env or WordPress admin endpoints, send an active defense gzip bomb stream that expands ~1000× in client RAM:
{ order route_warden before reverse_proxy}honeypot.example.com { route_warden { enable_default_patterns true path_patterns "(?i)^/wp-login\.php$" "(?i)^/xmlrpc\.php$" response { mode gzip_bomb status_code 200 gzip_bomb_mb 10 } } reverse_proxy backend:80}4. Interactive Captcha Verification
Challenge visitors accessing sensitive URLs using Cloudflare Turnstile:
{ order route_warden before reverse_proxy}portal.example.com { route_warden { path_patterns "(?i)^/portal/sensitive(/.*)?$" response { mode captcha captcha { provider turnstile site_key "0x4AAAAAAAxxyyzz" } } } reverse_proxy backend:8080}JSON Configuration (Caddy Native API)
If you configure Caddy via its native JSON API:
{ "apps": { "http": { "servers": { "srv0": { "listen": [":443"], "routes": [ { "handle": [ { "handler": "route_warden", "enabled": true, "enable_default_patterns": true, "allowed_ips": ["10.0.0.0/8"], "response": { "mode": "json", "status_code": 404, "body": "{\"error\":\"Not Found\"}" } }, { "handler": "reverse_proxy", "upstreams": [ { "dial": "localhost:8080" } ] } ] } ] } } } }}