Caddy is a web server that automatically provisions and renews TLS certificates, making HTTPS a breeze.
Let’s get Caddy installed and serving a simple site.
First, installation. The easiest way is usually via your package manager. For Debian/Ubuntu:
sudo apt update
sudo apt install caddy
For Fedora/CentOS/RHEL:
sudo dnf install caddy
On macOS with Homebrew:
brew install caddy
Once installed, Caddy runs as a system service. You can start, stop, and check its status with systemctl (on most Linux systems):
sudo systemctl start caddy
sudo systemctl enable caddy # To start on boot
sudo systemctl status caddy
Now, the magic. Caddy’s configuration is incredibly simple, especially for basic HTTPS. It uses a Caddyfile. The default location is /etc/caddy/Caddyfile. If it doesn’t exist, create it.
Here’s a minimal Caddyfile to serve a static site:
yourdomain.com {
root * /var/www/yourdomain.com
file_server
}
Let’s break this down.
yourdomain.com is the address Caddy will listen for. When a request comes in for this domain, Caddy will apply the directives within the curly braces.
root * /var/www/yourdomain.com tells Caddy that for any request (*), the root directory for serving files is /var/www/yourdomain.com. You’ll need to create this directory and put your website’s index.html (and other assets) inside it.
file_server is a directive that enables static file serving. Caddy will look for index.html in the root directory by default.
Before Caddy can serve your domain, you need to point your domain’s DNS A record to the IP address of the server where Caddy is running. For example, create an A record for yourdomain.com pointing to 192.0.2.1 (replace with your server’s actual public IP).
After creating or editing your Caddyfile, you need to tell Caddy to reload its configuration.
sudo systemctl reload caddy
If Caddy is running and your DNS is set up correctly, visiting https://yourdomain.com in your browser should now show your website. Caddy automatically obtained a TLS certificate from Let’s Encrypt (or ZeroSSL, by default) and is serving it over HTTPS. It will also automatically renew the certificate before it expires.
What if you want to serve multiple sites, or have a more complex setup? Caddy’s Caddyfile is very powerful. Here’s an example for serving a site with a reverse proxy to a backend application:
example.org {
reverse_proxy localhost:8080
}
another.com {
root * /var/www/another.com
file_server
}
In this example:
example.org will proxy all requests to an application running on localhost:8080.
another.com will serve static files from /var/www/another.com, just like the first example.
Caddy can also handle requests for multiple domains within a single Caddyfile block, which is useful for subdomains.
*.example.net {
root * /var/www/wildcard.example.net
file_server
}
This configuration would serve files from /var/www/wildcard.example.net for any subdomain of example.net (e.g., a.example.net, b.example.net).
The most surprising thing about Caddy’s automatic HTTPS is how it handles certificate issuance and renewal without any manual intervention. It uses the ACME protocol, the same one Let’s Encrypt uses, but it’s built directly into Caddy. When Caddy starts or reloads, it checks if it has valid certificates for the domains it’s configured to serve. If not, or if they’re nearing expiration, it initiates the ACME challenge (usually HTTP-01 or TLS-ALPN-01) with an ACME server to prove control over the domain, obtains the certificate, and stores it locally (typically in /var/lib/caddy/.local/share/caddy/pki/authorities/acme-v02.api.letsencrypt.org/user.json).
The one thing most people don’t realize is that Caddy’s default configuration automatically redirects all HTTP traffic to HTTPS. You don’t need to explicitly add a redirect directive for this; it’s enabled by default for any domain configured to serve over HTTPS. This means if someone types http://yourdomain.com, Caddy will seamlessly switch them to https://yourdomain.com.
The next step in mastering Caddy is exploring its advanced features like load balancing, request rewriting, and custom error pages.