Site icon GetPageSpeed

How to use multiple real IP headers with nginx

nginx

nginx

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:

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.

Exit mobile version