← Writing

Rate limiting behind Netlify Functions silently collapses without x-nf-client-connection-ip

Behind a serverless proxy, req.ip is the proxy — so every per-IP rate-limit key maps to one global bucket. Restore the real client IP from the platform header.

My login rate limiter worked perfectly in local dev and silently did the wrong thing in production. Behind Netlify’s proxy, req.ip is the proxy’s address — the same value for every visitor — so every per-IP rate-limit key collapsed into one global bucket. One user hitting the limit would lock out everyone; in practice it mostly meant the limiter throttled nobody correctly.

The fix is one middleware that restores the real client IP from the platform’s header before any limiter runs:

app.use((req, _res, next) => {
  const real = req.headers["x-nf-client-connection-ip"];
  if (typeof real === "string") Object.defineProperty(req, "ip", { get: () => real });
  next();
});

Every proxy platform has its equivalent (CF-Connecting-IP on Cloudflare, X-Forwarded-For handling elsewhere). The lesson generalizes: any per-IP logic is broken until you’ve verified what “IP” means on your actual deployment target.

← All writing Book a call →
Book a call → WhatsApp