Caddy is a web server that obtains and renews TLS certificates on its own. For a reverse proxy in front of a few services, that removes most of the work: no Certbot, no renewal timer, no certificate paths in the config.

Ringkasan singkat
- Automatic HTTPS with no Certbot step.
- Headers such as X-Forwarded-For are sent for you.
- Raise
request_body max_sizefor uploads. - Validate before reload; old config stays on failure.
This guide puts Caddy in front of two local services, adds security headers, and explains the parts that can still go wrong.
Prerequisites
- Ubuntu with a public IP and ports 80/443 reachable.
- A DNS A record pointing to the server.
- Services listening on localhost, for example
127.0.0.1:3000.
1. Install Caddy
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 -y caddy
caddy version
systemctl status caddy --no-pager
2. Write the Caddyfile
sudo nano /etc/caddy/Caddyfile
app.example.com {
reverse_proxy 127.0.0.1:3000
}
api.example.com {
reverse_proxy 127.0.0.1:8080
}
That is the entire configuration. Caddy sees the hostname, requests a certificate, redirects HTTP, and proxies the request. Four lines replace a server block plus a Certbot run.
3. Validate and reload
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -I https://app.example.com
validate catches syntax errors before they reach production. If the reload fails, Caddy keeps serving the previous configuration — useful, but it also hides mistakes, so read the output.
4. Proxy headers
Caddy forwards Host, X-Forwarded-For, X-Forwarded-Proto, and X-Real-IP automatically. Most applications therefore work without extra directives, and logs show real client addresses.
5. Uploads and timeouts
app.example.com {
reverse_proxy 127.0.0.1:3000
request_body {
max_size 50MB
}
}
Large uploads fail with 413 without this. Do not remove the limit entirely; pick a size that matches the application.
6. Security headers
app.example.com {
reverse_proxy 127.0.0.1:3000
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
}
-Server removes the version banner from responses. HSTS should only be enabled after HTTPS is confirmed working on every subdomain you own.
7. Static files and path routing
example.com {
root * /var/www/site
file_server
handle_path /api/* {
reverse_proxy 127.0.0.1:8080
}
}
handle_path strips the prefix before forwarding, which is what most APIs expect.
8. Logs
journalctl -u caddy -f
journalctl -u caddy --since "10 min ago" | grep -i error
9. Redirects and the www variant
www.example.com {
redir https://example.com{uri} permanent
}
Pick one canonical hostname and redirect the other. Two working hostnames mean two indexed versions of the same page, which splits ranking signals instead of strengthening them.
10. Restrict access to an admin app
admin.example.com {
basic_auth {
admin $2a$14$HASHED_PASSWORD
}
reverse_proxy 127.0.0.1:9000
}
Generate the hash with caddy hash-password. Basic authentication over HTTPS is acceptable for a single operator, but a VPN remains the stronger option: it removes the login page from the public internet entirely.
11. Several upstreams and health checks
app.example.com {
reverse_proxy 10.0.0.11:3000 10.0.0.12:3000 {
lb_policy round_robin
health_uri /health
health_interval 10s
}
}
Caddy stops sending traffic to an upstream that fails the health check and brings it back when it recovers. This is what turns two application servers into something more resilient than one, without adding a separate load balancer.
Keep the health endpoint cheap. A check that queries the database every ten seconds becomes its own incident.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Certificate not issued | DNS not pointing at the server | dig +short app.example.com |
| 502 Bad Gateway | Upstream down or wrong port | ss -lntp | grep 3000 |
| 413 on upload | Default body limit | request_body max_size |
| Config ignored after edit | Reload not run | caddy validate then reload |
| Too many certificates | Repeated failed issuance | Fix DNS first; use staging for tests |
12. What Caddy does not do for you
It handles certificates, HTTP/2, and proxying. It does not patch applications, rate-limit abusive clients, or replace a firewall. Ports 80 and 443 still need to be open; everything else can stay closed. If you later move a service to another host, the Caddyfile is usually the only file that changes.
FAQ
Caddy or Nginx?
Caddy wins on simplicity: automatic HTTPS, few directives, readable config. Nginx has more tuning options and a larger body of guides. For a handful of self-hosted services, Caddy is usually faster to get right.
Where are certificates stored?
In Caddy's data directory, managed automatically. You never edit certificate paths.
Can it proxy WebSockets?
Yes, by default, with no extra configuration.
Does it work behind Cloudflare?
Yes. Use DNS-only while issuing the first certificate, or configure the DNS challenge if you keep the proxy enabled.
Related guides:
0 comments:
Post a Comment