Skip to content

Caddy Warden FAQ & Troubleshooting ​

Solutions to common Caddy integration edge cases, directive ordering conflicts, Automatic HTTPS redirects, and reverse proxy behaviors.


1. Why is Routewarden not blocking requests when my site uses handle blocks? ​

Symptom ​

You test a blocked sensitive endpoint like curl https://api.example.com/.env, but instead of Routewarden intercepting it, the request slips through to your backend application and returns a backend 404 Not Found (with Via: 1.1 Caddy and a duration around 5-10ms).

Root Cause ​

In Caddy's HTTP architecture, handle and handle_path are routing directives with higher evaluation precedence than standard proxy handlers.

If your Caddyfile defines order routewarden before reverse_proxy, Caddy compiles the internal middleware pipeline in this order:

  1. handle { ... } runs first.
  2. routewarden is placed second (never reached because handle already dispatched to the backend).

Solution ​

{    admin off    # Order Routewarden ahead of routing directives    order routewarden before handle}(routewarden) {    routewarden {        enable_default_allow_patterns true        enable_default_patterns true        response {            mode redirect            redirect_url "https://routewarden.github.io"        }    }}api.example.com {    import routewarden    encode zstd gzip    handle {        reverse_proxy web:8000    }}

TIP

Option 1 (order routewarden before handle) is recommended because it applies globally to all site blocks and lets your downstream site configurations remain clean and concise.


2. Why do I get a 308 Permanent Redirect when curling http://api.example.com/.env? ​

Symptom ​

Running curl http://api.example.com/.env returns 308 Permanent Redirect to https://api.example.com/.env without Routewarden blocking it or redirecting to your configured response target.

Root Cause ​

When a domain name is specified without a scheme in a Caddyfile (e.g. api.example.com { ... }), Caddy enables Automatic HTTPS:

  1. Your actual site configuration (including Routewarden, compression, and proxying) is bound exclusively to HTTPS (port 443).
  2. Caddy automatically starts an internal HTTP-to-HTTPS redirect server on HTTP (port 80).
  3. This port 80 redirect server does not run any of your site's middleware. It immediately returns a 308 Permanent Redirect to https://.

If curl is run without -L (follow redirects), curl terminates at the port 80 redirect and never reaches Routewarden on port 443.

Solution ​

# Follow redirects (-L) and allow self-signed local certs (-k)curl -v -L -k https://api.example.com/.env

3. Why do I get a generic 404 when testing locally with curl https://localhost/.env? ​

Symptom ​

Curling localhost or 127.0.0.1 returns an empty 404 Not Found with Server: Caddy.

Root Cause ​

Caddy uses strict SNI and Host header routing. If your Caddyfile defines api.example.com { ... }, and you execute curl -k https://localhost/.env, curl sends Host: localhost. Because Caddy has no site block matching localhost, Caddy returns a core 404 Not Found before entering any site routes.

Solution ​

# Resolve the domain to 127.0.0.1 while keeping correct Host & SNI headerscurl -v -k --resolve api.example.com:443:127.0.0.1 https://api.example.com/.env

4. How does directive ordering (order) work with Routewarden? ​

In Caddy v2, plugins define where they should execute in the middleware chain. Starting in Caddy Warden v1.6.2, Routewarden registers a default directive order automatically.

When configuring manual orders in the global options block:

Directive OrderWhen to Use
order routewarden before handle(Recommended) When using handle or handle_path blocks in site definitions.
order routewarden before basicauthWhen you want scanner recon and probe requests blocked before presenting an HTTP authentication prompt to attackers.
order routewarden before reverse_proxyWhen sites define reverse_proxy at the top level without wrapping in handle.
{    admin off    # Recommended when site blocks use handle or handle_path    order routewarden before handle}

5. How do I verify Routewarden is compiled into my Caddy container? ​

Verify that the Caddy binary inside your container includes the Routewarden plugin module:

# Inspect compiled Caddy modules inside your running containerdocker exec caddy-container caddy list-modules | grep routewarden# Expected output:# http.handlers.routewarden# http.handlers.route_warden

6. Why do I see a 502 Bad Gateway when using mode silent_drop behind Cloudflare? ​

Root Cause ​

In mode silent_drop, Routewarden hijacks the underlying TCP connection and closes it immediately (conn.Close()) without transmitting an HTTP status line.

If Caddy is behind an edge proxy or CDN (like Cloudflare, AWS ALB, NGINX, or Traefik), the edge proxy interprets the unexpected TCP socket termination as an upstream failure and returns 502 Bad Gateway to the client.

Solution ​

When deploying behind a CDN or reverse proxy, use mode json or mode html with status_code 403 or 404 instead of silent_drop:

routewarden {    enable_default_patterns true    # Safe for Cloudflare, AWS ALB, NGINX: returns 403 Forbidden    response {        mode json        status_code 403        body "{\"error\":\"Forbidden\",\"message\":\"Access denied\"}"    }}

7. How do I define a reusable Routewarden snippet across multiple sites? ​

Define a snippet using parentheses (routewarden) and import it into each site block:

Caddyfile (Multi-Domain Snippet)caddy
# 1. Define reusable Routewarden configuration snippet(routewarden_common) {    routewarden {        enable_default_allow_patterns true        enable_default_patterns true        response {            mode redirect            redirect_url "https://routewarden.github.io"        }    }}# 2. Import into API domainapi.example.com {    route {        import routewarden_common        reverse_proxy app:8000    }}# 3. Import into Dashboard domaindashboard.example.com {    route {        import routewarden_common        reverse_proxy dashboard:3000    }}

Released under the MIT License.