Get the FREE Ultimate OpenClaw Setup Guide →

caddy-reverse-proxy

npx machina-cli add skill ddnetters/homelab-agent-skills/caddy-reverse-proxy --openclaw
Files (1)
SKILL.md
5.6 KB

Caddy Reverse Proxy

Caddy is a modern web server with automatic HTTPS. This skill covers Caddyfile configuration, reverse proxy patterns, and Docker deployment for homelabs.

Caddyfile basics

# Reverse proxy to a backend service
app.example.com {
    reverse_proxy localhost:8080
}

# Multiple domains, same backend
app1.example.com, app2.example.com {
    reverse_proxy backend:3000
}

# Wildcard with on-demand TLS
*.example.com {
    tls {
        on_demand
    }
    reverse_proxy localhost:8080
}

Reverse proxy patterns

Basic proxy

service.example.com {
    reverse_proxy service:8080
}

With path stripping

example.com {
    handle /api/* {
        reverse_proxy api-server:3000
    }
    handle {
        reverse_proxy frontend:80
    }
}

WebSocket support (automatic)

Caddy handles WebSocket upgrades automatically. No extra config needed.

Load balancing

app.example.com {
    reverse_proxy node1:8080 node2:8080 node3:8080 {
        lb_policy round_robin
        health_uri /health
        health_interval 10s
    }
}

Headers

service.example.com {
    reverse_proxy backend:8080 {
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }
}

TLS / HTTPS

Automatic (Let's Encrypt)

Caddy gets certificates automatically. Just use a domain name:

app.example.com {
    reverse_proxy localhost:8080
}

Cloudflare DNS challenge

For wildcard certs or when port 80/443 isn't publicly reachable. Requires the caddy-cloudflare build.

*.example.com {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }
    @app1 host app1.example.com
    handle @app1 {
        reverse_proxy app1:8080
    }
}

Internal / LAN-only (no public TLS)

:80 {
    reverse_proxy localhost:8080
}

# Or with self-signed cert
service.lan {
    tls internal
    reverse_proxy localhost:8080
}

Restrict to LAN only

service.example.com {
    @denied not remote_ip 192.168.0.0/16 10.0.0.0/8 172.16.0.0/12
    respond @denied 403

    reverse_proxy backend:8080
}

Common homelab patterns

Catch-all / landing page

*.example.com {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }

    # Named matchers for each service
    @grafana host grafana.example.com
    handle @grafana {
        reverse_proxy grafana:3000
    }

    @n8n host n8n.example.com
    handle @n8n {
        reverse_proxy n8n:5678
    }

    # Fallback for unmatched subdomains
    handle {
        respond "Nothing here" 404
    }
}

Basic auth

admin.example.com {
    basicauth {
        # Generate hash: caddy hash-password
        admin $2a$14$HASHED_PASSWORD_HERE
    }
    reverse_proxy backend:8080
}

File server

files.example.com {
    file_server browse {
        root /srv/files
    }
}

Redirect HTTP to HTTPS (automatic)

Caddy does this by default. To redirect a domain:

old.example.com {
    redir https://new.example.com{uri} permanent
}

Docker deployment

docker-compose.yml

services:
  caddy:
    image: caddy:2-alpine
    # Or for Cloudflare DNS: caddybuilds/caddy-cloudflare
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    environment:
      - CLOUDFLARE_API_TOKEN=your-token  # if using DNS challenge
    restart: unless-stopped

volumes:
  caddy_data:
  caddy_config:

Connecting to other Docker services

Services on the same Docker network are reachable by container name:

app.example.com {
    reverse_proxy container-name:8080
}

For services on different networks, use the host IP or join networks:

services:
  caddy:
    networks:
      - frontend
      - backend

Management commands

# Validate config
caddy validate --config /etc/caddy/Caddyfile

# Reload without downtime (inside container)
docker exec <caddy-container> caddy reload --config /etc/caddy/Caddyfile

# Adapt Caddyfile to JSON (debugging)
caddy adapt --config /etc/caddy/Caddyfile

# View loaded config
caddy config

# Hash a password for basicauth
caddy hash-password

Environment variables

Use {env.VAR_NAME} in Caddyfile:

service.example.com {
    reverse_proxy {env.BACKEND_HOST}:{env.BACKEND_PORT}
}

Logging

service.example.com {
    log {
        output file /var/log/caddy/access.log
        format json
        level INFO
    }
    reverse_proxy backend:8080
}

Troubleshooting

ProblemFix
Certificate not issuingCheck DNS points to Caddy, ports 80/443 open, or use DNS challenge
502 Bad GatewayBackend not running or wrong hostname/port. Check docker network ls
Config not reloadingUse caddy reload, not restart (avoids downtime). Check caddy validate first
Wildcard cert failingRequires DNS challenge plugin (e.g. caddy-cloudflare), not default image
WebSocket disconnectingUsually a proxy timeout. Add transport http { keepalive 30s }
LAN service reachable externallyAdd remote_ip matcher to restrict access

Caddy vs nginx quick reference

nginxCaddy
proxy_pass http://backend;reverse_proxy backend:8080
ssl_certificate /path/cert.pem;Automatic (just use domain name)
location /api { ... }handle /api/* { ... }
nginx -t && nginx -s reloadcaddy validate && caddy reload
Separate config for HTTPS redirectAutomatic

Source

git clone https://github.com/ddnetters/homelab-agent-skills/blob/main/caddy-reverse-proxy/SKILL.mdView on GitHub

Overview

Caddy Reverse Proxy lets you map public domains to internal services using straightforward Caddyfile blocks. It adds automatic TLS, supports multiple reverse_proxy patterns, and integrates cleanly with Docker deployments for homelabs.

How This Skill Works

Define site blocks in a Caddyfile to declare hostnames and reverse_proxy targets. Caddy automatically handles TLS, WebSocket upgrades, and optional patterns like path stripping, load balancing, and header rewriting. For Docker-based setups, containers on the same network can be addressed by their service names, simplifying inter-service routing.

When to Use It

  • Expose multiple internal services under distinct subdomains
  • Add wildcard subdomains with on-demand TLS for public exposure
  • Route specific paths to backends using handle blocks
  • Proxy to Docker services by container name on the same network
  • Operate in LAN-only mode with internal TLS or no public TLS

Quick Start

  1. Step 1: Write a Caddyfile snippet mapping domains to backends
  2. Step 2: Deploy Caddy (e.g., docker-compose) mounting the Caddyfile and data/config volumes
  3. Step 3: Bring up services and verify TLS and routing

Best Practices

  • Use explicit reverse_proxy directives for each service
  • Leverage handle blocks for path stripping and routing
  • Enable TLS with automatic certificates; consider DNS or on_demand TLS for wildcards
  • Balance load across multiple backends with health checks
  • Limit exposure to LAN when needed; use internal tls or private domains

Example Use Cases

  • app.example.com { reverse_proxy localhost:8080 }
  • app1.example.com, app2.example.com { reverse_proxy backend:3000 }
  • *.example.com { tls { dns cloudflare {env.CLOUDFLARE_API_TOKEN} } reverse_proxy localhost:8080 }
  • service.example.com { reverse_proxy backend:8080 { header_up X-Real-IP {remote_host} X-Forwarded-Proto {scheme} } }
  • admin.example.com { basicauth { admin $2a$14$HASHED_PASSWORD_HERE } reverse_proxy backend:8080 }

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers