Skip to content

trust proxy

Severity: highApplies to: Express 4.xApplies to: Express 5.xFacts last verified 2026-07-26 against Express 5.2.1 · express-rate-limit 8.6.0

trust proxy decides whether Express believes X-Forwarded-For and X-Forwarded-Proto. It defaults to false, and it has two wrong values rather than one — which is why this page exists separately from the cookie and rate-limit pages that both depend on it.

Value Result
false (default) req.ip is the proxy. Secure cookies never send. One shared rate-limit bucket.
true req.ip is whatever the client claims. Trivially spoofable.
1 / 'loopback' / a specific address Correct: trust exactly the hops you operate
the fix
// Trust exactly one proxy hop — the one you run.
app.set("trust proxy", 1);

The number is a hop count, not a boolean-ish. 1 means “take the last entry in X-Forwarded-For”, which is the value your own proxy appended and the only one a client cannot forge. If you run two proxies (a CDN in front of a load balancer), the number is 2.

verify it workedRun this in: http response
Terminal window
curl -sI -H 'X-Forwarded-For: 1.2.3.4' https://example.com/whoami

Expose req.ip on a debug route temporarily, or read it from your access logs. If a forged header changes what the application records, the setting is too permissive.

if you see this
The Express 'trust proxy' setting is true, which allows anyone to trivially bypass IP-based rate limiting.

express-rate-limit refuses to treat `true` as a valid production configuration, because it makes req.ip attacker-controlled.

Read in: express-rate-limit source/validations.ts — ERR_ERL_PERMISSIVE_TRUST_PROXY
The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default). This could indicate a misconfiguration which would prevent express-rate-limit from accurately identifying users.

A proxy is in front of the app but Express does not know it, so req.ip is the proxy's address and every visitor shares one rate-limit bucket.

Read in: express-rate-limit source/validations.ts — ERR_ERL_UNEXPECTED_X_FORWARDED_FOR

true is the dangerous one, and a real library refuses it

Section titled “true is the dangerous one, and a real library refuses it”

trust proxy: true means “trust the whole chain”, so Express takes the leftmost entry of X-Forwarded-For — the part a client wrote. Anyone can then present any IP they like.

express-rate-limit treats this as a configuration error rather than a preference, and its message says why: it “allows anyone to trivially bypass IP-based rate limiting.” That is a library declining to operate on a setting it considers unsafe, which is about as clear a signal as this ecosystem gives.

Note that both failure directions are silent in Express itself. Nothing in Express warns on either value; the warnings above come from express-rate-limit noticing.

Three separate controls quietly rest on trust proxy being right:

  • Secure cookiessecure: true needs X-Forwarded-Proto to be believed, or the cookie is never sent. See session cookies.
  • Rate limiting — keyed on req.ip. Too strict and everyone shares one bucket; too permissive and the limit is bypassable. See rate limiting.
  • Anything IP-based — audit logs, allow-lists, geo rules, abuse heuristics.

That shared dependency is why the threat page for this cluster is about this setting rather than about a vulnerability.

before you ship this

Changing this changes what every downstream consumer of req.ip sees, including your logs. Expect the client addresses in your access logs to change the moment you deploy it — that is the fix working, but it will look like a logging bug if nobody is warned.

The setting is only as trustworthy as the proxy. Express believing the last hop is correct only if that proxy overwrites or appends X-Forwarded-For itself. A proxy that passes a client-supplied header through untouched makes any value here a formality.