侧边栏壁纸

frpc 穿透雷池 WAF 获取真实 IP 完整教程|Proxy Protocol v2 配置

2026年08月22日 17阅读 0评论 1点赞

frpc TCP 穿透 + 雷池 WAF 获取真实访客 IP 完整教程

适用场景: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),导致:

  • 雷池攻击日志里全是内网 IP,无法定位真实攻击者
  • IP 黑白名单、限流、CC 防护全部失效
  • 后端应用拿到的也是内网 IP

解决方案:Proxy Protocol v2

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。

关键点

  • Proxy Protocol 只在连接建立时发一次,后续数据流零开销,不影响下载速度
  • Nginx 解析开销纳秒级,对性能影响 <1ms
  • 必须两端同时开启:frpc 发头 + 雷池 listen 收头,缺一边就会断连

整体架构

访客(公网IP)
    │
    ▼
frps (公网服务器)
    │  TCP 转发
    ▼
frpc (内网机器)
    │  Proxy Protocol v2 头
    ▼
雷池 WAF (Nginx tengine)
    │  解析真实 IP + WAF 检测
    ▼
后端源站 (Web 服务)

数据流:访客 → frps:443 → frpc → 雷池:10400 (proxy_protocol) → 后端:8080


第一部分:frpc 客户端配置

安装 frpc

参考 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 等)
remotePortfrps 上对外暴露的端口(通常 443/80)
transport.proxyProtocolVersion设为 "v2",frpc 会在每个连接开头发送 PP 头

启动 frpc

# 测试配置文件
frpc check -c frpc.toml

# 前台运行(调试用)
frpc -c frpc.toml

# 后台运行(正式用)
nohup frpc -c frpc.toml > frpc.log 2>&1 &

# 或用 systemd 管理(推荐)
# 创建 /etc/systemd/system/frpc.service

systemd 服务文件 /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.target
systemctl daemon-reload
systemctl enable frpc
systemctl start frpc
systemctl status frpc

第二部分:雷池 Nginx 全局配置

修改 proxy_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 占位符

# 把 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_addrNginx 变量,值为解析后的真实 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

文件 1: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
done

文件 2:apply_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."
fi

文件 3:auto-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 格式(全部自动覆盖)

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; →  已存在,跳过,不重复

第四部分:雷池控制台配置

