Caddy: The Web Server That Handles Its Own SSL
Nginx makes you configure SSL yourself. Caddy does it automatically, for every domain, with zero lines of certificate configuration. That difference sounds small until you've spent an afternoon debugging expired certs at 2 AM.
If you're self-hosting anything on your own hardware, Caddy is the web server that gets out of your way and stays there.
What Caddy Replaces
Most solo builders running services on a Mac Mini or a VPS end up with Nginx. It works. It's been working since 2004. But Nginx was designed for a world where SSL certificates cost money and were installed by sysadmins. The configuration syntax reflects that era: verbose, bracket-heavy, and full of directives that exist purely because someone had to tell the server where the certificate files live.
Caddy was built for a world where Let's Encrypt exists and HTTPS should be the default, not something you bolt on. When you point a domain at a Caddy server, it automatically provisions a TLS certificate from Let's Encrypt, configures HTTPS, sets up HTTP-to-HTTPS redirects, and renews the certificate before it expires. You don't configure any of that. It just happens.
The practical difference: with Nginx, you install certbot, run it for each domain, set up a cron job for renewals, configure the certificate paths in your server blocks, and hope the renewal cron doesn't silently fail three months later. With Caddy, you write the domain name in the config file. That's the entire SSL setup.
The Caddyfile
Here's a Caddyfile that serves a static site with automatic HTTPS:
mysite.com {
root * /srv/www
file_server
}
Three lines. The domain gets a certificate. Static files get served. HTTPS is on. HTTP requests redirect to HTTPS. Headers are set sensibly.
Here's the Nginx equivalent:
server {
listen 80;
server_name mysite.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name mysite.com;
ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
root /srv/www;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
That's 18 lines to do what Caddy does in 3. And the Nginx version doesn't even include the certbot setup, the renewal cron, or the certificate path configuration. Those are separate steps you have to get right independently.
The readability gap compounds as you add services. One reverse proxy in Nginx is manageable. Five reverse proxies with SSL, each with their own server block, certificate paths, and upstream definitions, becomes a file you dread opening.
Reverse Proxying Multiple Services
This is where Caddy earns its place in a solo builder stack. Most self-hosted setups involve 3-5 services running on different ports on the same machine: a blog on port 2368, analytics on port 3000, a database admin panel on port 8080, maybe an API on port 4000. Each needs its own subdomain and its own SSL certificate.
Here's a Caddyfile that reverse proxies three services:
blog.mysite.com {
reverse_proxy localhost:2368
}
analytics.mysite.com {
reverse_proxy localhost:3000
}
api.mysite.com {
reverse_proxy localhost:4000
}
Nine lines. Three subdomains. Three certificates. All provisioned and renewed automatically. Add a fourth service by adding three more lines.
The Nginx equivalent for three reverse-proxied services with SSL runs to about 70 lines, plus certbot commands for each domain. I've written those configs. They work. But every time I added a new service, I'd copy-paste a server block, change the domain and port, re-run certbot, reload Nginx, and hope I didn't introduce a syntax error in a bracket somewhere.
With Caddy, adding a new service takes 15 seconds and zero mental overhead.
Behind Cloudflare Tunnel
If you're running services on a home network behind CGNAT (Starlink, most fiber ISPs), you're probably using Cloudflare Tunnel to expose them to the internet. This changes Caddy's SSL behavior, because Cloudflare terminates TLS at their edge. Traffic between Cloudflare and your origin server travels through the tunnel, which is already encrypted.
The Caddyfile for this setup needs three adjustments:
{
auto_https off
http_port 80
}
http://blog.mysite.com {
reverse_proxy localhost:2368
}
http://analytics.mysite.com {
reverse_proxy localhost:3000
}
The global options block at the top disables automatic HTTPS. Each site block is prefixed with http:// to tell Caddy not to provision certificates. And http_port 80 sets the listening port explicitly.
Skip these and Caddy will try to provision Let's Encrypt certificates for domains that resolve to Cloudflare's IPs, not yours. The ACME challenge will fail, and you'll see certificate errors in the logs every 30 seconds. I've watched it happen. The fix takes 10 seconds once you know about it, but discovering the problem takes considerably longer.
The Gotchas
Caddy has three things that will bite you if nobody warns you about them.
Port 80 and 443 must be available. Caddy needs port 80 for the ACME HTTP challenge that Let's Encrypt uses to verify domain ownership. If another process is using port 80 — an old Apache install, a stale Nginx process, a Python dev server you forgot about — Caddy will fail to start with a bind error. Run lsof -i :80 before your first Caddy start.
DNS must resolve before Caddy starts. If you add a new domain to your Caddyfile before pointing the DNS record at your server, Caddy will attempt the ACME challenge and fail. It handles this gracefully (serves the other domains fine, retries the failed one later), but you'll see errors in the logs that look alarming until you understand them. Point DNS first, then add the site to Caddy.
The Caddyfile reload is not the same as a restart. Use caddy reload to apply config changes. It validates the new config before swapping, so a syntax error won't take down your running services. A full restart (caddy stop && caddy start) will cause a brief downtime window. Get in the habit of caddy reload and you'll never drop a request during config changes.
Installation and First Run
On macOS:
brew install caddy
On Ubuntu/Debian:
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
Create a Caddyfile in /etc/caddy/Caddyfile (Linux) or wherever you keep configs (macOS), then:
caddy run --config /path/to/Caddyfile
On Linux with systemd, Caddy ships with a service unit. Enable it and it starts on boot, restarts on crash, and logs to journalctl:
sudo systemctl enable --now caddy
Caddy vs Nginx vs Traefik
| Feature | Nginx | Caddy | Traefik |
|---|---|---|---|
| Automatic HTTPS | No (certbot required) | Yes (built-in) | Yes (with config) |
| Config syntax | Verbose, bracket-heavy | Minimal, readable | YAML/TOML, label-based |
| HTTP/3 (QUIC) | Experimental | Built-in | Built-in |
| Hot reload | Yes (nginx -s reload) | Yes (caddy reload) | Yes (file watch) |
| Reverse proxy | Manual per-block config | One-line directive | Docker label discovery |
| Learning curve | Steep | Gentle | Moderate (Docker-centric) |
| Community/ecosystem | Massive | Growing | Strong in Docker |
| Best for | High-traffic production | Self-hosted services | Container orchestration |
Nginx wins on raw performance and ecosystem maturity. If you're serving 50,000 concurrent connections, Nginx is battle-tested at that scale. Traefik wins if your entire infrastructure is Docker containers with labels and you want automatic service discovery.
Caddy wins on simplicity, and for a solo builder running 3-8 services on one machine, simplicity is the feature that matters most. You don't need automatic Docker discovery. You don't need a 2,000-line config. You need HTTPS that works, reverse proxying that's readable, and certificate renewals you never think about.
Where Caddy Fits
The solo builder server stack has a pattern: containers for services, a reverse proxy to route traffic, a tunnel for public access, and SSL to keep everything secure. Caddy collapses the reverse proxy and SSL layers into a single tool with a config file you can read in 30 seconds.
Every hour you don't spend debugging certificate renewals or deciphering Nginx configs is an hour you spend on the thing you're actually building. Infrastructure should disappear into the background. Caddy is the first web server I've used where it actually does.
Three lines per service. Automatic certificates. Config changes without downtime. The web server is the last thing you should be thinking about, and Caddy makes sure you don't.