Caddy HTTPS Automation — Let's Encrypt Certificates That Renew Forever
Setting up HTTPS on Nginx requires installing certbot, issuing certificates, registering cron jobs, and editing config files — a tedious multi-step process. Caddy automates all of it. Write the domain name, and Caddy obtains a Let's Encrypt certificate automatically, renews it automatically, and even handles HTTP-to-HTTPS redirects. Reverse proxy configuration takes exactly one line.
Caddy vs Nginx — When to Switch
Nginx is a proven web server with massive community support and fine-grained performance tuning. Caddy is a modern Go-based alternative where simplicity and automatic HTTPS are the primary selling points. Here is how they compare:
| Feature | Nginx | Caddy |
|---|---|---|
| HTTPS setup | certbot + cron required | Automatic (just add domain) |
| Config syntax | Complex block structure | Intuitive Caddyfile |
| HTTP/2 | Manual activation | Enabled by default |
| Raw performance | Very high | Sufficiently high |
| Community size | Very large | Growing |
| Best for | High traffic, fine-grained control | SMB, fast setup |
For small to mid-size projects (up to tens of thousands of daily visitors), Caddy's performance is more than adequate. Unless you need Nginx's granular tuning for high-traffic scenarios, the configuration simplicity and automatic HTTPS alone justify choosing Caddy.
Caddyfile Basics
The Caddyfile is Caddy's configuration file — similar to nginx.conf but dramatically simpler. Write the domain, open curly braces, and add directives:
# Static file serving
example.com {
root * /var/www/html
file_server
}
# This alone enables HTTPS automatically
# HTTP → HTTPS redirect is also automaticInstallation on Ubuntu/Debian is straightforward:
# Add repository and install
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
# Caddyfile location: /etc/caddy/Caddyfile
sudo systemctl enable caddy
sudo systemctl start caddyReverse Proxy — One Line Replaces Twenty
Placing Caddy in front of an app server (Next.js, Express, etc.) as a reverse proxy is the most common deployment pattern. In Caddy, the entire configuration is:
example.com {
reverse_proxy localhost:3000
}For comparison, the equivalent Nginx configuration requires approximately 20 lines covering SSL certificates, proxy headers, HTTP/2 setup, and WebSocket upgrade handling. Caddy handles all of these by default — WebSocket upgrades, HTTP/2 protocol negotiation, and proxy header forwarding work out of the box without additional directives.
If you need to forward real client IPs or custom headers:
example.com {
reverse_proxy localhost:3000 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}Multi-Domain Hosting
Running multiple domains on a single server is trivial — add a domain block for each, and Caddy issues separate HTTPS certificates automatically:
# Main site
example.com {
reverse_proxy localhost:3000
}
# Admin panel
admin.example.com {
reverse_proxy localhost:3001
}
# API server
api.example.com {
reverse_proxy localhost:4000
}
# Completely different domain
another-domain.com {
reverse_proxy localhost:5000
}
# Each domain gets its own auto-issued certificateImportant: DNS records must point to the server's IP before Caddy starts. Let's Encrypt validates domain ownership during certificate issuance — if DNS propagation is incomplete, the challenge fails and no certificate is issued.
Security Headers
HTTPS alone is not sufficient for robust security. Adding security headers protects against XSS, clickjacking, MIME-type sniffing, and protocol downgrade attacks:
example.com {
reverse_proxy localhost:3000
header {
# Force HTTPS
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Prevent XSS
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
# Limit referrer information
Referrer-Policy "strict-origin-when-cross-origin"
# Hide server identity
-Server
}
}| Header | Purpose | Priority |
|---|---|---|
Strict-Transport-Security | Forces HTTPS, prevents downgrade attacks | Required |
X-Content-Type-Options | Prevents MIME-type sniffing | Required |
X-Frame-Options | Prevents iframe embedding (clickjacking) | Required |
Referrer-Policy | Limits referrer data sent to external sites | Recommended |
How Automatic Certificate Renewal Works
Let's Encrypt certificates are valid for 90 days. Caddy automatically attempts renewal 30 days before expiration with zero service interruption. The complete lifecycle:
- Caddy detects domains in the Caddyfile and requests certificates via the ACME protocol.
- Domain ownership is verified through HTTP-01 or TLS-ALPN-01 challenges.
- Certificate is issued and applied immediately — no restart needed.
- 30 days before expiry, Caddy auto-renews. Failed attempts are retried.
- OCSP stapling is applied automatically, improving client connection speed.
To inspect certificate status:
# Check via Caddy admin API
curl localhost:2019/config/
# Certificate storage location
ls ~/.local/share/caddy/certificates/
# Certificate events in logs
journalctl -u caddy | grep -i certificatePorts 80 and 443 must be open. Let's Encrypt certificate issuance and renewal require these ports. Verify that your firewall and cloud security groups allow inbound traffic on both before starting Caddy.
Summary Checklist
- Install Caddy and register it as a systemd service
- Add your domain to the Caddyfile — HTTPS activates automatically
- Configure reverse proxy with a single
reverse_proxyline - Add domain blocks for multi-domain hosting — each gets its own certificate
- Always include security headers (HSTS, X-Frame-Options, etc.)
- Ensure ports 80/443 are open for certificate issuance and renewal
Caddy eliminates the most tedious parts of web server administration — certificate management, complex configuration syntax, and protocol negotiation — while maintaining the performance and security standards required for production. For teams that do not need Nginx's fine-grained control, the trade-off is overwhelmingly favorable.