适用场景:frpc 通过 TCP 隧道将流量转发到雷池(SafeLine)WAF,雷池防护后再转发到后端源站。本文解决的核心问题:如何让雷池日志和防护策略拿到访客的真实公网 IP,而不是 frpc 的内网 IP。
适用版本:雷池社区版 5.x / 6.x / 7.x(站点配置文件位于
/data/safeline/resources/nginx/sites-enabled/IF_backend_*)最后更新:修复 inotify 脚本死循环 bug,支持任意端口、HTTP/HTTPS 混合、多站点全自动
frpc 通过 TCP 隧道把流量从公网 frps 转发到内网雷池。对雷池来说,所有请求的 $remote_addr 都是 frpc 的内网 IP(如 192.168.1.100),导致:
Proxy Protocol 是 TCP 层面的协议扩展,在 TCP 三次握手完成后、应用数据(TLS/HTTP)发送前,由 frpc 插入一段二进制头(约 16~100 字节),携带真实访客的 IP 和端口。
TCP 三次握手 → Proxy Protocol v2 头(~几十字节)→ TLS 握手 → HTTP 数据...雷池的 Nginx(tengine)解析这个头,把真实 IP 写入 $proxy_protocol_addr 和 $realip_remote_addr,后续所有日志记录、WAF 规则匹配都使用真实 IP。
访客(公网IP)
│
▼
frps (公网服务器)
│ TCP 转发
▼
frpc (内网机器)
│ Proxy Protocol v2 头
▼
雷池 WAF (Nginx tengine)
│ 解析真实 IP + WAF 检测
▼
后端源站 (Web 服务)数据流:访客 → frps:443 → frpc → 雷池:10400 (proxy_protocol) → 后端:8080
参考 frp 官方文档:https://gofrp.org/zh-cn/docs/setup/
frpc.toml# ============================================
# frpc 配置 - Proxy Protocol v2
# ============================================
serverAddr = "你的FRPS公网IP"
serverPort = 7000
auth.method = "token"
auth.token = "你的token"
# ---------- HTTPS 隧道 ----------
[[proxies]]
name = "safeline-https"
type = "tcp"
localIP = "雷池的内网IP" # 雷池所在机器的内网 IP
localPort = 10400 # 必须 = 雷池站点监听端口
remotePort = 443 # 对外公网端口(用户访问的端口)
transport.proxyProtocolVersion = "v2" # ★ 关键:启用 Proxy Protocol v2
# ---------- HTTP 隧道(可选)----------
[[proxies]]
name = "safeline-http"
type = "tcp"
localIP = "雷池的内网IP"
localPort = 344 # 雷池 HTTP 站点监听端口
remotePort = 80
transport.proxyProtocolVersion = "v2"| 参数 | 说明 |
|---|---|
localIP | 雷池机器的内网 IP(不是 127.0.0.1,除非 frpc 和雷池同机) |
localPort | 必须等于雷池防护站点的监听端口(如 10400、344 等) |
remotePort | frps 上对外暴露的端口(通常 443/80) |
transport.proxyProtocolVersion | 设为 "v2",frpc 会在每个连接开头发送 PP 头 |
# 测试配置文件
frpc check -c frpc.toml
# 前台运行(调试用)
frpc -c frpc.toml
# 后台运行(正式用)
nohup frpc -c frpc.toml > frpc.log 2>&1 &
# 或用 systemd 管理(推荐)
# 创建 /etc/systemd/system/frpc.service/etc/systemd/system/frpc.service[Unit]
Description=frpc client
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetsystemctl daemon-reload
systemctl enable frpc
systemctl start frpc
systemctl status frpcproxy_params雷池的 Nginx 全局配置位于 /data/safeline/resources/nginx/proxy_params。
备份原文件:
cp /data/safeline/resources/nginx/proxy_params \
/data/safeline/resources/nginx/proxy_params.bak.$(date +%Y%m%d%H%M%S)写入新内容(直接用下面这个完整模板,只改第一行 FRPS_IP):
# ============================================
# SafeLine Nginx proxy_params - Proxy Protocol 配置
# ============================================
# 【必改】换成你的 frps 公网 IP
set_real_ip_from FRPS_IP_PLACEHOLDER;
# 局域网信任段(frpc 及内网直连的所有可能来源)
set_real_ip_from 192.168.0.0/16;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 127.0.0.1;
# 从 Proxy Protocol 头取真实 IP
real_ip_header proxy_protocol;
real_ip_recursive on;
# 向后端传递真实 IP
proxy_set_header X-Real-IP $realip_remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 兼容无 Proxy Protocol 头的连接(内网直连/健康检查/容器内部探测)
set $proxy_x_forwarded_for $proxy_add_x_forwarded_for;
set $proxy_x_real_ip $remote_addr;
if ($proxy_protocol_addr) {
set $proxy_x_forwarded_for "$proxy_protocol_addr, $proxy_x_forwarded_for";
set $proxy_x_real_ip $proxy_protocol_addr;
}
proxy_set_header X-Forwarded-For $proxy_x_forwarded_for;
proxy_set_header X-Real-IP $proxy_x_real_ip;
# 大文件下载优化
proxy_buffering on;
proxy_buffer_size 64k;
proxy_buffers 8 128k;
proxy_busy_buffers_size 256k;
proxy_max_temp_file_size 0; # 禁止写入磁盘临时文件,避免 I/O 瓶颈
proxy_temp_file_write_size 256k;
# 启用 upstream keepalive,复用连接
proxy_http_version 1.1;
proxy_set_header Connection "";
# 其他默认头
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_hide_header X-Powered-By;# 把 FRPS_IP_PLACEHOLDER 替换成你的 frps 公网 IP
sed -i 's/FRPS_IP_PLACEHOLDER/1.2.3.4/' /data/safeline/resources/nginx/proxy_params
# ↑ 把 1.2.3.4 换成你真正的 frps 公网 IP| 指令 | 作用 |
|---|---|
set_real_ip_from | 信任列表:来自这些地址的 Proxy Protocol 头会被解析 |
real_ip_header proxy_protocol | 告诉 Nginx 从 PP 头里取真实 IP |
real_ip_recursive on | 递归解析 XFF 头,处理多层代理 |
$realip_remote_addr | Nginx 变量,值为解析后的真实 IP |
proxy_max_temp_file_size 0 | 禁止磁盘缓冲,大文件下载走内存流式转发 |
⚠️ 安全警告:set_real_ip_from绝不能设为0.0.0.0/0。这意味着任何人都能伪造 PP 头伪装成任意 IP,绕过 WAF 封禁。只信任 frps IP + 内网段。
雷池在以下场景会重新生成 IF_backend_* 配置文件,导致我们手动加的 proxy_protocol 参数丢失:
解决方案:用 inotifywait 监控 sites-enabled/ 目录,一旦检测到新文件创建或移入,自动给所有 listen 指令追加 proxy_protocol 参数,然后自动 nginx -s reload。
auto_proxy_protocol.sh(inotify 监控脚本)存放路径:/root/auto_proxy_protocol.sh#!/bin/bash
#
# auto_proxy_protocol.sh
# 监控雷池 sites-enabled 目录,自动给新建/重建的站点注入 proxy_protocol
# 防死循环设计:只监控 create + moved_to,不监控 modify
#
SITES_DIR="/data/safeline/resources/nginx/sites-enabled"
LOG_FILE="/var/log/auto_proxy_protocol.log"
# 日志函数
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
}
log "=== auto_proxy_protocol monitor started (PID $$) ==="
# ===== 首次运行:注入所有现有站点 =====
for f in "$SITES_DIR"/IF_backend_*; do
[ -f "$f" ] || continue
# 只处理:存在 listen 行 且 不含 proxy_protocol 的文件
if grep -q "^[[:space:]]*listen" "$f" && ! grep -q "proxy_protocol" "$f"; then
sed -i '/listen.*proxy_protocol/! s/\(^[[:space:]]*listen[^;]*\);$/\1 proxy_protocol;/' "$f" 2>/dev/null
log "Initial injected: $(basename "$f")"
fi
done
# 首次重载 Nginx
if docker exec safeline-tengine nginx -t 2>/dev/null; then
docker exec safeline-tengine nginx -s reload 2>/dev/null
log "Initial Nginx reload done"
else
log "ERROR: Initial nginx -t failed!"
fi
# ===== 持续监控:只监控 create + moved_to =====
# 不监控 modify —— 避免 sed 重写文件触发自身导致死循环
inotifywait -m -e create -e moved_to "$SITES_DIR" \
--format '%f' 2>/dev/null | while read -r filename; do
case "$filename" in
IF_backend_*)
FILE="$SITES_DIR/$filename"
sleep 0.5 # 等待雷池写完文件
# 前置检查:有 listen 行 且 没有 proxy_protocol 才处理
if [ -f "$FILE" ] \
&& grep -q "^[[:space:]]*listen" "$FILE" \
&& ! grep -q "proxy_protocol" "$FILE"; then
sed -i '/listen.*proxy_protocol/! s/\(^[[:space:]]*listen[^;]*\);$/\1 proxy_protocol;/' "$FILE" 2>/dev/null
log "Auto-injected: $filename"
# 注入后立刻重载
if docker exec safeline-tengine nginx -t 2>/dev/null; then
docker exec safeline-tengine nginx -s reload 2>/dev/null
log "Nginx reloaded after $filename"
else
log "ERROR: nginx -t failed after $filename, NOT reloaded!"
fi
fi
;;
esac
doneapply_proxy_protocol.sh(手动救急脚本)存放路径:
/root/apply_proxy_protocol.sh用途:脚本挂了 / 刚改完配置 / 怀疑有遗漏时,手动跑一次全量检查
#!/bin/bash
#
# apply_proxy_protocol.sh
# 手动批量注入所有站点的 proxy_protocol(带前置检查,不会重复添加)
#
SITES_DIR="/data/safeline/resources/nginx/sites-enabled"
LOG_FILE="/var/log/auto_proxy_protocol.log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Manual apply triggered" >> "$LOG_FILE"
changed=0
for f in "$SITES_DIR"/IF_backend_*; do
[ -f "$f" ] || continue
if grep -q "^[[:space:]]*listen" "$f" && ! grep -q "proxy_protocol" "$f"; then
sed -i '/listen.*proxy_protocol/! s/\(^[[:space:]]*listen[^;]*\);$/\1 proxy_protocol;/' "$f" 2>/dev/null
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Injected: $(basename "$f")" >> "$LOG_FILE"
((changed++))
fi
done
echo "Changed $changed files."
if [ "$changed" -gt 0 ]; then
if docker exec safeline-tengine nginx -t 2>/dev/null; then
docker exec safeline-tengine nginx -s reload 2>/dev/null
echo "✅ Nginx reloaded."
else
echo "❌ ERROR: nginx -t failed!"
docker exec safeline-tengine nginx -t
fi
else
echo "Nothing to do, all sites already have proxy_protocol."
fiauto-proxy-protocol.service(systemd 服务)存放路径:/etc/systemd/system/auto-proxy-protocol.service[Unit]
Description=Auto inject proxy_protocol into SafeLine Nginx config
After=docker.service
Wants=docker.service
[Service]
Type=simple
ExecStart=/root/auto_proxy_protocol.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target# 1. 赋予执行权限
chmod +x /root/auto_proxy_protocol.sh
chmod +x /root/apply_proxy_protocol.sh
# 2. 安装 inotify-tools(如果没装)
which inotifywait || apt update -y && apt install -y inotify-tools
# 3. 安装 systemd 服务
cp /root/auto-proxy-protocol.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable auto-proxy-protocol.service
systemctl start auto-proxy-protocol.service
# 4. 确认服务正常运行
systemctl status auto-proxy-protocol --no-pager| 设计要点 | 说明 |
|---|---|
只监控 create + moved_to | 雷池新建/重建站点时触发;不监控 modify,避免 sed 改文件触发自身 |
前置 grep 检查 | 文件已有 proxy_protocol 则跳过,绝不重复添加 |
| sed 精准匹配 | 只给"含 listen 但不含 proxy_protocol"的行追加,不影响其他内容 |
| nginx -t 前置验证 | 注入后先测试配置再 reload,测试失败不重载 |
| 通用端口匹配 | 不写死 443/80,任何端口(10400、344、8443 等)都能自动识别 |
listen 80; → listen 80 proxy_protocol;
listen 443 ssl; → listen 443 ssl proxy_protocol;
listen 0.0.0.0:10400; → listen 0.0.0.0:10400 proxy_protocol;
listen 0.0.0.0:344; → listen 0.0.0.0:344 proxy_protocol;
listen [::]:8443 ssl http2; → listen [::]:8443 ssl http2 proxy_protocol;
listen 0.0.0.0:10400 proxy_protocol; → 已存在,跳过,不重复https://你的IP:9443)从 X-Forwarded-For 中获取上一级代理的地址
| 选项 | 效果 |
|---|---|
| 从网络连接中获取 | 拿到的是 frpc 的内网 IP ❌ |
| 从 X-Forwarded-For 中获取第一级代理的地址 | 拿到的是 frps 的 IP ❌ |
| 从 X-Forwarded-For 中获取上一级代理的地址 | 拿到访客真实公网 IP ✅ |
在雷池控制台创建/编辑站点时,监听端口必须和 frpc 的 localPort 一致:
| frpc localPort | 雷池站点监听端口 | 说明 |
|---|---|---|
| 10400 | 10400 | 自定义 HTTPS 端口 |
| 344 | 344 | 自定义 HTTP 端口 |
| 443 | 443 | 标准 HTTPS 端口 |
| 80 | 80 | 标准 HTTP 端口 |
端口不一致 = 流量打不到雷池 = 网站打不开
grep -rn "listen" /data/safeline/resources/nginx/sites-enabled/IF_backend_*预期输出(每个 listen 行末尾都有 proxy_protocol):
listen 0.0.0.0:10400 ssl proxy_protocol;
listen 0.0.0.0:344 proxy_protocol;docker exec safeline-tengine nginx -t预期:nginx: configuration file /etc/nginx/nginx.conf test is successful
docker exec -it safeline-tengine tail -f /var/log/nginx/access.log预期:日志第一个字段是访客的真实公网 IP(不是 192.168.x.x)
114.222.33.44 - - [21/Aug/2026:10:15:30 +0800] "GET / HTTP/1.1" 200 1234 ...# 在公网另一台机器上(或用手机流量)
curl -I https://你的域名登录雷池控制台 → 检测日志,查看最近请求的「客户端 IP」列,确认显示的是真实公网 IP。
systemctl status auto-proxy-protocol --no-pager预期:active (running)
查看自动注入日志:
tail -20 /var/log/auto_proxy_protocol.log预期输出(只有关键事件,不会疯狂刷屏):
2026-08-21 10:00:01 === auto_proxy_protocol monitor started (PID 12345) ===
2026-08-21 10:00:01 Initial injected: IF_backend_2
2026-08-21 10:00:01 Initial Nginx reload done
2026-08-21 10:05:30 Auto-injected: IF_backend_3
2026-08-21 10:05:30 Nginx reloaded after IF_backend_3nginx -t 报错症状:修改/重建站点后网站 502 或连接拒绝
排查:
# 检查 listen 有没有 proxy_protocol
grep "listen" /data/safeline/resources/nginx/sites-enabled/IF_backend_*
# 如果有 listen 行但没有 proxy_protocol → 注入丢失
bash /root/apply_proxy_protocol.shbroken header原因:listen 加了 proxy_protocol,但连接方没发 PP 头
排查:
docker exec safeline-tengine tail -20 /var/log/nginx/error.log解决:确认 frpc 配置了 transport.proxyProtocolVersion = "v2" 并重启 frpc
检查清单:
# 1. proxy_params 是否正确
cat /data/safeline/resources/nginx/proxy_params | head -20
# 2. listen 是否有 proxy_protocol
grep "listen" /data/safeline/resources/nginx/sites-enabled/IF_backend_*
# 3. 雷池控制台源 IP 获取方式是否选对
# 应该是「从 X-Forwarded-For 中获取上一级代理的地址」
# 4. frpc 是否启用了 PP v2
grep "proxyProtocol" /etc/frp/frpc.toml症状:/var/log/auto_proxy_protocol.log 每秒输出一条 Injected
原因:使用了旧版脚本(监控了 modify 事件 + 无前置检查)
解决:
# 1. 立刻停掉
systemctl stop auto-proxy-protocol
# 2. 用本文第三部分的脚本覆盖(已修复)
vim /root/auto_proxy_protocol.sh
# 粘贴新版内容,保存
# 3. 清理可能重复的 proxy_protocol
sed -i 's/\(proxy_protocol\)\{2,\}/proxy_protocol/g' /data/safeline/resources/nginx/sites-enabled/IF_backend_*
# 4. 确认文件干净
grep "listen" /data/safeline/resources/nginx/sites-enabled/IF_backend_*
# 5. 重启服务
systemctl start auto-proxy-protocol
# 6. 观察 10 秒,应该只输出 2-3 行就停住
> /var/log/auto_proxy_protocol.log
sleep 10
tail -20 /var/log/auto_proxy_protocol.logIF_Backend_not_found原因:雷池已知 bug,删除站点时残留空文件
解决:
rm -f /data/safeline/resources/nginx/sites-enabled/IF_Backend_not_found
docker exec safeline-tengine nginx -t && docker exec safeline-tengine nginx -s reload原因:升级会重新生成所有配置文件
解决:
# 手动跑一次全量注入
bash /root/apply_proxy_protocol.sh
# inotify 脚本也会自动处理,确认它在跑
systemctl status auto-proxy-protocol| 场景 | 是否需要人工干预 | 说明 |
|---|---|---|
| 重启主机 | ❌ 不需要 | systemd 自启 inotify → 首次扫描注入 → reload |
| 升级雷池 | ❌ 不需要 | inotify 检测到新文件 → 自动注入 → reload |
| 修改站点 | ❌ 不需要 | inotify 检测到 moved_to → 自动注入 → reload |
| 增加站点 | ❌ 不需要 | inotify 检测到 create → 自动注入 → reload |
| 删除站点 | ❌ 不需要 | 文件被删,不影响其他站点 |
# 查看自动注入日志
tail -f /var/log/auto_proxy_protocol.log
# 手动全量检查 + 注入
bash /root/apply_proxy_protocol.sh
# 重启监控服务
systemctl restart auto-proxy-protocol
# 停止监控服务(维护时用)
systemctl stop auto-proxy-protocol
# 查看服务状态
systemctl status auto-proxy-protocol --no-pager
# 查看 Nginx 错误日志
docker exec safeline-tengine tail -f /var/log/nginx/error.log
# 查看访问日志(确认真实 IP)
docker exec safeline-tengine tail -f /var/log/nginx/access.loglisten 带了 proxy_protocol 后,内网直连雷池端口会失败(必须发 PP 头)。
内网调试正确方式:直接访问后端源站 IP,不经过雷池:
# 查雷池站点配置的上游地址
# 控制台 → 防护站点 → 上游服务器
# 比如后端是 192.168.1.50:8080
curl http://192.168.1.50:8080内网是可信环境,直连后端最快最稳,不需要过 WAF。
deploy.sh存放路径:任意目录(如 /root/safeline-proxy-protocol/deploy.sh)#!/bin/bash
#
# deploy.sh - 一键部署 frpc + 雷池 Proxy Protocol 自动化
#
set -e
SAFELINE_DIR="/data/safeline/resources/nginx"
PROXY_PARAMS="$SAFELINE_DIR/proxy_params"
SITES_DIR="$SAFELINE_DIR/sites-enabled"
AUTO_SCRIPT="/root/auto_proxy_protocol.sh"
APPLY_SCRIPT="/root/apply_proxy_protocol.sh"
SERVICE_FILE="/etc/systemd/system/auto-proxy-protocol.service"
echo "============================================"
echo " SafeLine + frp Proxy Protocol 一键部署"
echo "============================================"
echo ""
# --- 检查路径 ---
if [ ! -d "$SAFELINE_DIR" ]; then
echo "[ERROR] 雷池目录 $SAFELINE_DIR 不存在"
exit 1
fi
# --- 输入 frps 公网 IP ---
read -rp "请输入你的 frps 公网 IP: " FRPS_IP
if [ -z "$FRPS_IP" ]; then
echo "[ERROR] frps IP 不能为空"
exit 1
fi
echo ""
echo "开始部署..."
echo ""
# --- Step 1: 备份 proxy_params ---
if [ -f "$PROXY_PARAMS" ]; then
cp "$PROXY_PARAMS" "${PROXY_PARAMS}.bak.$(date +%Y%m%d%H%M%S)"
echo "[OK] 已备份 proxy_params"
fi
# --- Step 2: 写入 proxy_params ---
cat > "$PROXY_PARAMS" << 'EOF'
# ============================================
# SafeLine Nginx proxy_params - Proxy Protocol 配置
# 由 deploy.sh 自动写入
# ============================================
# frps 公网 IP(自动替换)
set_real_ip_from FRPS_PLACEHOLDER;
# 局域网信任段
set_real_ip_from 192.168.0.0/16;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 127.0.0.1;
# 从 Proxy Protocol 头取真实 IP
real_ip_header proxy_protocol;
real_ip_recursive on;
# 向后端传递真实 IP
proxy_set_header X-Real-IP $realip_remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 兼容无 Proxy Protocol 头的连接
set $proxy_x_forwarded_for $proxy_add_x_forwarded_for;
set $proxy_x_real_ip $remote_addr;
if ($proxy_protocol_addr) {
set $proxy_x_forwarded_for "$proxy_protocol_addr, $proxy_x_forwarded_for";
set $proxy_x_real_ip $proxy_protocol_addr;
}
proxy_set_header X-Forwarded-For $proxy_x_forwarded_for;
proxy_set_header X-Real-IP $proxy_x_real_ip;
# 大文件下载优化
proxy_buffering on;
proxy_buffer_size 64k;
proxy_buffers 8 128k;
proxy_busy_buffers_size 256k;
proxy_max_temp_file_size 0;
proxy_temp_file_write_size 256k;
# 其他默认头
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Connection "";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_hide_header X-Powered-By;
EOF
# 替换 frps IP
sed -i "s/FRPS_PLACEHOLDER/$FRPS_IP/" "$PROXY_PARAMS"
echo "[OK] proxy_params 已写入(frps IP: $FRPS_IP)"
# --- Step 3: 安装 inotify-tools ---
if ! command -v inotifywait &>/dev/null; then
echo "正在安装 inotify-tools..."
if command -v apt &>/dev/null; then
apt update -y && apt install -y inotify-tools
elif command -v yum &>/dev/null; then
yum install -y inotify-tools
elif command -v dnf &>/dev/null; then
dnf install -y inotify-tools
else
echo "[WARN] 无法自动安装 inotify-tools,请手动安装"
exit 1
fi
echo "[OK] inotify-tools 已安装"
else
echo "[OK] inotify-tools 已存在,跳过"
fi
# --- Step 4: 写入自动监控脚本 ---
cat > "$AUTO_SCRIPT" << 'AUTOSCRIPT'
#!/bin/bash
SITES_DIR="/data/safeline/resources/nginx/sites-enabled"
LOG_FILE="/var/log/auto_proxy_protocol.log"
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"; }
log "=== auto_proxy_protocol monitor started (PID $$) ==="
for f in "$SITES_DIR"/IF_backend_*; do
[ -f "$f" ] || continue
if grep -q "^[[:space:]]*listen" "$f" && ! grep -q "proxy_protocol" "$f"; then
sed -i '/listen.*proxy_protocol/! s/\(^[[:space:]]*listen[^;]*\);$/\1 proxy_protocol;/' "$f" 2>/dev/null
log "Initial injected: $(basename "$f")"
fi
done
docker exec safeline-tengine nginx -t 2>/dev/null && docker exec safeline-tengine nginx -s reload 2>/dev/null
log "Initial Nginx reload done"
inotifywait -m -e create -e moved_to "$SITES_DIR" --format '%f' 2>/dev/null | while read -r filename; do
case "$filename" in
IF_backend_*)
FILE="$SITES_DIR/$filename"; sleep 0.5
if [ -f "$FILE" ] && grep -q "^[[:space:]]*listen" "$FILE" && ! grep -q "proxy_protocol" "$FILE"; then
sed -i '/listen.*proxy_protocol/! s/\(^[[:space:]]*listen[^;]*\);$/\1 proxy_protocol;/' "$FILE" 2>/dev/null
log "Auto-injected: $filename"
docker exec safeline-tengine nginx -t 2>/dev/null && docker exec safeline-tengine nginx -s reload 2>/dev/null
fi
;;
esac
done
AUTOSCRIPT
chmod +x "$AUTO_SCRIPT"
echo "[OK] 自动监控脚本已写入"
# --- Step 5: 写入手动救急脚本 ---
cat > "$APPLY_SCRIPT" << 'APPLYSCRIPT'
#!/bin/bash
SITES_DIR="/data/safeline/resources/nginx/sites-enabled"
LOG_FILE="/var/log/auto_proxy_protocol.log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Manual apply triggered" >> "$LOG_FILE"
changed=0
for f in "$SITES_DIR"/IF_backend_*; do
[ -f "$f" ] || continue
if grep -q "^[[:space:]]*listen" "$f" && ! grep -q "proxy_protocol" "$f"; then
sed -i '/listen.*proxy_protocol/! s/\(^[[:space:]]*listen[^;]*\);$/\1 proxy_protocol;/' "$f" 2>/dev/null
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Injected: $(basename "$f")" >> "$LOG_FILE"
((changed++))
fi
done
echo "Changed $changed files."
if [ "$changed" -gt 0 ]; then
docker exec safeline-tengine nginx -t 2>/dev/null && docker exec safeline-tengine nginx -s reload 2>/dev/null && echo "Nginx reloaded." || echo "ERROR: nginx -t failed!"
else
echo "Nothing to do, all sites already have proxy_protocol."
fi
APPLYSCRIPT
chmod +x "$APPLY_SCRIPT"
echo "[OK] 手动救急脚本已写入"
# --- Step 6: 安装 systemd 服务 ---
cat > "$SERVICE_FILE" << 'SERVICE'
[Unit]
Description=Auto inject proxy_protocol into SafeLine Nginx config
After=docker.service
Wants=docker.service
[Service]
Type=simple
ExecStart=/root/auto_proxy_protocol.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
SERVICE
systemctl daemon-reload
systemctl enable auto-proxy-protocol.service
systemctl restart auto-proxy-protocol.service
sleep 1
if systemctl is-active --quiet auto-proxy-protocol.service; then
echo "[OK] inotify 自动监控服务已启动并设为开机自启"
else
echo "[ERROR] 服务启动失败,查看日志: journalctl -u auto-proxy-protocol.service"
exit 1
fi
# --- Step 7: 首次全量注入 ---
echo "首次注入所有站点..."
bash "$APPLY_SCRIPT"
# --- Step 8: 重载 Nginx ---
echo "重载雷池 Nginx..."
if docker exec safeline-tengine nginx -t 2>/dev/null; then
docker exec safeline-tengine nginx -s reload 2>/dev/null
echo "[OK] Nginx 重载成功"
else
echo "[WARN] Nginx 配置测试失败"
docker exec safeline-tengine nginx -t
fi
echo ""
echo "============================================"
echo " 部署完成!"
echo "============================================"
echo ""
echo "后续步骤:"
echo " 1. frpc 配置加一行: transport.proxyProtocolVersion = \"v2\""
echo " 2. 重启 frpc: systemctl restart frpc"
echo " 3. 雷池控制台 → 防护站点 → 高级配置 → 源 IP 获取方式"
echo " → 选「从 X-Forwarded-For 中获取上一级代理的地址」"
echo " 4. 验证: docker exec -it safeline-tengine tail -f /var/log/nginx/access.log"
echo ""
echo "管理命令:"
echo " 查看自动注入日志: tail -f /var/log/auto_proxy_protocol.log"
echo " 手动全量注入: bash /root/apply_proxy_protocol.sh"
echo " 重启监控服务: systemctl restart auto-proxy-protocol"
echo " 停止监控服务: systemctl stop auto-proxy-protocol"
echo ""# 1. 创建目录并进入
mkdir -p /root/safeline-proxy-protocol
cd /root/safeline-proxy-protocol
# 2. 把 deploy.sh 上传到这个目录(vim 粘贴或 scp)
# 3. 赋予权限并运行
chmod +x deploy.sh
./deploy.sh
# 4. 按提示输入 frps 公网 IP,回车等待完成运行 deploy.sh
│
▼
输入 frps 公网 IP
│
▼
备份 + 写入 proxy_params(含 frps IP + 内网段 + PP 配置)
│
▼
安装 inotify-tools
│
▼
写入 auto_proxy_protocol.sh(inotify 监控)
│
▼
写入 apply_proxy_protocol.sh(手动救急)
│
▼
安装 systemd 服务 + 开机自启
│
▼
首次全量注入所有站点
│
▼
重载 Nginx
│
▼
✅ 部署完成
│
▼
你去做的 3 件事:
① frpc 加 transport.proxyProtocolVersion = "v2" → 重启 frpc
② 雷池控制台选「从 XFF 获取上一级代理地址」
③ 看 access.log 确认真实 IP不会。 PP 头只在 TCP 连接建立时发一次(约几十字节),之后整个连接就是纯净的数据流,不封装、不加密、不增加 RTT。Nginx 解析 PP 头的 CPU 开销在纳秒级。下载速度的瓶颈是 frps 的出口带宽,跟 PP 无关。
listen 加了 proxy_protocol 后内网直连打不开?因为 proxy_protocol 是强制要求——连这个端口的所有连接都必须先发 PP 头,否则直接拒绝。set_real_ip_from 内网段 只是"信任白名单",不是"免检通行证"。
内网调试请直连后端源站 IP,不经过雷池。
必须。 这是协议层面的对称要求:
| frpc | 雷池 listen | 结果 |
|---|---|---|
| ✅ 发 PP v2 | ✅ 收 PP | ✅ 正常 |
| ❌ 不发 | ✅ 收 PP | ❌ broken header 拒绝连接 |
| ✅ 发 PP v2 | ❌ 不收 | ❌ TLS 握手失败(PP 头被当成乱码) |
| ❌ 不发 | ❌ 不收 | ✅ 正常(但拿不到真实 IP) |
完全不需要。 inotify 脚本会自动检测新文件并注入,支持任意端口(443、80、10400、344、8443 等)、HTTP 和 HTTPS 混合、多域名多站点。你唯一要确保的是 frpc 的 localPort 和雷池站点的监听端口一致。
社区版 detector 默认单线程 + 800 QPS 硬限制。对个人博客、小企业站、家庭网络来说完全够用(800 QPS ≈ 日 PV 200 万)。下载/流媒体场景不受单核限制影响(检测器只检测请求头,之后流式转发)。只有高并发 API 或频繁 CC 攻击场景才会撞上限。
IF_backend_* 文件,如果雷池改了命名规则怎么办?当前所有雷池版本(5.x~7.x)都使用 IF_backend_* 命名。如果未来版本改了前缀,只需修改脚本里两处 IF_backend_* 的匹配规则即可,改动量极小。
部署完成后,你的系统里会有以下文件:
| 文件路径 | 作用 |
|---|---|
/data/safeline/resources/nginx/proxy_params | 雷池全局 Nginx 配置(PP 信任 + 头传递 + 缓冲优化) |
/root/auto_proxy_protocol.sh | inotify 自动监控脚本(后台常驻) |
/root/apply_proxy_protocol.sh | 手动全量注入脚本(救急/维护用) |
/etc/systemd/system/auto-proxy-protocol.service | systemd 开机自启服务 |
/var/log/auto_proxy_protocol.log | 自动注入操作日志 |
5 步部署,永久生效:
1. 运行 deploy.sh → 输入 frps IP → 自动完成所有配置
2. frpc 加一行 transport.proxyProtocolVersion = "v2" → 重启 frpc
3. 雷池控制台 → 源 IP 获取方式 → 选「从 XFF 获取上一级代理地址」
4. 看 access.log 确认第一个字段是真实公网 IP
5. 收工。以后增删改站点、升级、重启,全部自动处理,零维护。核心原理一句话:frpc 在每个 TCP 连接开头塞一个 Proxy Protocol v2 头告诉雷池"访客真实 IP 是 xxx",雷池 Nginx 解析这个头并替换 $remote_addr,WAF 日志和规则全部基于真实 IP 工作。文档版本:v2.0(修复 inotify 死循环 bug,支持任意端口通用匹配)
最后更新:2026-08-21
—— 评论区 ——