Why Caddy Is Worth Your Attention
Running multiple self-hosted services on a single server creates an immediate problem: you can’t point every service at port 80 or 443. A reverse proxy sits in front of your services, routing incoming requests by domain name to the right internal port. Nginx and Apache have handled this job for years, but they require separate tools – like Certbot – to manage TLS certificates. Caddy handles certificate issuance and renewal automatically through Let’s Encrypt, with no extra configuration required.
What makes Caddy different is its configuration file. The Caddyfile uses a syntax clean enough to read at a glance, where a full HTTPS reverse proxy for a service takes three lines. Nginx would need a server block with listen directives, ssl_certificate paths, location blocks, and proxy headers – all maintained separately from the certificate renewal cron job. Caddy collapses that entire stack into something you can write in under a minute and reload without downtime.

Installing Caddy on a Linux Server
Caddy provides official packages for Debian, Ubuntu, Fedora, and most common Linux distributions. On Debian or Ubuntu, the installation pulls from Caddy’s own APT repository rather than the default distribution packages, which tend to lag several major versions behind. Start by installing the required dependencies and adding the repository:
- Install curl and gnupg if they are not already present
- Add Caddy’s GPG key:
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg - Add the APT source:
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list - Run
sudo apt update && sudo apt install caddy
Once installed, the Caddy service starts automatically and is managed by systemd. You can check its status with sudo systemctl status caddy. The default Caddyfile lives at /etc/caddy/Caddyfile, and any changes you make there take effect after running sudo systemctl reload caddy. There is no need to restart the process – Caddy performs a graceful config reload that keeps existing connections alive while applying the new rules.
Before touching the Caddyfile, confirm that your domain’s DNS A record points to your server’s public IP address. Caddy will attempt to obtain a TLS certificate from Let’s Encrypt the moment it sees a domain name in the config, and that process requires the domain to resolve correctly. If the DNS hasn’t propagated or the A record is wrong, the ACME challenge will fail and you’ll get an error in the Caddy logs rather than a working certificate. Check propagation with dig yourdomain.com or any public DNS lookup tool before proceeding.
Writing the Caddyfile
The Caddyfile syntax is block-based. Each block starts with the address Caddy should respond to, followed by directives inside curly braces. For a basic reverse proxy that forwards traffic from a public domain to a local service running on port 8080, the entire configuration looks like this:
- Open
/etc/caddy/Caddyfilein your editor - Replace the default contents with your site block:
app.yourdomain.com {
reverse_proxy localhost:8080
} - Save the file and run
sudo systemctl reload caddy
That three-line block handles everything: HTTPS certificate issuance, HTTP-to-HTTPS redirection, and proxying. Caddy sets sensible proxy headers by default, including X-Forwarded-For and X-Real-IP, so the upstream service sees the original client IP rather than the loopback address. If you need to proxy multiple services, add additional site blocks below the first one – each with its own domain and internal port. Caddy manages a separate certificate for each domain without any extra configuration.

Handling Multiple Services and Advanced Configuration
Most self-hosted setups involve several services running on different ports – a dashboard on 8080, a media server on 8096, a password manager on 8443. Caddy handles this cleanly by stacking site blocks in a single Caddyfile. Each block is completely independent: its own domain, its own certificate, its own proxy target. Adding a new service is a matter of appending a new three-line block and reloading. If one of those services is something like Grocy, which runs on an internal port and needs HTTPS exposure, Caddy handles it identically to any other backend.
For services that use WebSockets – common in monitoring dashboards and real-time apps – Caddy proxies WebSocket connections automatically without additional directives. The reverse_proxy directive detects and upgrades the connection. Services using long-polling or server-sent events behave the same way. This is one area where Caddy’s defaults save meaningful configuration time compared to Nginx, where WebSocket support requires explicit Upgrade and Connection header directives.
Caddy also supports wildcard certificates for situations where you’re running subdomains dynamically. To use a wildcard, the domain block is written as *.yourdomain.com, and Caddy requires a DNS challenge rather than the HTTP challenge. This means configuring a DNS provider plugin – Caddy has official plugins for Cloudflare, Route53, and others – so Caddy can create and delete DNS TXT records automatically to prove domain ownership. The DNS challenge is also the only option when your server isn’t publicly reachable, such as when it sits behind a NAT without port forwarding. In that scenario, you lose the HTTP challenge but keep automated certificate management.
Rate limiting, basic authentication, and access control are all available as native Caddy directives. Adding HTTP basic auth to a site block requires two lines: a basicauth directive with a path matcher and a hashed password generated by caddy hash-password. Caddy doesn’t store plaintext passwords – the hash is what goes in the config file. For teams managing a larger infrastructure, including network assets tracked with something like Netbox, Caddy’s per-site auth makes it possible to expose specific services to staff without opening everything to the same level of access.

One operational detail that catches people off guard: Caddy stores its certificates and ACME account data in /var/lib/caddy/.local/share/caddy by default on systemd-managed installs. If you migrate your server or rebuild the system, that directory needs to travel with the config, otherwise Caddy will re-request all certificates from scratch and hit Let’s Encrypt’s rate limits if you’ve already issued several in the same week. Let’s Encrypt allows five duplicate certificate requests per domain per week – not a generous limit when you’re debugging a fresh setup. Back up the data directory alongside your Caddyfile, and you avoid the issue entirely.





