Skip to content

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:

# 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.1

Using 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/caddy

Using 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.

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" ➔ Caddyfile methods GET POST / JSON "methods": ["GET", "POST"]
  • "allowedIps" ➔ Caddyfile allowed_ips ... / JSON "allowed_ips": [...]
  • "pathPatterns" ➔ Caddyfile path_patterns ... / JSON "path_patterns": [...]
  • "response" ➔ Caddyfile response { 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 > Caddyfile

Directive 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" }                  ]                }              ]            }          ]        }      }    }  }}

Released under the MIT License.