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. But the real_ip_header directive does not support variables.
In the following example solution, some users access the site through Cloudflare and sometimes through DDos Guard service.
We have to account for the fact that the realip module does not support variables in real_ip_header. The solution involves:
- Creating
geomaps designating values used in deciding which HTTP headers to use for real IP value - Setting up a single synthetic input HTTP header (not actually coming from CDN) with the value of the real IP, for
real_ip_headerdirective.
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:
real_ip_recursive on;
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;
How it works
For each request:
geo $realip_remote_addrmarks whether the TCP peer belongs to DDoS-Guard or Cloudflaremapuses those flags to choose the right header:X-Real-IP,CF-Connecting-IP, or the defaultX-Forwarded-For– single variable$real_ipis set up to tell NGINX which HTTP headers to use for real IP- in the
REWRITEphase,more_set_input_headers "X-IP: $real_ip";turns that variable into a synthetic headerX-IP
The more_set_input_headers runs at the end of the REWRITE phase, while real_ip_header is enforced by the realip module in the later PREACCESS phase. This is why setting a synthetic input header in rewrite still affects remote_addr correctly.
Caveats
X-IP should be an arbitrary synthetic header name to make it work; do not use any of the actual header names your CDNs use for real IP designation. Otherwise, NGINX will finalize real IP detection at POST_READ phase, and the synthetic header won’t be used.
In other words, it is essential to use different, arbitrary name for our rewritten real client header, e.g. real_ip_header X-IP;.

