Remembering http://192.0.2.130:3000 is not a service-discovery strategy. An
internal reverse proxy gives applications stable names, centralizes TLS and
access logging, and lets backends move without changing bookmarks.
Caddy is a convenient fit because a small Caddyfile expresses the common case clearly. The proxy is still a security boundary, so keep the first design small.
Prepare names and routes #
Create internal DNS records such as grafana.home.arpa and wiki.home.arpa
that point to the proxy, not directly to the applications. Give the proxy a
stable address and make sure it can reach only the backend ports it needs.
A minimal Caddyfile for plaintext internal HTTP is:
1http://grafana.home.arpa {
2 reverse_proxy 192.0.2.130:3000
3}
4
5http://wiki.home.arpa {
6 reverse_proxy 192.0.2.140:8080
7}
Validate before reloading:
1caddy validate --config /etc/caddy/Caddyfile
2sudo systemctl reload caddy
Test through the intended name so the Host header selects the correct site:
1curl -I http://grafana.home.arpa
Choose a TLS model deliberately #
Publicly trusted certificates require a name under a domain you control and a
supported challenge path. A private home.arpa name cannot receive a public
certificate. Caddy can issue certificates from its internal CA instead:
1grafana.home.arpa {
2 tls internal
3 reverse_proxy 192.0.2.130:3000
4}
Every client must trust that CA through an intentional distribution process. Do not click through certificate warnings or copy a root key casually. For a small lab, plaintext on a restricted management network may be more honest than poorly operated private PKI; for sensitive applications, invest in the trust model.
Preserve the application boundary #
A reverse proxy does not automatically add authentication. If the backend has its own login, keep it enabled. If central authentication is added later, verify which paths, APIs, and WebSocket connections it covers, and prevent clients from bypassing the proxy by reaching the backend directly.
Many applications need to know their external URL or trusted proxy addresses. Configure those settings in the application rather than adding random forwarded headers. Caddy passes common proxy headers, while the backend must decide which proxies it trusts.
Restrict where the proxy listens #
Internal does not mean harmless. Bind the service to the intended interfaces or
enforce the boundary with the host firewall. If the proxy also serves public
sites, separate site policy carefully and confirm that private names cannot be
reached through the public listener merely by changing the Host header.
The backend should accept connections from the proxy and management path, not from every client VLAN. That preserves the proxy as the controlled entry point and makes bypass attempts visible. Keep a documented emergency path for administration that does not depend on the user-facing route.
When an application needs WebSockets, large uploads, or streaming responses, test that behavior explicitly. A successful homepage proves very little about timeout and body-size assumptions during real use.
Make failures observable #
Monitor the proxy and the backend separately. A successful TCP connection to Caddy says nothing about the wiki database. Check an application-level endpoint through the public internal name, and keep logs long enough to correlate a failure without retaining sensitive requests indefinitely.
Back up the Caddyfile and any private PKI material according to their sensitivity. Validate configuration during deployment, reload instead of restarting when possible, and retain console access to the backend for proxy outages.
Add one service at a time. After each route, verify the expected name, redirect, certificate chain, login, backend address, and a deliberately invalid path. Keep the previous validated Caddyfile so a syntax-correct but behaviorally wrong change can be rolled back quickly.
Updates deserve the same discipline. Read release notes, validate the current configuration with the new binary, take a relevant backup, and check every route after reload. Automatic certificate handling reduces certificate work; it does not remove the need to monitor expiration and renewal errors.
The result should reduce complexity for clients without hiding it from the operator: DNS selects the proxy, the hostname selects a route, and a narrow firewall path reaches the application.