Setting up Caddy as a static file server
For a static site I want three things from a web server: HTTPS without thinking about it, compression, and a config file I can still read a year later. Caddy gives all three with very little configuration. This is the setup this site runs on.
Install
On Debian and Ubuntu, use the official repository so updates arrive through apt:
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 caddy
The package installs a systemd unit that runs as the caddy user and reads /etc/caddy/Caddyfile.
A minimal Caddyfile
example.com, www.example.com {
root * /var/www/example.com
encode zstd gzip
file_server
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
@assets path /assets/*
header @assets Cache-Control "public, max-age=604800"
handle_errors {
rewrite * /404.html
file_server
}
}
That is the whole thing. When the site block names a real domain, Caddy gets a certificate from Let's Encrypt (or ZeroSSL as a fallback), renews it before expiry, and redirects HTTP to HTTPS. Port 80 must be reachable for the HTTP-01 challenge unless you configure DNS challenges instead.
Things worth knowing
Directory indexes
file_server serves index.html for a directory automatically, so a post at /posts/my-post/index.html is reachable as /posts/my-post/. Requests without the trailing slash are redirected to the canonical form.
Error pages
Inside handle_errors the original status code is kept, so the rewrite above returns your 404.html with a real 404 status — search engines will not index missing pages as soft 200s.
Validate before reload
caddy validate --config /etc/caddy/Caddyfile
caddy fmt --overwrite /etc/caddy/Caddyfile
sudo systemctl reload caddy
reload applies the new config without dropping connections. If the config is invalid, Caddy keeps running the old one and logs the error.
Logs
Access logs are off by default. Add log { output file /var/log/caddy/access.log } to the site block if you need them, and remember to rotate them — Caddy's file writer rolls logs at 100 MB by default.
Deploying
Since the site is just files, deploying is a copy:
rsync -av --delete public/ server:/var/www/example.com/
No restart is needed; Caddy serves whatever is on disk. If you cache aggressively, put a content hash in asset file names so a new deploy is picked up immediately.