Caddyfile generator
Fill in a form, get a valid Caddyfile for reverse proxies, static sites and redirects.
Last updated
In short: To put an app behind Caddy with HTTPS, point a DNS record at your server, open ports 80 and 443, and write a site block with the domain and one line: reverse_proxy localhost:8080. Caddy fetches and renews the certificate by itself. This generator adds load balancing, health checks, basic_auth, IP allowlists, security headers and logging, and every output is tested with caddy validate.
Your input never leaves your browser. Everything is calculated on your device; nothing you type is sent to a server.
How does the Caddyfile generator work?
A Caddyfile is Caddy's configuration file, usually /etc/caddy/Caddyfile. It is a list
of site blocks: each starts with one or more addresses (a domain such as
app.example.com, a wildcard, or a port such as :8080), followed by
directives inside braces that say what to do with requests for that address.
reverse_proxy passes them to an app, file_server serves files from disk and
redir sends the visitor elsewhere. Every directive goes on its own line, with the opening
brace at the end of the address line.
HTTPS is automatic. For any public domain name, Caddy requests a certificate from Let's Encrypt or
ZeroSSL, renews it, and redirects HTTP to HTTPS, as long as the domain's DNS record points at the
server and ports 80 and 443 are reachable. For names only your LAN knows,
tls internal uses Caddy's own certificate authority instead. When the server cannot be
reached from the internet, or you want a wildcard certificate, the DNS challenge
proves ownership by creating a TXT record through your DNS provider's API; that needs a Caddy build
with the matching caddy-dns module.
The generator writes the parts in the order Caddy expects: the optional global options
block must come first (email, admin off), then a reusable
(security_headers) snippet, then one block per site. Named matchers such as
@denied not remote_ip 192.168.1.0/24 restrict a site to your own network. If Caddy
runs in a podman container, put it on the same podman network as your apps and use their container
names as upstreams, for example reverse_proxy jellyfin:8096.
To apply the result, save it, run caddy validate --config /etc/caddy/Caddyfile, then
caddy reload --config /etc/caddy/Caddyfile (or restart the service when the admin API
is off). caddy fmt --overwrite tidies the indentation of a file you edited by hand.
Worked examples
One self-hosted app with HTTPS
Input cloud.example.com → localhost:8080 gives a cloud.example.com block containing reverse_proxy localhost:8080
The whole config is the domain, an opening brace, one reverse_proxy line and a closing brace; the directive must sit on its own line. Caddy gets a certificate on first start, redirects HTTP to HTTPS and renews it automatically well before it expires. The generator also adds encode zstd gzip and a security headers snippet, which you can untick.
Several subdomains to several podman containers
Input jellyfin.example.com → jellyfin:8096, photos.example.com → immich-1:2283 + immich-2:2283 gives two site blocks; the second with lb_policy least_conn and health_uri /api/server/ping
Add one site per subdomain. When Caddy runs in a podman container on the same podman network as the apps, the container names resolve as hostnames, so the upstream is jellyfin:8096 rather than an IP. Two upstreams on one site are load balanced; the health check stops traffic to a replica that is down.
A static website with an access log
Input www.example.com, root /srv/www, log to /var/log/caddy/www.log gives root * /srv/www + file_server + log { output file /var/log/caddy/www.log }
root * sets the folder for every request and file_server serves it, with index.html as the directory index. The log rolls at 10 MiB and keeps 5 old files. Add a second site with mode Redirect to send example.com to www.example.com.
Frequently asked questions
Does Caddy need port 80 open to get a certificate?
Yes, for the default setup: Let's Encrypt must reach your server on port 80 (HTTP-01) or 443 (TLS-ALPN-01) from the internet. Behind a home router, forward both ports to the machine running Caddy and point the DNS A/AAAA record at your public IP. If you cannot open ports (CGNAT, or an internal-only service), use the DNS challenge, or tls internal for LAN-only names. Rootless podman cannot bind ports below 1024 by default; set net.ipv4.ip_unprivileged_port_start=80 or publish 8080/8443 and forward to those.
How do I use the DNS challenge with Caddy and Porkbun?
Build Caddy with the caddy-dns/porkbun module, give it an API key pair, and put a dns porkbun block inside tls. The stock image cannot do this: build one with xcaddy build --with github.com/caddy-dns/porkbun (in a Containerfile starting FROM caddy:2-builder). Keep the keys out of the Caddyfile: store them as podman secrets and expose them as environment variables (Secret=porkbun_key,type=env,target=PORKBUN_API_KEY in a quadlet), which the generated {env.PORKBUN_API_KEY} placeholders read. The DNS challenge is also the only way to get a wildcard certificate such as *.example.com.
What is the difference between handle_path and handle in Caddy?
handle_path strips the matched prefix before passing the request on; handle keeps the path unchanged. With handle_path /app/* a request for /app/login reaches the upstream as /login. Note that /app/* does not match /app without a trailing slash, so add redir /app /app/ above it. Many apps break under a sub-path anyway; a subdomain per app (what this generator produces) avoids the problem.
Does Caddy reverse_proxy support WebSockets?
Yes, with no extra configuration: reverse_proxy passes the Upgrade and Connection headers and keeps the connection open. By default Caddy closes WebSocket connections when you reload the config; set stream_close_delay (for example 5m) inside reverse_proxy if clients should survive reloads. See 'reverse_proxy' in the Caddy documentation.
How do I generate a password hash for Caddy basic_auth?
Run caddy hash-password and paste its output after the username in the basic_auth block. Without a local Caddy: podman run --rm -it docker.io/library/caddy:2 caddy hash-password, which prompts for the password so it does not land in your shell history. This page makes the same kind of bcrypt hash (cost 14) in your browser with bcryptjs; Caddy accepts its $2b$ hashes, which we test against Caddy 2.11. Never put the plain password in the Caddyfile.
Does Caddy set X-Forwarded-For automatically?
Yes. reverse_proxy sends X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host by default, so adding header_up lines for them only produces a warning. Some apps want X-Real-IP instead; tick the option to add header_up X-Real-IP {remote_host}. If another proxy or a CDN sits in front of Caddy, configure trusted_proxies so the client IP is taken from its headers.
