caddy-reverse-proxy
SKILL.md
Caddy Reverse Proxy
Caddy is a web server with automatic HTTPS. It obtains and renews certificates automatically, redirects HTTP to HTTPS, and provides a simple configuration syntax.
Quick Reference
| Task | Syntax |
|---|---|
| Basic proxy | reverse_proxy backend:8080 |
| Multiple upstreams | reverse_proxy node1:80 node2:80 |
| Path routing | handle /api/* { reverse_proxy api:8080 } |
| Strip path prefix | handle_path /api/* { reverse_proxy api:8080 } |
| Set request header | header_up Host localhost |
| Remove response header | header_down -Server |
| Wildcard cert | tls { dns cloudflare {env.CF_API_TOKEN} } |
| Reload config | caddy reload --config /etc/caddy/Caddyfile |
Basic Patterns
Simple Reverse Proxy
example.com {
reverse_proxy backend:8080
}
Multiple Sites
app.example.com {
reverse_proxy app:3000
}
api.example.com {
reverse_proxy api:8080
}
Path-Based Routing
example.com {
handle /api/* {
reverse_proxy api:8080
}
handle {
reverse_proxy frontend:3000
}
}
Strip Path Prefix
example.com {
handle_path /api/* {
reverse_proxy api:8080 # /api/users → /users
}
}
Header Manipulation
example.com {
reverse_proxy backend:8080 {
# Set Host header (required by some backends)
header_up Host localhost
# Add client IP
header_up X-Real-IP {remote_host}
# Remove sensitive header
header_up -Authorization
# Remove server identity from response
header_down -Server
}
}
Docker Integration
Proxy to Container (Same Network)
# docker-compose.yml
services:
caddy:
image: caddy:2-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
networks:
- web
app:
image: myapp
networks:
- web
expose:
- "8080"
# Caddyfile - use container name as hostname
example.com {
reverse_proxy app:8080
}
Proxy to Host Service
services:
caddy:
extra_hosts:
- "host.docker.internal:host-gateway"
example.com {
reverse_proxy host.docker.internal:8080
}
TLS Configuration
Automatic (Default)
Caddy automatically obtains certificates when:
- Domain DNS points to server
- Ports 80/443 accessible
Wildcard Certificates
Requires DNS provider plugin:
*.example.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
@app1 host app1.example.com
handle @app1 {
reverse_proxy app1:8080
}
@app2 host app2.example.com
handle @app2 {
reverse_proxy app2:8080
}
}
Internal/Development
localhost {
tls internal
reverse_proxy app:8080
}
Request Matchers
# Path matching
@api path /api/*
# Header matching
@websocket header Connection *Upgrade*
# Method matching
@post method POST
# Combined (AND logic)
@api_post {
path /api/*
method POST
}
# Use matcher
handle @api {
reverse_proxy api:8080
}
Load Balancing
example.com {
reverse_proxy node1:80 node2:80 node3:80 {
lb_policy round_robin
health_uri /health
health_interval 30s
}
}
Common Configurations
Security Headers
(security) {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
}
}
example.com {
import security
reverse_proxy backend:8080
}
www Redirect
www.example.com {
redir https://example.com{uri} permanent
}
SPA (Single Page App)
example.com {
root * /srv
try_files {path} /index.html
file_server
}
gRPC / HTTP/2 Cleartext
example.com {
reverse_proxy h2c://grpc-server:9000
}
Global Options
{
email admin@example.com
# Use staging for testing
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
example.com {
# site config
}
Reference Files
| File | When to Read |
|---|---|
| reverse-proxy.md | Load balancing, health checks, transport options |
| caddyfile-syntax.md | Matchers, handle blocks, directives |
| tls-certificates.md | DNS challenge, wildcards, mTLS |
| docker-patterns.md | Docker Compose, networks, host access |
| troubleshooting.md | Common errors and diagnostics |
Common Issues
| Problem | Solution |
|---|---|
| "no upstreams available" | Check backend running, same Docker network |
| "connection refused" | Backend binding to 0.0.0.0, not 127.0.0.1 |
| Certificate not obtained | Check DNS points to server, ports 80/443 open |
| 403 Forbidden | Backend needs header_up Host localhost |
| WebSocket fails | Usually works; check flush_interval -1 for SSE |
Verification Commands
# Validate config
caddy validate --config /etc/caddy/Caddyfile
# Format config
caddy fmt --overwrite /etc/caddy/Caddyfile
# Reload (zero-downtime)
caddy reload --config /etc/caddy/Caddyfile
# Debug mode
# Add to global options: { debug }
# Check certificate
openssl s_client -connect example.com:443 -servername example.com
Official Documentation
| Topic | URL |
|---|---|
| Caddyfile | https://caddyserver.com/docs/caddyfile |
| reverse_proxy | https://caddyserver.com/docs/caddyfile/directives/reverse_proxy |
| Automatic HTTPS | https://caddyserver.com/docs/automatic-https |
| Common Patterns | https://caddyserver.com/docs/caddyfile/patterns |
Weekly Installs
6
Repository
dimdasci/vps-setupFirst Seen
Mar 1, 2026
Security Audits
Installed on
opencode6
gemini-cli6
github-copilot6
amp6
cline6
codex6