If you're tired of wiring Certbot into a cron job and hoping it renews before your certificate expires, Caddy is worth ten minutes of your time. It's a web server that gets HTTPS right out of the box — no separate ACME client, no renewal script, no forgetting about it until a customer emails you about a broken padlock. Here's how to get it running on a fresh VPS, plus the handful of things that trip people up the first time.
Why Bother Switching From Nginx or Apache
Nginx and Apache are fine servers, but TLS is bolted on — you run Certbot, it edits your config or drops certs in /etc/letsencrypt, and a systemd timer renews them twice a day just in case. That works until it doesn't: a firewall rule blocks the renewal, a config edit breaks the Certbot hook, or someone reboots the box and the timer never comes back. Caddy handles certificate issuance and renewal inside the same process that serves your site. One binary, one config file, and HTTPS just happens.
This isn't an argument that Caddy is objectively better for every workload — if you've already got a tuned Nginx setup with custom modules, don't rip it out. But for a new VPS, a reverse proxy in front of an app, or a static site you want live in minutes, it's hard to beat.
Before You Start
- A VPS running Ubuntu 22.04/24.04 or Debian 11/12, with root or sudo access
- A domain (or subdomain) with an A record pointing at your VPS's public IP — Caddy needs this to prove domain ownership to Let's Encrypt
- Ports 80 and 443 open in your firewall (port 80 is used briefly for the ACME HTTP-01 challenge, even though your site will serve on 443)
If you're not sure the A record has propagated yet, check with dig +short yourdomain.com from your local machine — it should return your VPS IP before you go any further.
Step 1: Install Caddy
Caddy maintains its own apt repo, so you get real updates instead of a stale distro package:
sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
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 -y caddyThe install starts a caddy systemd service automatically, serving a placeholder page on port 80. Check it's alive:
systemctl status caddyStep 2: Write Your Caddyfile
Caddy's config lives at /etc/caddy/Caddyfile. For a static site, it's almost insultingly short:
yourdomain.com {
root * /var/www/yourdomain.com
file_server
}That's it. No listen 443 ssl, no cert paths, no cipher suite bikeshedding. Caddy sees the domain name, requests a certificate from Let's Encrypt on first request, and serves over HTTPS from then on.
Create the web root and drop a test file in before you reload:
sudo mkdir -p /var/www/yourdomain.com
echo "<h1>It works</h1>" | sudo tee /var/www/yourdomain.com/index.htmlStep 3: Reload and Verify
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddycaddy validate catches syntax mistakes before they take your site down — always run it before reloading on a production box. Then confirm HTTPS is actually working:
curl -I https://yourdomain.comYou should see HTTP/2 200 and the response headers, with no certificate warnings. Check the logs if something's off:
sudo journalctl -u caddy -fReverse Proxying an App (Node, PHP-FPM, Anything)
Most people running a VPS aren't serving static files — they've got a Node app, a Python service, or PHP-FPM behind the scenes. Caddy's reverse proxy directive is one line:
api.yourdomain.com {
reverse_proxy localhost:3000
}For PHP-FPM (say, a Laravel or WordPress app you're running outside cPanel), pair php_fastcgi with a root:
yourdomain.com {
root * /var/www/yourdomain.com/public
php_fastcgi unix//run/php/php8.3-fpm.sock
file_server
}Restart PHP-FPM's socket path may differ by PHP version — check with ls /run/php/ if the reload fails.
Common Setup Problems
| Symptom | Likely Cause | Fix |
|---|---|---|
| Caddy won't get a certificate; logs show timeouts on port 80 | Firewall or cloud security group is blocking inbound port 80 | Open port 80 (sudo ufw allow 80/tcp) even if you only care about HTTPS — the ACME challenge needs it |
| "tls: no certificates configured" or endless retry loop | DNS A record doesn't point at this VPS yet, or points at Cloudflare with proxying on | Confirm dig +short yourdomain.com matches the VPS IP; if using Cloudflare, either set it to DNS-only (grey cloud) during first issuance, or switch to the DNS-01 challenge with an API token |
| 502 from reverse_proxy | Backend app isn't listening on the port/socket Caddy expects, or hasn't started yet | Confirm with ss -tlnp that the app is actually bound to that port before Caddy tries to proxy to it |
| Old page still showing after edits | Config wasn't reloaded, or browser/CDN cache | Run sudo systemctl reload caddy after every Caddyfile change, and hard-refresh or purge CDN cache |
| Rate limited by Let's Encrypt | Too many reloads/tests against the same domain in a short window | Point the Caddyfile at Let's Encrypt's staging CA (acme_ca https://acme-staging-v02.api.letsencrypt.org/directory) while testing, then remove that line for production |
Certificate Renewal — the Part You Get to Ignore
Caddy renews certificates automatically about a month before expiry, in the background, with no cron job or systemd timer to babysit. You genuinely don't need to do anything here. If you want to confirm it's working, journalctl -u caddy will show renewal attempts logged around the 30-day mark — but there's no action required on your end.
Prevention Tips
- Keep
ufwor your cloud firewall open on 80 and 443 permanently, not just during setup — renewals need port 80 too - Run
sudo apt update && sudo apt upgrade caddyperiodically; Caddy ships security fixes through the same repo - Back up
/etc/caddy/Caddyfileand/var/lib/caddy(where certs are cached) as part of your normal VPS backup routine, so a server rebuild doesn't mean re-issuing every certificate from scratch - If you're moving from Nginx or Apache, don't run both bound to the same ports — stop and disable the old service (
sudo systemctl disable --now nginx) before starting Caddy
Once it's running, a Caddy box needs almost no ongoing maintenance for TLS — which is really the whole pitch. If you'd rather skip the server admin entirely, Getwebup's managed VPS plans come with this kind of setup handled for you.