fbpx

NGINX

How to use multiple real IP headers with nginx

by , , revisited on


We have by far the largest RPM repository with NGINX module packages and VMODs for Varnish. If you want to install NGINX, Varnish, and lots of useful performance/security software with smooth yum upgrades for production use, this is the repository for you.
Active subscription is required.

In some cases, you would have traffic flowing to your NGINX instance from two different cloud services / load balancers.
Some users will visit your site via service A, and other users coming from service B.

Then there is need to configure NGINX properly in order to log the correct visitor IP. When using load balancers, the connecting machine’s IP is not the end visitor’s IP address.

The standard means for communicating end visitor’s IP address is by supplying it in an HTTP header, commonly X-Forwarded-For. However, you may stumble on a case when the two different services will use different headers, e.g. X-Real-IP and CF-Connecting-IP.

NGINX is very flexible with its map and geo directives. And the real_ip_header directive can be set to a variable.

Let’s put those great features together and not without some duplication, achieve completion for this tricky task.
In the following example solution, some users access the site through Cloudflare and sometimes through DDos Guard service.

Non-working solution

I keep this obvious solution for educational purpose. It doesn’t work! See reasons why below.

The following goes into http {} section of your nginx.conf:

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;
# .. put all Cloudflare ranges like above

# ddos-guard.net range:
set_real_ip_from 186.2.160.0/24;

# DDoS Guard
geo $use_x_real_ip {
  default 0;
  186.2.160.0/24 1;
}

# Cloudflare
geo $use_x_cf_connecting_ip {
  default 0;
  103.21.244.0/22 1;
  103.22.200.0/22 1;
  103.31.4.0/22 1;
  # all other Cloudflare's ranges ...
}

map "$use_x_real_ip:$use_x_cf_connecting_ip" $real_ip_header {
  default 'X-Forwarded-For';
  "1:0" 'X-Real-Ip';
  "0:1" 'CF-Connecting-IP';
}

real_ip_header $real_ip_header;

How this is flawed:

  • The realip module does not really support variables for real_ip_header header. When we pass $real_ip_header, then that’s what it actually receives – the raw string “$real_ip_header”
  • The geo module works with $remote_addr by default. And this variable gets rewritten by realip module! So our geo maps had to use original connecting (load balancer’s) IP address, which is available in $realip_remote_addr variable

Working solution

The working solution accounts for the fact that the realip module does not support variables in real_ip_header. Thus, we have to rewrite X-Forwarded-For with the value of the relevant headers. We also fix our geo maps to look into the correct value for connecting IP.

First, make sure you have installed Headers More module. If you have installed NGINX from our repository, this can be done via:

yum install nginx-module-headers-more

Then load it at the top of your nginx.conf:

load_module modules/ngx_http_headers_more_filter_module.so;

The following goes into http {} section of your nginx.conf:

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;
# .. put all Cloudflare ranges like above

# ddos-guard.net range:
set_real_ip_from 186.2.160.0/24;

# DDoS Guard
geo $realip_remote_addr $use_x_real_ip {
  default 0;
  186.2.160.0/24 1;
}

# Cloudflare
geo $realip_remote_addr $use_x_cf_connecting_ip {
  default 0;
  103.21.244.0/22 1;
  103.22.200.0/22 1;
  103.31.4.0/22 1;
  # all other Cloudflare's ranges ...
}

map "$use_x_real_ip:$use_x_cf_connecting_ip" $real_ip {
  default $http_x_forwarded_for;
  "1:0" $http_x_real_ip;
  "0:1" $http_cf_connecting_ip;
}

more_set_input_headers "X-IP: $real_ip";
real_ip_header X-IP;

Note that in this solution it is essential to use different, arbitrary name for our rewritten real client header, e.g. real_ip_header X-IP;. Otherwise NGINX would not be able to lookup maps properly, and the result would be preference toward the default (X-Forwarded-For) even from networks where we care about other headers.

  1. Dmitry

    Have you actually tried it yourself? Variables in real_ip_header do not work.

    Reply
  2. Peter

    Confirming what Dmitry said, this doesn’t work.

    Reply
    • Danila Vershinin

      Hi Peter & Dmitry,

      Indeed 🙂 I have played around with it and came up with something that actually works in my testing. Have a look at the working solution posted above!

      Reply
  3. Andrew

    This won’t work since more_set_input_headers happens in the rewrite phase, but the realip module is evaluated in the post read phase.

    Reply
    • Satheesh

      Yep, tested and ended up facing the issue where, headers created are not read by the setting “real_ip_header”.

      Reply
  4. hell

    nice for ip spoof (X-Forwarded-For)

    Reply
  5. JG

    Is there a way to map the Forwarded header from here (https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) to the real_ip_header . I can’t seem to get it work with the headers_more library.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

This site uses Akismet to reduce spam. Learn how your comment data is processed.