步骤

  1. 登录雷池控制台(默认 https://你的IP:9443
  2. 进入 防护站点 → 点击你的站点 → 高级配置
  3. 找到 源 IP 获取方式,选择:
从 X-Forwarded-For 中获取上一级代理的地址
  1. 点击 保存

为什么选这个

选项效果
从网络连接中获取拿到的是 frpc 的内网 IP ❌
从 X-Forwarded-For 中获取第一级代理的地址拿到的是 frps 的 IP ❌
从 X-Forwarded-For 中获取上一级代理的地址拿到访客真实公网 IP

配置监听端口

在雷池控制台创建/编辑站点时,监听端口必须和 frpc 的 localPort 一致

frpc localPort雷池站点监听端口说明
1040010400自定义 HTTPS 端口
344344自定义 HTTP 端口
443443标准 HTTPS 端口
8080标准 HTTP 端口
端口不一致 = 流量打不到雷池 = 网站打不开

第五部分:验证部署结果

验证 1:检查 listen 配置

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;

验证 2:检查 Nginx 配置测试

docker exec safeline-tengine nginx -t

预期nginx: configuration file /etc/nginx/nginx.conf test is successful

验证 3:查看访问日志中的真实 IP

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 ...

验证 4:从外网实际访问测试

# 在公网另一台机器上(或用手机流量)
curl -I https://你的域名

验证 5:雷池防护日志

登录雷池控制台 → 检测日志,查看最近请求的「客户端 IP」列,确认显示的是真实公网 IP。

验证 6:自动脚本运行状态

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_3

第六部分:排错速查表

问题 1:网站打不开,nginx -t 报错

症状:修改/重建站点后网站 502 或连接拒绝

排查

# 检查 listen 有没有 proxy_protocol
grep "listen" /data/safeline/resources/nginx/sites-enabled/IF_backend_*

# 如果有 listen 行但没有 proxy_protocol → 注入丢失
bash /root/apply_proxy_protocol.sh

问题 2:Nginx 错误日志出现 broken header

原因listen 加了 proxy_protocol,但连接方没发 PP 头

排查

docker exec safeline-tengine tail -20 /var/log/nginx/error.log

解决:确认 frpc 配置了 transport.proxyProtocolVersion = "v2" 并重启 frpc

问题 3:日志里还是内网 IP

检查清单

# 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

问题 4:inotify 脚本死循环(日志每秒刷屏)

症状/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.log

问题 5:删除站点后 Nginx 报 IF_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

问题 6:升级雷池后配置丢失

原因:升级会重新生成所有配置文件

解决

# 手动跑一次全量注入
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.log

内网调试

listen 带了 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

FAQ

Q1:Proxy Protocol 会影响网站速度和下载速度吗?

不会。 PP 头只在 TCP 连接建立时发一次(约几十字节),之后整个连接就是纯净的数据流,不封装、不加密、不增加 RTT。Nginx 解析 PP 头的 CPU 开销在纳秒级。下载速度的瓶颈是 frps 的出口带宽,跟 PP 无关。

Q2:为什么 listen 加了 proxy_protocol 后内网直连打不开?

因为 proxy_protocol强制要求——连这个端口的所有连接都必须先发 PP 头,否则直接拒绝。set_real_ip_from 内网段 只是"信任白名单",不是"免检通行证"。

内网调试请直连后端源站 IP,不经过雷池。

Q3:frpc 和雷池必须同时开启 PP 吗?

必须。 这是协议层面的对称要求:

frpc雷池 listen结果
✅ 发 PP v2✅ 收 PP✅ 正常
❌ 不发✅ 收 PPbroken header 拒绝连接
✅ 发 PP v2❌ 不收❌ TLS 握手失败(PP 头被当成乱码)
❌ 不发❌ 不收✅ 正常(但拿不到真实 IP)

Q4:以后加新站点/新端口需要手动操作吗?

完全不需要。 inotify 脚本会自动检测新文件并注入,支持任意端口(443、80、10400、344、8443 等)、HTTP 和 HTTPS 混合、多域名多站点。你唯一要确保的是 frpc 的 localPort 和雷池站点的监听端口一致。

Q5:雷池社区版单核限制会影响吗?

社区版 detector 默认单线程 + 800 QPS 硬限制。对个人博客、小企业站、家庭网络来说完全够用(800 QPS ≈ 日 PV 200 万)。下载/流媒体场景不受单核限制影响(检测器只检测请求头,之后流式转发)。只有高并发 API 或频繁 CC 攻击场景才会撞上限。

Q6:脚本监控的是 IF_backend_* 文件,如果雷池改了命名规则怎么办?

当前所有雷池版本(5.x~7.x)都使用 IF_backend_* 命名。如果未来版本改了前缀,只需修改脚本里两处 IF_backend_* 的匹配规则即可,改动量极小。


文件清单

部署完成后,你的系统里会有以下文件:

文件路径作用
/data/safeline/resources/nginx/proxy_params雷池全局 Nginx 配置(PP 信任 + 头传递 + 缓冲优化)
/root/auto_proxy_protocol.shinotify 自动监控脚本(后台常驻)
/root/apply_proxy_protocol.sh手动全量注入脚本(救急/维护用)
/etc/systemd/system/auto-proxy-protocol.servicesystemd 开机自启服务
/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

1
打赏

—— 评论区 ——

昵称
邮箱
网址
取消
博主栏壁纸
7 文章数
32 标签数
29 评论量
人生倒计时
舔狗日记