install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/TerminalSkills/skills/caddy" ~/.claude/skills/comeonoliver-skillshub-caddy && rm -rf "$T"
manifest:
skills/TerminalSkills/skills/caddy/SKILL.mdsource content
Caddy
Modern web server with automatic HTTPS. Configures TLS certificates from Let's Encrypt without any setup — just specify domain names and Caddy handles the rest.
Setup
# Debian/Ubuntu sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update && sudo apt install caddy # Docker docker run -d -p 80:80 -p 443:443 \ -v caddy_data:/data -v caddy_config:/config \ -v $PWD/Caddyfile:/etc/caddy/Caddyfile \ caddy:latest
Caddyfile (config format)
Simple Static Site
example.com { root * /var/www/html file_server encode gzip zstd }
That's it. Caddy automatically obtains and renews a TLS certificate for
example.com.
Reverse Proxy
# Single backend app.example.com { reverse_proxy localhost:3000 } # With health checks and load balancing api.example.com { reverse_proxy localhost:3001 localhost:3002 localhost:3003 { lb_policy round_robin health_uri /health health_interval 10s health_timeout 5s } } # WebSocket support (automatic — no special config needed) ws.example.com { reverse_proxy localhost:8080 }
Multi-Service Setup
# Main app example.com { # API routes → backend handle /api/* { reverse_proxy localhost:3000 } # Static assets → file server with caching handle /static/* { root * /var/www/static file_server header Cache-Control "public, max-age=31536000, immutable" } # Everything else → frontend SPA handle { root * /var/www/app try_files {path} /index.html file_server } encode gzip zstd } # Admin panel — separate subdomain admin.example.com { reverse_proxy localhost:3001 # Basic auth protection basicauth { admin $2a$14$... # bcrypt hash: caddy hash-password } } # Redirect www to non-www www.example.com { redir https://example.com{uri} permanent }
Security Headers
example.com { reverse_proxy localhost:3000 header { # Security headers Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-Content-Type-Options "nosniff" X-Frame-Options "DENY" Referrer-Policy "strict-origin-when-cross-origin" Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" Permissions-Policy "camera=(), microphone=(), geolocation=()" # Remove server identification -Server } }
Rate Limiting
example.com { # Rate limit: 100 requests per minute per IP rate_limit { zone api_zone { key {remote_host} events 100 window 1m } } reverse_proxy localhost:3000 }
CORS
api.example.com { @cors_preflight method OPTIONS handle @cors_preflight { header Access-Control-Allow-Origin "https://app.example.com" header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" header Access-Control-Allow-Headers "Content-Type, Authorization" header Access-Control-Max-Age "86400" respond "" 204 } header Access-Control-Allow-Origin "https://app.example.com" reverse_proxy localhost:3000 }
JSON Config (API mode)
For programmatic configuration instead of Caddyfile:
{ "apps": { "http": { "servers": { "main": { "listen": [":443"], "routes": [ { "match": [{"host": ["api.example.com"]}], "handle": [{ "handler": "reverse_proxy", "upstreams": [ {"dial": "localhost:3000"}, {"dial": "localhost:3001"} ], "load_balancing": {"selection_policy": {"policy": "round_robin"}}, "health_checks": { "active": {"uri": "/health", "interval": "10s"} } }] } ] } } } } }
Config API
Caddy exposes a REST API for live configuration changes — no restarts needed:
# Load full config curl -X POST http://localhost:2019/load \ -H "Content-Type: application/json" \ -d @caddy-config.json # Get current config curl http://localhost:2019/config/ # Update a specific route without touching anything else curl -X PATCH "http://localhost:2019/config/apps/http/servers/main/routes/0" \ -H "Content-Type: application/json" \ -d '{"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "localhost:3002"}]}]}'
Common Patterns
Local Development with HTTPS
# Caddyfile.dev — local HTTPS with self-signed cert localhost { tls internal # Auto-generate a local CA cert reverse_proxy /api/* localhost:3000 reverse_proxy localhost:5173 # Vite dev server }
caddy run --config Caddyfile.dev # App available at https://localhost with valid (local) TLS
Wildcard Subdomains
# Requires DNS challenge (e.g., Cloudflare DNS plugin) *.example.com { tls { dns cloudflare {env.CF_API_TOKEN} } # Route subdomains to different backends @app host app.example.com handle @app { reverse_proxy localhost:3000 } @docs host docs.example.com handle @docs { reverse_proxy localhost:3001 } # Catch-all: 404 handle { respond "Not found" 404 } }
Caching Proxy
api.example.com { reverse_proxy localhost:3000 # Cache responses from the backend @cacheable { method GET path /api/public/* } header @cacheable Cache-Control "public, max-age=300" # 5 min cache }
Caddy vs Nginx
| Caddy | Nginx | |
|---|---|---|
| HTTPS | Automatic (Let's Encrypt) | Manual cert setup or certbot |
| Config format | Caddyfile (simple) or JSON | nginx.conf (complex) |
| Hot reload | API-based, zero downtime | |
| HTTP/3 | Built-in | Requires compilation flags |
| Plugins | Go modules, | C modules, recompilation |
| Performance | Fast, slightly slower than Nginx | Fastest for static files |
| Use case | Modern apps, auto-HTTPS | Legacy, high-traffic static |
Commands
caddy run # Foreground with Caddyfile in current dir caddy start # Background daemon caddy stop # Stop daemon caddy reload # Reload config without downtime caddy adapt --config Caddyfile # Convert Caddyfile to JSON (debugging) caddy validate --config Caddyfile # Check for syntax errors caddy hash-password # Generate bcrypt hash for basicauth caddy fmt --overwrite Caddyfile # Format Caddyfile
Guidelines
- Automatic HTTPS is the default — don't disable it unless you have a specific reason. Caddy handles cert issuance, renewal, and OCSP stapling.
- Use
for SPAs —try_files
serves the SPA shell for all routes, letting the frontend router handle them.try_files {path} /index.html - Caddyfile for humans, JSON for automation — Caddyfile is easier to read and maintain; JSON API is for programmatic management.
- Config API is live — changes via the admin API take effect immediately without restart or reload.
- DNS challenge for wildcards — wildcard certs require DNS-01 challenge. Install the appropriate DNS provider plugin via
.xcaddy
on every site — compression is free performance. Zstandard is faster than gzip for modern clients.encode gzip zstd- Health checks on reverse proxy — always configure them for multi-backend setups to avoid routing to dead backends.
header — remove it in production to avoid leaking server info.-Server