Caddy's configuration file, the Caddyfile, is famously short: a site block with a domain gets automatic HTTPS with no extra lines. Once Caddy is installed, most real work is writing the right directives. This guide collects tested Caddyfile examples for the setups people actually run.
Before you start
- Caddy installed as a service: install Caddy with automatic HTTPS.
- Domains pointing to the server and ports 80 and 443 (TCP, plus UDP 443 for HTTP/3) open.
- The file lives at
/etc/caddy/Caddyfile. Apply changes withsudo systemctl reload caddy.
Several sites in one Caddyfile
example.com, www.example.com {
root * /var/www/example.com
file_server
encode zstd gzip
}
blog.example.com {
root * /var/www/blog
file_server
}Each site gets its own certificate automatically. encode zstd gzip turns on compression.
Redirect www to the bare domain
www.example.com {
redir https://example.com{uri} permanent
}
example.com {
root * /var/www/example.com
file_server
}HTTP to HTTPS redirects happen automatically; you never write them.
PHP sites with php_fastcgi
shop.example.com {
root * /var/www/shop/public
php_fastcgi unix//run/php/php8.3-fpm.sock
file_server
encode gzip
}php_fastcgi is a shortcut that does what several Nginx blocks do: it passes .php files to PHP-FPM, sets the FastCGI parameters, and tries {path}, {path}/index.php and finally index.php, so WordPress and Laravel pretty URLs work out of the box. Make sure PHP-FPM's socket is readable by the caddy user (set listen.owner = caddy in the pool, or add caddy to the socket group).
Reverse proxy to an app
app.example.com {
reverse_proxy 127.0.0.1:3000
}Caddy sets X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host for you, and proxies WebSockets without extra config.
Load balancing with health checks
api.example.com {
reverse_proxy 10.0.0.11:8080 10.0.0.12:8080 {
lb_policy least_conn
health_uri /health
health_interval 10s
fail_duration 30s
}
}Proxy only part of a site
example.com {
handle_path /api/* {
reverse_proxy 127.0.0.1:4000
}
handle {
root * /var/www/example.com
file_server
}
}handle_path strips /api before proxying; use handle to keep the prefix. Blocks are mutually exclusive, like Nginx locations.
Security headers
(security) {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
-Server
}
}
example.com {
import security
root * /var/www/example.com
file_server
}The parenthesised block is a reusable snippet; -Server removes the Server header.
Protect a folder with a password
- Hash a password:
caddy hash-password. - Add:
basic_auth /admin/* { admin $2a$14$REPLACE_WITH_HASH }
(Older Caddy versions spell it basicauth.)
Access logs
example.com {
log {
output file /var/log/caddy/example.log {
roll_size 50mb
roll_keep 10
}
format json
}
}Caddy rotates the file itself.
Validate before reloading
caddy validate --config /etc/caddy/Caddyfile
caddy fmt --overwrite /etc/caddy/CaddyfileTroubleshooting
- Certificate not issued: DNS does not point here yet, or port 80/443 is blocked. Check
journalctl -u caddy. - PHP downloads instead of running:
php_fastcgimissing or socket path wrong. - 502 from reverse_proxy: the backend is not listening on that address.
FAQ: Caddyfile
Can Caddy read Nginx or Apache configs?
No. Rules must be rewritten in Caddyfile syntax, usually far shorter.
Does Caddy support .htaccess?
No. Put redirects and rules in the Caddyfile.
Is Caddy fast enough for production?
Yes, for most sites. Very high-traffic PHP sites may still prefer LiteSpeed with LSCache or Nginx with FastCGI cache.
