.claude/skills/ts-caddy/SKILL.md
Configure Caddy as a web server and reverse proxy — automatic HTTPS, reverse proxy, load balancing, file server, redirects, headers, rate limiting, and API configuration. Use when tasks involve serving websites, proxying to backend services, automatic TLS certificate management, or replacing Nginx with a simpler configuration.
npx skillsauth add eliferjunior/Claude caddyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
# 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
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.
# 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
}
# 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
}
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
}
}
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
}
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
}
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"}
}
}]
}
]
}
}
}
}
}
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"}]}]}'
# 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
# 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
}
}
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 | 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 | nginx -s reload |
| HTTP/3 | Built-in | Requires compilation flags |
| Plugins | Go modules, xcaddy build | C modules, recompilation |
| Performance | Fast, slightly slower than Nginx | Fastest for static files |
| Use case | Modern apps, auto-HTTPS | Legacy, high-traffic static |
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
try_files for SPAs — try_files {path} /index.html serves the SPA shell for all routes, letting the frontend router handle them.xcaddy.encode gzip zstd on every site — compression is free performance. Zstandard is faster than gzip for modern clients.-Server header — remove it in production to avoid leaking server info.development
Expert guidance for Fireworks AI, the platform for running open-source LLMs (Llama, Mixtral, Qwen, etc.) with enterprise-grade speed and reliability. Helps developers integrate Fireworks' inference API, fine-tune models, and deploy custom model endpoints with function calling and structured output support.
development
Convert any website into clean, structured data with Firecrawl — API-first web scraping service. Use when someone asks to "turn a website into markdown", "scrape website for LLM", "Firecrawl", "extract website content as clean text", "crawl and convert to structured data", or "scrape website for RAG". Covers single-page scraping, full-site crawling, structured extraction, and LLM-ready output.
tools
Expert guidance for Firebase, Google's platform for building and scaling web and mobile applications. Helps developers set up authentication, Firestore/Realtime Database, Cloud Functions, hosting, storage, and analytics using Firebase's SDK and CLI.
development
When the user needs to build file upload functionality for a web application. Use when the user mentions "file upload," "image upload," "upload endpoint," "multipart upload," "presigned URL," "S3 upload," "file validation," "upload to cloud storage," or "accept user files." Handles upload endpoints, file validation (type, size, magic bytes), cloud storage integration, and upload status tracking. For image/video processing after upload, see media-transcoder.