30

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?

2
  • Do not trust HTTP headers. It’s not something you can’t set to anything you want. May 31, 2021 at 21:11
  • HTTP headers can and should be trusted exactly because you can (and should) set it to anything you want (on the web server for the upstream).
    – EugZol
    Mar 2, 2023 at 20:33

3 Answers 3

35

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;
8
  • I just came to the same conclusion, and in a quick test it works fine. Thanks for pointing out the increased memory usage. Mar 13, 2013 at 14:59
  • 4
    Beware there may be serious security concerns to this: blog.ircmaxell.com/2012/11/anatomy-of-attack-how-i-hacked.html
    – ircmaxell
    Apr 8, 2015 at 19:34
  • Note that the information in that blog post regarding symfony was addresses with the following: symfony.com/doc/current/components/http_foundation/… Sep 21, 2015 at 13:44
  • 8
    If you use realip module, $binary_remote_addr variable gets set correctly.
    – Cenk Alti
    Jan 10, 2017 at 17:35
  • 2
    @КонстантинВан 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, 2021 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.

1
  • 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. Jul 9, 2019 at 21:33
0

Just as an example and demonstration of setting up real_ip using the header.

  • We precise a header to take the ip from

  • The client ip will be then overridden with that value

  • Then we can keep using $binary_remote_addr

    • not always x-forwarded-for is used.
  • That method can help too with logs (default format will work as $remote_addr now it's set to the overridden value)

  • Nginx Module to use: ngx_http_realip_module Example of setting that up with Cloudflare (more than one server source, or ip range)

# Ipv4
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;

# Ipv6
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;

real_ip_header CF-Connecting-IP;
  • you can have that in a file. ex: cloudflare_ip.conf and add it to conf.d

You can see the usage of

  • set_real_ip_from

    • only the requests coming from those ip's, the overriding will be applied to
  • real_ip_header

  • The header containing the client ip connecting through Cloudflare is CF-Connecting-IP

  • Cloudflare guide

And for the rate limit

limit_req_zone $binary_remote_addr zone=limit_ip_10rs:10m rate=10r/s;
  • We keep using $binary_remote_addr

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .