What a Reverse Proxy Does to Your View
A reverse proxy sits between the internet and your app. The browser talks to the proxy; the proxy talks to you. So the connection your server sees originates from the proxy's IP, every time. On a single machine behind one proxy, req.socket.remoteAddress is the same address for all traffic — useless for telling visitors apart.
The proxy therefore passes the original details in forwarded headers:
X-Forwarded-For: client, proxy1, proxy2— a comma list, left-to-right, appended by each hop.
X-Forwarded-Proto— the scheme the client actually used (fixes "is this HTTPS?" behind a TLS-terminating edge).
X-Forwarded-Host/ theHostyou see — which the app uses for correct URLs and redirects.
Cloudflare also sets CF-Connecting-IP (a single value: the client it saw). That is usually the header you want when you sit behind Cloudflare — not the forgeable X-Forwarded-For.
``js`
// trust only the hop you control, then read the leftmost XFF entry
app.set('trust proxy', 1);
const ip = req.headers['cf-connecting-ip'] || req.ip;
The Trust Trap
Headers are attacker-controlled. If your code blindly takes X-Forwarded-For[0] as the client, anyone can spoof it — bypass rate limits, fake a geo, poison your logs. The rule:CF-Connecting-IP behind Cloudflare, the leftmost XFF entry behind a single known proxy.
Express's trust proxy setting exists for exactly this — set it to the number of hops, not true (which trusts everyone).
Everything That Silently Breaks
- Rate limiting keyed on the proxy IP → the whole site shares one bucket, or one abusive key exhausts it for everyone. Fix the IP source, then limit per real client.
- Redirect loops — TLS ends at the proxy, your app sees http
, issues an "https" redirect, the proxy sendshttpagain… loop. ReadX-Forwarded-Proto.
- Audit logs / abuse reports pointing at your CDN's datacenter, not the culprit.
- Cookies marked Secure
droppedbecause the app thinks it's plain HTTP.
A Small Checklist
- Know your topology: is there exactly one proxy in front? Count the hops.
- Set framework trust accordingly (trust proxy
= hop count).
- Read the provider's dedicated client-IP header when one exists.
- Validate scheme from X-Forwarded-Proto` for cookies, HSTS, and redirects.
- Test with a request from a known remote IP and confirm it lands as that IP in logs — if not, your trust config is wrong.
Once your app agrees on who the client actually is, rate limits, geo, and logs all start agreeing with it too.