Reverse Proxy Basics - One Front Door for Every Service
Status: Active
Last Updated: 2026-08-26
Category: Networking - Reverse Proxies
Prerequisites: dns-explained, ../security/tls-configuration
Time: 2 hours
Tags: reverse-proxy, tls-termination, routing, headers, caddy, traefik, nginx
Summary
Why a self-hosted fleet funnels all public traffic through one reverse proxy: TLS termination in one place, name- and path-based routing to many backends on private ports, and correct forwarded headers. Concepts here apply to Caddy (caddy-reverse-proxy), Traefik (traefik-v3-reverse-proxy), and nginx (nginx-configuration) alike.
๐ฏ What You'll Learn
By the end of this article, you'll be able to:
- โ Explain proxy vs reverse proxy vs port forward
- โ Design host-based vs path-based routing for a fleet
- โ Terminate TLS once and speak plain HTTP internally
- โ Set X-Forwarded-* headers correctly (and fix apps that break without them)
Table of Contents
- Context / Why This Matters
- What a Reverse Proxy Buys You
- Routing Models
- TLS Termination
- Forwarded Headers
- Troubleshooting & Common Pitfalls
- Next Steps / Ops Actions
- Sources & Related
- Change Log
Context / Why This Matters
The fogserv edge model is: Cloudflare โ one reverse-proxy host โ N internal services. Without it, every service would need its own public port, its own cert renewal, its own exposure in the firewall โ and Cloudflare's limited proxied ports make most of those unreachable anyway. The proxy concentrates ingress so the firewall has exactly one job on the public side (firewalls-nftables).
Internet โโ443โโโถ Cloudflare โโ443โโโถ Caddy/Traefik/nginx (edge)
โโ grafana.fogserv.cloud โ 127.0.0.1:3000
โโ git.fogserv.cloud โ 10.10.0.11:3000
โโ jellyfin.fogserv.cloudโ 10.10.0.21:8096
What a Reverse Proxy Buys You
| Capability | Effect |
|---|---|
| Single TLS endpoint | One ACME/DNS-01 setup, wildcard cert, uniform cipher policy (../security/certificate-fundamentals) |
| Name-based routing | Many services share 443; browsers see clean hostnames |
| Backend isolation | Services bind to localhost/LAN only; never publicly exposed directly |
| Central access control | Auth middleware, IP allowlists, rate limits applied per-route |
| Hiding topology | Visitors never learn backend IPs or ports |
A forward proxy sits in front of clients; a reverse proxy sits in front of servers. A plain router port forward does none of the above โ it just punches a hole.
Routing Models
Host-based (default choice) โ service.fogserv.cloud โ backend:
- Clean URLs, no app config needed, easy per-service policies.
- Cost: each service needs a DNS record (dns-management) and a cert name covered by your cert.
Path-based โ fogserv.cloud/grafana โ backend:
- Saves DNS names; but many apps assume they're served at
/and needbase_urlrewrites. Use only when the app supports a sub-path or you enjoy pain.
Decision rule: host-based unless there's a specific reason not to be.
TLS Termination
Terminate at the edge; talk plain HTTP to backends on the trusted LAN:
Browser โTLSโ Edge proxy โhttpโ grafana:3000 (internal network trusted segment)
This is acceptable when backends live on an isolated services VLAN/overlay (virtual-networks). If traffic crosses untrusted links (between sites), either route through the WireGuard tunnel (which encrypts anyway) or use re-encryption (https upstreams with internal CA certs). Never terminate at Cloudflare then forward plain HTTP across the internet โ that's the Flexible-mode trap from cloudflare-dns.
Forwarded Headers
Backends must learn the real client info or logs break, redirects go to wrong schemes, and CSRF checks fail. The edge sets:
X-Forwarded-For: 203.0.113.5 # real client
X-Forwarded-Proto: https # scheme the client used
X-Forwarded-Host: grafana.fogserv.cloud
Host: grafana.fogserv.cloud # usually preserved as-is
Backend side: configure apps to trust these headers only from the proxy IP (otherwise clients can spoof them). Grafana example:
[server]
root_url = https://grafana.fogserv.cloud
[auth]
; trust proxy only from edge
Practical Examples
Trace what the proxy is doing:
# From outside: full request path + headers returned
curl -sv https://grafana.fogserv.cloud/api/health -o /dev/null 2>&1 | grep -E "^< |^> Host"
# On the edge: confirm the backend answers before blaming the proxy
curl -s http://127.0.0.1:3000/api/health
# Confirm real client IPs reach backend logs (not 127.0.0.1)
ssh edge "tail -f /var/log/*proxy*" & curl -s https://grafana.fogserv.cloud >/dev/null
Add a new service checklist:
- Backend binds to
127.0.0.1or LAN IP only. - DNS record
name CNAME edge(proxied) โ cloudflare-dns. - Route block in Caddy/Traefik/nginx config.
- Firewall: nothing extra publicly; only the edge listens.
- Test:
curl -sv https://name.fogserv.cloud/api/health.
Troubleshooting & Common Pitfalls
| Problem | Cause | Fix |
|---|---|---|
| App redirects to http:// or wrong port | Missing/untrusted X-Forwarded-Proto | Set headers at edge; enable app's "behind reverse proxy" mode |
| Logs show every client as proxy IP | Headers set but app doesn't read them | Configure trusted proxies in app |
| 502 Bad Gateway | Backend down/wrong port/binding to 127.0.0.1 while proxy targets LAN IP | curl backend directly from edge host first |
| Works via HTTP but breaks over HTTPS | Mixed content: app emits absolute http:// asset URLs | Set root_url/base URL; check CSP errors in browser console |
| Websockets disconnect | Proxy lacks upgrade handling | Enable websocket support (Caddy/Traefik auto; nginx needs Upgrade headers map) |
| Two proxies fight over port 80/443 | Legacy nginx + new Caddy on same host | One edge proxy per host; move others behind it |
Next Steps / Ops Actions
- Concrete configs: caddy-reverse-proxy, nginx-configuration, traefik-v3-reverse-proxy
- Cert strategy (wildcard via DNS-01): caddy-reverse-proxy, ../security/letsencrypt-automation
Sources & Related
External references consulted:
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling
- https://mdn.io/Web/HTTP/Headers/X-Forwarded-For
Related knowledge-base articles:
Change Log
2026-08-26
- Initial creation: routing models, TLS termination, forwarded headers, service checklist.