On this page
Caddy Operations Manual
Caddy is a web server and reverse proxy written in Go. Its core selling points are automatic TLS (ACME + Let's Encrypt zero-configuration) and a concise Caddyfile configuration syntax. The configuration file location depends on the installation method: for package manager installations, it is typically at /etc/caddy/Caddyfile, while for manual deployments, it is at ./Caddyfile.
Configuration
Static File Server
example.com {
root * /var/www/html # * matches all paths
file_server # Static file serving (automatic directory listing)
encode gzip zstd # Response compression
}
Reverse Proxy
grafana.example.com {
reverse_proxy 127.0.0.1:3000 {
# Headers passed to the backend (backend logs can see the real client)
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up Host {upstream_hostport}
}
}
Multiple Backends (Load Balancing)
api.example.com {
reverse_proxy backend-1:8080 backend-2:8080 backend-3:8080 {
lb_policy least_conn # round_robin / least_conn / random
health_uri /health # Health check endpoint
health_interval 30s
}
}
mTLS (Mutual TLS Authentication)
otlp.example.com {
tls /etc/caddy/certs/server.crt /etc/caddy/certs/server.key {
client_auth {
mode require_and_verify
trusted_ca_cert_file /etc/caddy/certs/ca.crt # Trusted CA
trusted_leaf_cert_file /etc/caddy/certs/revoked.caddy # Revocation list
}
}
# Only allow specific paths, return 403 for the rest
@otlp path /api/v1/otlp/*
reverse_proxy @otlp 127.0.0.1:4318
respond 403
}
Reloading and Hot Updates
# Validate configuration syntax (verify before reloading after changing Caddyfile)
# → Outputs "Valid configuration" or error details
# Hot reload (does not interrupt existing connections; new connections use the new configuration)
# Or caddy reload --config /etc/caddy/Caddyfile
# View the final generated JSON configuration (Caddy internally translates Caddyfile to JSON)
# View the currently loaded configuration
Certificate Management
Caddy automatically manages Let's Encrypt certificates by default—issuing them via ACME HTTP-01 or TLS-ALPN-01 challenges on first startup, and automatically renewing them before expiration.
# View managed certificates
# Data directory (certificates + OCSP staple + account key):
# /var/lib/caddy/.local/share/caddy/
# Use static certificates (do not auto-issue LE) — specify file paths in tls:
# Multi-domain SAN certificate
Logging
# Default JSON format output to stderr (collected by systemd journal)
# Or configure Caddyfile to write to a file:
# log {
# output file /var/log/caddy/access.log {
# roll_size 100mb
# roll_keep 5
# }
# format json
# }
Troubleshooting
# Startup failure
|
# TLS certificate errors
|
# → Certificate expired / domain mismatch / ACME challenge failed (check journalctl)
# 502 Bad Gateway (backend unreachable)
# → Can Caddy connect to the upstream?
# → Check backend health: systemctl status <backend>
# HTTP/3 not working
# → Caddy enables h3 by default, but requires:
# - UDP port 443 allowed by firewall
# - Caddy's internal port equals external port (otherwise alt-svc port is incorrect)