β nginx β caddy
β’ 825 words β’ 4 min β’ updated
Problem statement: Replace nginx with caddy for miniflux on my Alpine
Linux VPS host.
First, install caddy:
doas apk add caddyNext, migrate the existing nginx config to caddy.
The stock config:
# Caddy's configuration file
# see: https://caddyserver.com/docs/caddyfileThe new config:
# Caddy's configuration file
# see: https://caddyserver.com/docs/caddyfile
{miniflux_host}.perrotta.dev {
reverse_proxy * {
to 127.0.0.1:8080
header_up Host {host}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}Stop nginx:
doas service nginx stopStart caddy:
% doas service caddy start
% doas service caddy status
* status: startedNow I tried to access my server via a web browser, and it worked out-of-the-box :)
I can see information about the SSL certificate in my web browser:
Issued On Thursday, December 25, 2025 at 1:41:33β―AM
Expires On Wednesday, March 25, 2026 at 1:41:32β―AMIt was issued just a few seconds ago!
How to view logs?
I expect to find them at /var/log/caddy but there’s nothing there.
I need to add an explicit directive to enable logging:
{miniflux_host}.perrotta.dev {
log {
output file /var/log/caddy/miniflux.log
}
reverse_proxy * {
to 127.0.0.1:8080
header_up Host {host}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}Then reload caddy:
% doas service caddy reload
~ # service caddy reload
* Reloading Caddy web server ...
2025/12/25 05:49:04.531 INFO using config from file {"file": "/etc/caddy/Caddyfile"}
2025/12/25 05:49:04.532 WARN caddyfile Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream
2025/12/25 05:49:04.532 WARN caddyfile Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/12/25 05:49:04.532 INFO adapted config to JSON {"adapter": "caddyfile"}
2025/12/25 05:49:04.533 WARN Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies {"adapter": "caddyfile", "file": "/etc/caddy/Caddyfile", "line": 4}
Error: sending configuration to instance: caddy responded with error: HTTP 400: {"error":"loading config: loading new config: setting up custom log 'log0': opening log writer using \u0026logging.FileWriter{Filename:\"/var/log/caddy/miniflux.log\", Mode:0x0, Roll:(*bool)(nil), RollSizeMB:0, RollCompress:(*bool)(nil), RollLocalTime:false, RollKeep:0, RollKeepDays:0}: open /var/log/caddy/miniflux.log: permission denied"}Let’s address these issues:
/etc/caddy % doas caddy fmt --overwriteThe formatting warning is gone:
% doas service caddy reload
* Reloading Caddy web server ...
2025/12/25 05:53:06.079 INFO using config from file {"file": "/etc/caddy/Caddyfile"}
2025/12/25 05:53:06.079 WARN caddyfile Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream
2025/12/25 05:53:06.079 WARN caddyfile Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/12/25 05:53:06.080 INFO adapted config to JSON {"adapter": "caddyfile"}
Error: sending configuration to instance: caddy responded with error: HTTP 400: {"error":"loading config: loading new config: setting up custom log 'log0': opening log writer using \u0026logging.FileWriter{Filename:\"/var/log/caddy/miniflux.log\", Mode:0x0, Roll:(*bool)(nil), RollSizeMB:0, RollCompress:(*bool)(nil), RollLocalTime:false, RollKeep:0, RollKeepDays:0}: open /var/log/caddy/miniflux.log: permission denied"}Next, logging:
% doas mkdir /var/log/caddy
% doas touch /var/log/caddy/miniflux.log
% doas chown -R caddy:caddy /var/log/caddyNow it’s much better:
% doas service caddy reload
* Reloading Caddy web server ...
2025/12/25 05:56:11.041 INFO using config from file {"file": "/etc/caddy/Caddyfile"}
2025/12/25 05:56:11.041 WARN caddyfile Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream
2025/12/25 05:56:11.041 WARN caddyfile Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/12/25 05:56:11.042 INFO adapted config to JSON {"adapter": "caddyfile"}Regarding the two “unnecessary” configs, just remove them from the Caddyfile:
- header_up X-Forwarded-For {remote}
- header_up X-Forwarded-Proto {scheme}
And here’s the cleanest possible startup:
% doas service caddy reload
* Reloading Caddy web server ...
2025/12/25 06:22:17.476 INFO using config from file {"file": "/etc/caddy/Caddyfile"}
2025/12/25 06:22:17.478 INFO adapted config to JSON {"adapter": "caddyfile"}How did I know which user to chown to? /etc/init.d/caddy has the following
line:
command_user=caddy:caddy…though it wouldn’t have been difficult to guess it anyways.
I can see some logs at /var/log/caddy/miniflux.log already.
Use tail -f or tspin -f (from tailspin) for live log tailing.
LLM security analysis, for completeness:
I reviewed /etc/caddy/Caddyfile. Overall, it's a straightforward reverse proxy for {miniflux_host}.perrotta.dev to 127.0.0.1:8080 with logging to
/var/log/caddy/miniflux.log. Caddy will automatically manage HTTPS and pass the appropriate headers upstream, and Caddy's defaults already
set the Host and X-Forwarded-* headersβso explicitly setting them is optional but not harmful.
Security considerations:
- Ensure the local Miniflux instance is bound to loopback only (127.0.0.1) so it's not exposed directly.
- Confirm /var/log/caddy/ and the log file are readable by the Caddy user but not world-readable if the logs might contain sensitive data.
- If you ever place Caddy behind another proxy (e.g., Cloudflare), configure trusted_proxies in Caddy to ensure X-Forwarded-* is accurate.
Beyond that, the config is minimal and doesn't expose extra surfaces.Last step: let’s make caddy auto start, and disable nginx startup + uninstall it:
doas rc-update del nginx
doas rc-update add caddy
doas apk del nginxOut of curiosity: the certificates live here:
/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/{miniflux_host}.perrotta.dev/There are three files:
- crt and key: SSL / TLS
- json: configuration / metadata for let’s encrypt
And we’re done!
Backlinks
- UptimeKuma (Dec 27, 2025)