Behind a load balancer, every request seems to come from the load balancer's IP. The socket address is the load balancer's, not the user's. So every user shares one limit, and a single attacker can lock everyone out.
The naive fix is to read the X-Forwarded-For header. This header lists the client IP and each proxy the request passed through. Reading it blindly is dangerous, because clients can set this header themselves. An attacker can send a different fake IP each time and bypass the limit.
The correct fix: configure the framework to trust only your known proxies. In Express, use the trust proxy setting with the exact number of hops. Then the client IP comes from the entry that your own proxy added.
IPs alone are weak keys anyway:
- Attackers rotate through botnets and residential proxies, so they have many IPs.
- Many real users share one IP, through NAT (many devices behind one router) or a mobile carrier.
So combine several keys:
- per IP
- per account identifier, such as the username
- per API key or user id on authenticated routes
- a global limit per endpoint
Return status 429 (Too Many Requests) with a Retry-After header. Log what was limited.
- Behind a proxy, all requests seem to come from the proxy's IP.
- Clients control
X-Forwarded-For, unless you trust your proxies precisely. - Configure the exact number of trusted proxy hops.
- Combine IP, account, API key and global limits.
- Return 429 with
Retry-After, and log limit events.