点解此处查看视频教程
当你的网站使用多层Nginx代理(如CDN + 负载均衡 + 后端服务器),客户端真实IP总是被覆盖?日志里全是代理服务器的IP?我们应该如何在这种情况下透传客户端真实IP呢?
环境
网络架构图

在proxy-3上准备后端程序并启动,后端服务将监听在8080端口。
1
2
3
|
wget -O /opt/http_server https://tools.snoopyops.top/file/http_server
chmod a+x /opt/http_server
/opt/http_server
|
所有服务器Nginx日志格式均为默认格式。
1
2
3
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
|
remote_addr
在Nginx日志中第一个字段就表示remote_addr,先对Nginx不做多余配置,我们观察获取到的remote_addr地址。
配置Proxy_3,将来自test.snoopyops.top的所有请求都转发到本机的8080端口。
1
2
3
4
5
6
7
8
9
|
server {
listen 80;
server_name test.snoopyops.top;
location / {
proxy_pass http://172.31.36.28:8080;
proxy_set_header Host $http_host;
}
}
|
配置Proxy_2,将来自test.snoopyops.top的所有请求都转发到Proxy_3上。
1
2
3
4
5
6
7
8
9
|
server {
listen 80;
server_name test.snoopyops.top;
location / {
proxy_pass http://172.31.36.28;
proxy_set_header Host $http_host;
}
}
|
配置Proxy_1,同理将来自test.snoopyops.top的所有请求都转发到Proxy_2上。
1
2
3
4
5
6
7
8
9
|
server {
listen 80;
server_name test.snoopyops.top;
location / {
proxy_pass http://172.30.144.202;
proxy_set_header Host $http_host;
}
}
|
做完域名解析后,我们访问test.snoopyops.top后,依次查看所有服务器的Nginx日志。
在Proxy_1的Nginx日志中,我们通过remote_addr获取到了客户端的真实IP。
1
|
219.143.190.117 - - [03/Mar/2025:11:36:35 +0800] "GET / HTTP/1.1" 200 52 "-" "curl/8.4.0" "-"
|
在Proxy_2的Nginx日志中,我们通过remote_addr获取到的是Proxy_1的IP。
1
|
172.18.6.10 - - [03/Mar/2025:11:36:35 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "-"
|
在Proxy_3的Nginx日志中,我们通过remote_addr获取到的是Proxy_2的IP。
1
|
172.30.144.202 - - [03/Mar/2025:11:36:35 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "-"
|
由此我们可以总结出:remote_addr这个变量表示的是与Nginx服务器直接建立TCP连接的客户端的IP地址。客户端是直接与Proxy_1建立连接的,所以Proxy_1可以通过remote_addr获取到客户端真实IP,而与Proxy_2建立连接的是Proxy_1,所以他就只能记录Proxy_1的IP地址了。
在没有经过多层代理的情况下,我们可以通过remote_addr获取客户端真实IP。
X-Real-IP
既然我们在Proxy_1上可以通过remote_addr获取到客户端的真实IP,那么我们可以在Proxy_1上定义一个X-Real-IP的请求头,将remote_addr赋值给X-Real-IP。并将X-Real-IP一直传递到后端服务,这样后端服务就可以通过获取X-Real-IP的值达到获取客户端的真实IP的目的了。
在Proxy_1上,我们做如下配置,将获取到的remote_addr的值赋值给X-Real-IP。
1
2
3
4
5
6
7
8
9
10
11
|
server {
listen 80;
server_name test.snoopyops.top;
location / {
proxy_pass http://172.30.144.202;
proxy_set_header Host $http_host;
# 添加请求X-Real-IP头,并将remote_addr赋值给X-Real-IP
proxy_set_header X-Real-IP $remote_addr;
}
}
|
我们在Proxy_2中,获取proxy_1定义的X-Real-IP,并赋值给自己的给自己定义的X-Real-IP请求头。
1
2
3
4
5
6
7
8
9
10
|
server {
listen 80;
server_name test.snoopyops.top;
location / {
proxy_pass http://172.31.36.28;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $http_x_real_ip;
}
}
|
同理,我们在Proxy_3上也定义一个X-Real-IP,并将获取到的上一级的X-Real-IP赋值给自己的X-Real-IP
1
2
3
4
5
6
7
8
9
10
|
server {
listen 80;
server_name test.snoopyops.top;
location / {
proxy_pass http://172.31.36.28:8080;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $http_x_real_ip;
}
}
|
由于X-Real-IP为自定义字段,Nginx日志没有打印此字段,所以Nginx日志没有发生变化。
Proxy_1的Nginx日志如下:
1
|
219.143.190.117 - - [03/Mar/2025:11:47:28 +0800] "GET / HTTP/1.1" 200 52 "-" "curl/8.4.0" "-"
|
Proxy_2的Nginx日志如下:
1
|
172.18.6.10 - - [03/Mar/2025:11:47:28 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "-"
|
Proxy_3的Nginx日志如下:
1
|
172.30.144.202 - - [03/Mar/2025:11:47:28 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "-"
|
但是我们后端服务已经可以通过X-Real-IP获取到客户端的真实IP了。服务端日志如下:
1
2
|
Server Started on :8080
Client IP: 219.143.190.117 | Method: GET | URL: /
|
X-Real-IP是一个自定义的HTTP头部字段,通常用于在代理服务器转发请求时,传递客户端的真实IP地址。当使用代理时,代理服务器会将客户端的真实IP地址填充到这个头部字段中,然后后端服务可以通过配置从这个头部获取客户端真实IP。不过,它并非标准HTTP头部,依赖于代理服务器是否正确设置,且如果代理链中有恶意节点,可能会被篡改。
X-Forwarded-For
X-Forwarded-For是一个标准的HTTP头部字段,用于记录请求经过的代理服务器列表。它的格式为client_ip, proxy1_ip, proxy2_ip...,其中client_ip是客户端的真实IP,后面依次是请求经过的代理服务器IP。
相比X-Real-IP,X-Forwarded-For更规范,被广泛支持。但由于它记录了整个代理链的IP,在处理时需要解析地址,获取第一个字段。
我们需要在Nginx服务器上配置X-Forwarded-For请求头。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
# proxy_1的配置
server {
listen 80;
server_name test.snoopyops.top;
location / {
proxy_pass http://172.30.144.202;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
# proxy_2的配置
server {
listen 80;
server_name test.snoopyops.top;
location / {
proxy_pass http://172.31.36.28;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
# proxy_3的配置
server {
listen 80;
server_name test.snoopyops.top;
location / {
proxy_pass http://172.31.36.28:8080;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
|
在Nginx默认的日志格式中,最后一个字段表示的就是X-Forwarded-For,各级代理日志如下,我们发现客户端真实IP及各级代理IP都会被记录。
1
2
3
4
5
6
7
8
|
# Proxy_1的Nginx日志
219.143.190.117 - - [03/Mar/2025:11:55:35 +0800] "GET / HTTP/1.1" 200 52 "-" "curl/8.4.0" "-"
# Proxy_2的Nginx日志
172.18.6.10 - - [03/Mar/2025:11:55:35 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "219.143.190.117"
# Proxy_3的Nginx日志
172.30.144.202 - - [03/Mar/2025:11:55:35 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "219.143.190.117, 172.18.6.10"
|
总结
| 字段 |
说明 |
| remote_addr |
表示的是与Nginx服务器直接建立TCP连接的客户端的IP地址,多级代理下无法获取客户端真实IP。 |
| X-Real-IP |
自定义的HTTP头部字段,可传递客户端的真实IP地址,依赖于代理服务器是否正确设置,有被篡改的风险。 |
| X-Forwarded-For |
标准的HTTP头部字段,用于记录请求经过的代理服务器列表,但由于它记录了整个代理链的IP。在处理时需要解析地址,获取第一个字段。 |