Frp C/S部署 && 保姆级别 Nginx 公网反代真实IP获取
书接上回,为了便于使用公网服务器反代内网服务实现内网穿透,我部署了WireGruad,但是连接稳定性无法保证,连接总会在大约一天后断开。因此我想到了之前使用过的内网穿透项目:NPS和Frp,考虑到前者已经失去维护,并且后者的文档也逐渐健全,最终我选择了Frp。
参考文献
获取用户真实 IP —— gofrp.org
Accepting the PROXY Protocol —— Nginx docs
下载FRP
wget https://github.com/fatedier/frp/releases/download/v0.61.2/frp_0.61.2_linux_amd64.tar.gz
xfox@ClawHK:~$ tar -xzvf frp_0.61.2_linux_amd64.tar.gz
frp_0.61.2_linux_amd64/
frp_0.61.2_linux_amd64/LICENSE
frp_0.61.2_linux_amd64/frpc.toml
frp_0.61.2_linux_amd64/frpc
frp_0.61.2_linux_amd64/frps
frp_0.61.2_linux_amd64/frps.toml
cd frp_0.61.2_linux_amd64/
sudo mkdir -p /opt/frp
sudo cp frps /opt/frp/ #部署服务端
sudo cp frpc /opt/frp/ #部署客户端
编写服务端配置
sudo nano /opt/frp/frps.toml
bindPort = 7000
vhostHTTPPort = 8080
auth.token = "your_secure_token"
transport.proxyProtocolVersion = "v2"
编写客户端配置
sudo nano /opt/frp/frpc.toml
serverAddr = "47.242.89.175"
serverPort = 7000
auth.token = "your_secure_token"
[[proxies]]
name = "linuxuser.site"
type = "http"
localPort = 80
customDomains = ["linuxuser.site"]
#transport.proxyProtocolVersion = "v2"
[[proxies]]
name = "nas.xfox.fun"
type = "http"
localPort = 5212
customDomains = ["nas.xfox.fun"]
[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 22022
关于transport.proxyProtocolVersion = "v2"
的配置有一个误区,我应该在需要开启proxy_protocol的对应内网穿透条目[[proxies]]下写这行配置,也就是说上面的配置里如果没有注释也只是为linuxuser.site开启了proxy_protocol支持。
这一问题一度导致我以为nignx配置写错了,自查半天以为自己记忆里的写法有误,最终去问Deepseek浪费了大量时间。
服务/客户端Systemd配置
sudo nano /etc/systemd/system/frps.service
sudo nano /etc/systemd/system/frpc.service
[Unit]
# 服务名称,可自定义
Description = Frp Server/Client (Self Host)
After = network.target syslog.target
Wants = network.target
[Service]
Type = simple
# 启动frps的命令,需修改为您的frps的安装路径
ExecStart = /opt/frp/frps -c /opt/frp/frps.toml
# 启动frpc的命令,需修改为您的frpc的安装路径
ExecStart = /opt/frp/frpc -c /opt/frp/frpc.toml
[Install]
WantedBy = multi-user.target
运行客户端/服务端
sudo systemctl enable frpc.service --now
sudo systemctl enable frps.service --now
Nginx反向代理
# /etc/nginx/sites-enabled/linuxuser.site
server {
listen 80;
server_name 127.0.0.1 linuxuser.site www.linuxuser.site;
root /ZHITAIPC005/www/linuxuser.site;
index index.php index.html index.htm;
client_max_body_size 128m;
# 真实IP解析(信任前端代理)
# /etc/nginx/conf.d/frp_real_ip.conf
# Typecho URL重写规则
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# PHP处理配置
location ~ \.php$ {
fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi.conf;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# 日志配置
access_log /ZHITAIPC005/www/log/linuxuser.site.access.log frp_real_ip;
error_log /ZHITAIPC005/www/log/linuxuser.site.error.log;
}
这里也有个坑:Nginx的日志指令中,access_log的语法是:
access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition];
而error_log的语法是:error_log file [level];
所以不能在error_log也写上frp_real_ip,否则frp_real_ip会成为一个无效的错误日志等级。
至于上面提到的frp_real_ip这个log_format,必须位于nginx.conf的http块中,但是考虑到nginx是可能随着Debian13的发布更新版本因此/etc/nginx/nginx.conf
可能在更新时被覆盖写入。我们应该采取更规范的额外配置引入方式:sudo nano /etc/nginx/conf.d/frp_real_ip.conf
/etc/nginx/conf.d/frp_real_ip.conf
定义日志格式(必须在http块内)
使用proxy_protocol
log_format frp_real_ip '$proxy_protocol_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
set_real_ip_from 47.242.89.175;
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol; # 从 Proxy Protocol 数据中提取真实 IP
使用X-Forwarded-For
log_format frp_real_ip '$http_x_forwarded_for - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
可选:配置信任的代理IP(若使用Real IP模块)
set_real_ip_from 47.242.89.175;
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
这里还有一个小问题,你不能画蛇添足的给log_format外再套一个http{}块。
查看/etc/nginx/nginx.conf
发现
http{
...其他配置
include /etc/nginx/conf.d/*.conf;
...Other confg
}
也就是说/etc/nginx/conf.d/*.conf的配置默认就是在http块内引入的,所以不能画蛇添足的给log_format外再套一个http{}块。
前端服务器配置
我上面用到了$http_x_forwarded_for这个变量,如果客户端或上游代理已经添加了 X-Forwarded-For 头,Nginx 作为反向代理时默认会将其传递给后端服务器。
但需注意:如果 Nginx 是第一个代理(直接接收客户端请求),默认不会主动添加 X-Forwarded-For 头,除非显式配置。
所以在前端服务器,也就是我的公网服务器必须手动指定Nginx向后传递X-Forwarded-For 头。
以下就是linuxuser.site的最前端服务器配置
server {
listen [::]:443 ssl http2;
listen 443 ssl http2;
server_name linuxuser.site www.linuxuser.site;
# SSL证书路径(保持用户原有配置)
ssl_certificate /home/xfox/www/all_linuxuser.site/fullchain.pem;
ssl_certificate_key /home/xfox/www/all_linuxuser.site/privkey.pem;
# SSL优化配置
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
root /ZHITAIPC005/www/linuxuser.site;
index index.php index.html index.htm;
client_max_body_size 128m;
# 反向代理配置
location / {
proxy_pass http://linuxuser.site:8080;
proxy_set_header X-Forwarded-For $remote_addr;
# 保留必要头部(供FRP识别)
proxy_set_header Host $host;
}
# 其他通用配置
# access_log /var/log/nginx/linuxuser.site.access.log;
# error_log /var/log/nginx/linuxuser.site.error.log;
}
查看效果
通过上面的一系列配置,我已经实现了通过X-Forwarded-For
将访客IP 按照:
HK Server -> FRP -> Home Server 的完整链路传输
现在在Home Server执行 tail -f /ZHITAIPC005/www/log/linuxuser.site.access.log
再访问https://linuxuser.site/ 即可检验成果。
使用体验
感觉比使用WireGruad组网再反代的Web资源响应速度好了很多啊,nas的图片预览刷新明显更快了。
总结
今天的一番折腾更加坚定了我“不能完全依赖AI进行方案设计”的理念,使用AI要尽可能保证自己对达成目标所涉及的技术栈的基本原理框架有清晰认知。 大语言模型非常适合替代搜索引擎帮助用户高效检索信息,但是绝对不能用来替代你的逻辑思考过程,否则你会成为AI的奴隶,原本要完成的目标也在AI的幻觉迷惑下变得模糊不清,即使最终解决了问题也浪费了大量时间。
今天实际应用成功的Nginx 公网反代真实IP获取这一完整方案并非AI所推荐给我的。
Ai生成的方案存在肉眼可见的不和谐,比如端口冲突,没有考虑到Nginx 的http模块只能接收proxy_protocol而无法向后端发送proxy_protocol 导致后端只能接收到HK Server的公网IP。
还有诸多谬误,我不再一一记录,总而言之:不要偷懒让AI独自完成方案思考,用户必须有基于已知知识建立自己的主见。使用AI过程中出现的新知识点以及不了解的细节一定要及时查阅官方文档或者问AI。
建议要么前后端服务器流量走HTTPS,并且前端反代不能信任不安全的证书,要么就构建信任链,使用证书对客户端和服务器进行验证
有个问题,frp链路没有做信任链,存在中间人攻击风险,并且前后端服务器走的是80端口的HTTP,一旦发生中间人攻击,中间人可以直接窃取前后服务器的HTTP明文流量
终于还是用上了TCP(doge
我还没写完文章你就评论了。😭