Proxy foundations
Inspect forwarded headers
See how Host, X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host describe the original request.
10 minute lesson
The backend receives a new request from the proxy, not the client’s original one. From the backend’s point of view, every request now comes from the proxy’s address. That breaks anything that needs the real client IP or the original protocol — rate limiting, audit logs, redirect URLs.
Forwarded headers preserve selected client-facing context that the application may need. Caddy adds them to the upstream request automatically.
Log what the backend sees
Change the lab backend to print its incoming headers:
http.createServer((request, response) => {
console.log(request.headers)
console.log('peer:', request.socket.remoteAddress)
response.end('ok\n')
}).listen(4001, '127.0.0.1')
The extra request.socket.remoteAddress line matters. It shows the immediate network peer — who actually opened the TCP connection — which is a separate fact from anything a header claims.
Compare direct and proxied requests
Request the backend directly, then through Caddy:
curl http://127.0.0.1:4001/
curl http://127.0.0.1:8080/
The direct request shows a plain header set with host: 127.0.0.1:4001. The proxied request looks different:
host: '127.0.0.1:8080',
x-forwarded-for: '127.0.0.1',
x-forwarded-proto: 'http',
x-forwarded-host: '127.0.0.1:8080'
Read each one. Host is passed through unchanged, so the backend sees the hostname the client used. X-Forwarded-For carries the client’s IP address. X-Forwarded-Proto records whether the client-facing connection was http or https — essential once the proxy terminates TLS but talks plain HTTP to the backend. X-Forwarded-Host preserves the original host in setups that rewrite it.
In both cases the peer address printed by the backend is a loopback address. Through the proxy, that peer is Caddy, no matter what the headers say.
The trust problem
Now the uncomfortable part. Send a forged header directly to the backend:
curl -H "X-Forwarded-For: 203.0.113.99" http://127.0.0.1:4001/
The backend prints 203.0.113.99 as if it were real. Headers are just text the sender chose. Forwarded headers are trustworthy only when they come through a proxy boundary you control and configure. A later lesson makes that boundary explicit with trusted proxy configuration.
Lesson completed