2 min readcaddyself-hosting

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.