28

I'm looking into rate-limiting using nginx's HttpLimitReqModule. However, requests are all coming from the same IP (a loadbalancer), with the real IP address in the headers.

Is there a way to have nginx rate-limit based on the ip in the X-Forwarded-For header instead of the ip of the source?

John Brodie
  • 383
  • 1
  • 3
  • 6

2 Answers2

34

Yes, typical rate-limiting configuration definition string looks like:

 limit_req_zone  $binary_remote_addr zone=zone:16m rate=1r/s;

where $binary_remote_addr is the unique key for limiter. You should try changing it to $http_x_forwarded_for variable which gets the value of X-Forwarded-For header. Although this will increase memory consumption because $binary_remote_addr is using compressed binary format for storing IP addresses and $http_x_forwarded_for is not.

 limit_req_zone  $http_x_forwarded_for zone=zone:16m rate=1r/s;
markdsievers
  • 103
  • 4
Andrei Mikhaltsov
  • 2,987
  • 1
  • 22
  • 31
  • I just came to the same conclusion, and in a quick test it works fine. Thanks for pointing out the increased memory usage. – John Brodie Mar 13 '13 at 14:59
  • 4
    Beware there may be serious security concerns to this: http://blog.ircmaxell.com/2012/11/anatomy-of-attack-how-i-hacked.html – ircmaxell Apr 08 '15 at 19:34
  • Note that the information in that blog post regarding symfony was addresses with the following: http://symfony.com/doc/current/components/http_foundation/trusting_proxies.html – calumbrodie Sep 21 '15 at 13:44
  • 8
    If you use realip module, `$binary_remote_addr` variable gets set correctly. – Cenk Alti Jan 10 '17 at 17:35
  • What is 16m in this context? – Esqarrouth Mar 18 '21 at 11:01
  • @Esqarrouth The size of shared memory, namely “_zone_,” for that. 16 MiB. – Константин Ван May 31 '21 at 21:07
  • **Do not trust HTTP headers. It’s not something you can’t set to anything you want.** – Константин Ван May 31 '21 at 21:09
  • 1
    @КонстантинВан you can trust request headers if you know for sure they are always set or cleaned by a trusted reverse proxy/load balancer. Reject all non-proxied connections with a firewall or non-public network, or use a whitelist (on the original `$remote_addr`) for header usage like the RealIP module does. – Lukas Jul 11 '21 at 15:32
6

The limit_req_zone directive defines the variable to be used as key for request grouping.
Usually, the $binary_remote_addr is used rather than $remote_addr because it is smaller and saves space.

Maybe you alternatively want to use the ngx_http_realip_module.
This will rewrite the remote address variables to the address provided in a custom header and will also make logging and other variable usage easier.

aland
  • 172
  • 1
  • 7
Lukas
  • 984
  • 5
  • 14
  • 1
    +1 for RealIP module. When using this module, `$binary_remote_addr` and `$remote_addr` are set to the value of your configured header, typically `X-Forwarded-For` - so your standard variables are now the "real client IP address". Run `nginx -V` to see if NGINX was built with `--with-http_realip`. Then config is as simple as: `set_real_ip_from 10.0.0.0/8;` `real_ip_header X-Forwarded-For;` , where the CIDR range is that of your upstream load balancer which is setting the `X-Forwarder-For` header. – markdsievers Jul 09 '19 at 21:33