Why Caddy Is the Web Server Most Developers Wish They Had Started With
Setting up HTTPS on a self-hosted web server used to mean wrestling with Certbot, configuring renewal cron jobs, and hoping nothing broke at 2 a.m. when your certificate expired. Caddy eliminates most of that friction by handling TLS certificate acquisition and renewal automatically through Let’s Encrypt, with zero manual configuration required. It reads a single human-readable config file called a Caddyfile, serves your site over HTTPS by default, and redirects HTTP traffic without you writing a single redirect rule. For anyone running a home lab, a personal project, or a small production site, that changes the math on self-hosting considerably.
Caddy is written in Go, ships as a single binary, and runs on Linux, macOS, and Windows. There is no dependency hell, no PHP runtime to worry about, no Apache module maze. You point it at a domain, give it a site root or a reverse proxy target, and it handles the rest. This guide walks through installing Caddy on a Linux server, configuring it as a static file server and as a reverse proxy, enabling automatic HTTPS, and setting it up as a systemd service so it survives reboots.

Installing Caddy on a Linux Server
The cleanest installation method on Debian or Ubuntu is through Caddy’s official APT repository, which ensures you always receive the latest stable release through standard package management. Start by installing the required dependencies: curl and gnupg should already be present on most systems, but confirm with sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl. Next, add Caddy’s GPG key and repository with these two commands:
- 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
After adding the repository, run sudo apt update followed by sudo apt install caddy. The package manager handles everything – binary placement, initial configuration files, and a systemd service unit. You can verify the installation immediately with caddy version, which should return the current build number. If you are on a Red Hat-based distribution like Rocky Linux or Fedora, Caddy maintains an official COPR repository that follows the same pattern: add the repo, then install via DNF. On systems where neither package manager works cleanly, downloading the pre-compiled binary from https://caddyserver.com/download, placing it in /usr/local/bin, and marking it executable with chmod +x is a perfectly valid alternative.
Writing Your First Caddyfile
The Caddyfile is Caddy’s primary configuration format, and its syntax is intentionally minimal. The default Caddyfile lives at /etc/caddy/Caddyfile when installed via the package manager. For a basic static site, the entire configuration can be as short as three lines. Open the file with sudo nano /etc/caddy/Caddyfile, clear the defaults, and write the following:
- yourdomain.com {
- root * /var/www/html
- file_server
- }
Replace yourdomain.com with the actual domain pointing to your server’s public IP address. The root directive sets the document root, * is a request matcher meaning all requests, and file_server enables static file serving. That is genuinely the full configuration for a working HTTPS static site. When Caddy starts, it contacts Let’s Encrypt, completes the ACME HTTP-01 challenge automatically, retrieves a certificate, stores it locally, and schedules renewal before expiry. Your DNS record must point to the server’s public IP for this to work, and ports 80 and 443 must be reachable from the internet.
For a reverse proxy setup – which is the more common use case for self-hosted apps – the Caddyfile is equally concise. If you are running an application locally on port 3000, the configuration looks like this:
- app.yourdomain.com {
- reverse_proxy localhost:3000
- }
Caddy infers that you want HTTPS, acquires the certificate for app.yourdomain.com, and proxies all incoming requests to your local service. There is no need to specify SSL certificate paths, configure HSTS headers manually, or write redirect rules from HTTP to HTTPS – Caddy writes that redirect automatically. You can stack multiple site blocks in a single Caddyfile, each with its own subdomain, certificate, and upstream target. For a home lab running several self-hosted services, this makes a single Caddy instance the front door for everything. If you are also running Unraid as a NAS and app server, Caddy slots in cleanly as the HTTPS reverse proxy in front of its web-accessible services.
Caddy also supports wildcard certificates through DNS-01 challenges, which is useful when you want to cover all subdomains under a single certificate without exposing each one to the public internet. This requires a DNS provider plugin – Cloudflare is the most commonly used – and means building a custom Caddy binary with that plugin included, which the Caddy download page handles through a module selection interface. Wildcard setups are more involved but worth it for labs running a large number of internal subdomains.

Running Caddy as a Systemd Service
When installed via the APT or DNF package, Caddy’s systemd service is already configured and ready. Enable it to start at boot with sudo systemctl enable caddy, then start it immediately with sudo systemctl start caddy. Check its status with sudo systemctl status caddy – you should see it listed as active and running. Logs are accessible via journalctl -u caddy –follow, which is the fastest way to watch certificate negotiation in real time on first startup.
After editing the Caddyfile, do not restart the service. Caddy supports live configuration reloads with sudo systemctl reload caddy, which applies changes without dropping existing connections. This matters in production contexts where a full restart would interrupt active requests. If you want to validate your Caddyfile syntax before reloading, run caddy validate –config /etc/caddy/Caddyfile first. Caddy will report any errors and exit without touching the running process.
Troubleshooting Certificates and Common Pitfalls
The most common failure point during initial setup is DNS. If your domain does not yet resolve to the server’s IP, Caddy cannot complete the ACME challenge and the certificate request will fail. Check your DNS propagation with a tool like dig yourdomain.com before starting Caddy for the first time. Let’s Encrypt also enforces rate limits – five failed certificate requests per domain per hour – so if you are testing repeatedly with a real domain, those attempts add up quickly. Use the Let’s Encrypt staging environment while debugging by adding acme_ca https://acme-staging-v02.api.letsencrypt.org/directory inside your site block.
Firewall rules are the second most common obstacle. On a server running UFW, open both required ports with sudo ufw allow 80 and sudo ufw allow 443. If you are behind a home router or a cloud provider’s security group, the same ports need to be forwarded or allowed at the network level before Caddy can receive the challenge request from Let’s Encrypt’s servers.

Caddy stores its certificates and ACME account data in /var/lib/caddy/.local/share/caddy by default. Back up this directory if you are moving to a new server – doing so preserves your Let’s Encrypt account registration and avoids triggering rate limits during migration. Certificate files are stored in a structured format that Caddy manages entirely, so there is no need to touch them manually. The renewal process runs automatically in the background, typically 30 days before expiry, and logs any renewal activity to the systemd journal.
Frequently Asked Questions
Does Caddy automatically renew SSL certificates?
Yes. Caddy renews Let’s Encrypt certificates automatically in the background, typically 30 days before expiry, with no manual intervention required.
Can Caddy act as a reverse proxy for multiple apps?
Yes. You can define multiple site blocks in a single Caddyfile, each pointing to a different local port or upstream service, each with its own certificate.





