Dev

FRP 内网穿透与 Nginx 反向代理配置

上传于 2025年1月1日9 个章节

FRP 内网穿透与 Nginx 反向代理配置

9 个章节

使用 FRP 建立公网入口到内网服务的安全隧道,并通过 Nginx 统一处理 HTTPS 与反向代理。

  • #frp
  • #nginx
  • #内网穿透
  • #反向代理

1. 穿透原理

内网穿透的本质是流量中继。frp 通过在公网和内网之间建立一条长连接(隧道),将公网服务器的请求“搬运”到内网

流量接力路径:

  1. 客户端 (Browser):发起 HTTPS 请求 →\rightarrow www.zc6.asia:443
  2. Nginx (入口层):识别路径(如 /dev-api/),通过 127.0.0.1 转发给本地 frps
  3. frps (中转层):接收 Nginx 流量,通过隧道(Bind Port 8848)发往 frpc
  4. frpc (出口层):接收隧道流量,根据 localPort 转发给内网真实服务
  5. 内网服务 (Service):处理请求并原路返回响应。

2. frp 核心组件配置详解

A. 服务端 (frps.toml)

运行在公网服务器。它主要负责监听和中转。

# frp 服务端监听端口,供 frpc 连接
bindPort = 8848

# 鉴权 Token(建议开启,防止他人滥用你的中转服务器)
auth.token = "your_secure_token"

# HTTP/HTTPS 的虚拟主机端口(如果不用 Nginx 转发,直接用域名访问则必填)
# 但本场景下由 Nginx 统一处理 SSL,所以此处可不配

B. 客户端 (frpc.toml)

运行在内网环境(如 192.168.0.37)。定义具体的转发规则。

serverAddr = "111.228.57.71"
serverPort = 8848
auth.token = "your_secure_token"

# 规则 1:运维端前端 (静态网页)
[[proxies]]
name = "op-web"
type = "tcp"
localIP = "127.0.0.1" # 如果 frpc 和 Web 都在同一台机器,用 127.0.0.1
localPort = 80
remotePort = 8880      # 对应公网服务器开启的端口

# 规则 2:后端 API (SpringBoot 等)
[[proxies]]
name = "backend-api"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8081
remotePort = 18081

# 规则 3:图片/存储服务 (MinIO 等)
[[proxies]]
name = "image-storage"
type = "tcp"
localIP = "192.168.1.21" # 指向局域网内另一台机器的 IP
localPort = 8089
remotePort = 8089

3. Nginx 反向代理参数详解

在穿透场景下,Nginx 的配置决定了 Web 应用是否能感知到真实的请求上下文。

nginx配置解释

# 运维端后端接口转发
# --- 1. HTTP 自动跳转 HTTPS 模块 ---
server {
    listen 80;                           # 监听 80 端口(普通 HTTP)
    server_name www.zc6.asia;            # 匹配你的域名

    # 重定向逻辑:将所有 HTTP 请求永久重定向(301)到 HTTPS
    location / {
        return 301 https://$server_name$request_uri;
    }
}

# --- 2. HTTPS 主配置模块 ---
server {
    listen 443 ssl http2;                # 开启 443 端口,启用 SSL 和 HTTP/2 协议
    server_name www.zc6.asia;

    # SSL 证书路径(由 Let's Encrypt 提供)
    ssl_certificate /etc/letsencrypt/live/www.zc6.asia/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.zc6.asia/privkey.pem;

    # --- 核心:内网穿透代理配置 ---

    # 场景 A:后端 API 转发 (SpringBoot/Java)
    location /dev-api/ {
        # 转发目标:指向 frps 在本地开启的 18081 端口
        # 结尾的 "/" 表示从转发路径中剥离 "/dev-api/"
        proxy_pass http://127.0.0.1:18081/;

        # 重点:Host 透传
        # 作用:告诉后端真实的域名。如果后端有根据域名做逻辑校验(如 MinIO 签名),此项必填。
        proxy_set_header Host $host;

        # 重点:协议透传
        # 作用:告诉后端原始请求是 https。防止后端在做重定向时将用户跳回 http。
        proxy_set_header X-Forwarded-Proto $scheme;

        # 重点:真实 IP 透传
        # 作用:让后端程序通过 request.getRemoteAddr() 获取到真正的访客 IP,而非 127.0.0.1。
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # 场景 B:MinIO 图片服务 (带签名校验)
    location /zkss/ {
        # 结尾不加 "/":完整保留 "/zkss/" 路径发送给内网
        # 例如:访问 /zkss/1.png -> 转发给内网 /zkss/1.png
        proxy_pass http://127.0.0.1:8089;

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 性能优化:针对大文件/图片,关闭缓冲,直接流式传输
        proxy_buffering off;

        # 增加缓冲区,防止 S3 签名链接超长导致请求头溢出
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
    }

    # --- 3. 静态资源与本地文件 ---
    location /bds-webview {
        alias /opt/webview/dist;         # 映射服务器物理路径
        try_files $uri $uri/ /index.html; # 单页面应用(SPA)路由支持
        index index.html index.htm;
    }
}

4. 核心参数对比表

参数含义穿透中的作用
localIP内网服务地址frpc 所在机器能访问到的服务 IP
localPort内网服务端口真实服务的监听端口(如 8080)
remotePort公网映射端口公网服务器暴露给 frps 使用的入口端口
proxy_passNginx 转发路径必须指向公网本地的 remotePort
X-Forwarded-Proto原始协议字段解决 HTTPS 穿透后变 HTTP 的协议识别问题

5. 故障排查逻辑梳理

  1. 隧道层 (frp):
    • 检查 frpc 日志是否显示 login to server success
    • 检查 frpc 日志是否显示 start proxy [xxx] success
    • 在公网执行 netstat -tunlp | grep remotePort 确认端口已开启
  2. 网络层 (Firewall):
    • 云服务器安全组必须开放 serverPort (8848) 和所有 remotePort
    • 内网机器必须开放 localPort(如有 Windows 防火墙)
  3. 代理层 (Nginx):
    • 检查 proxy_pass 端口是否与 remotePort 严格对应
    • 检查 nginx -t 语法及是否有斜杠引起的路径偏移
  4. 应用层:
    • 在内网直接访问 localIP:localPort,确保服务本身正常

6.为什么 location 里面写“服务器公网 IP”也能访问?

在之前的尝试中,proxy_pass http://111.228.57.71:18081; 竟然也能通,这听起来很神奇,原因如下:

端口的“监听范围” (Listen on 0.0.0.0) 默认情况下,frps 开启的端口(如 18081)会监听在 0.0.0.0 上。这意味着这个端口不仅可以通过内部回环地址 127.0.0.1 访问,也可以通过服务器的“外网 IP”访问。

流量的“回环绕路” 当 Nginx 请求 111.228.57.71 时:

  1. Nginx 将请求发出,发向网卡。
  2. 操作系统的网络栈发现这个 IP 就是“我自己”。
  3. 流量直接在内核层被转回到了本地的 18081 端口。
  4. 结果:请求成功了。

为什么不建议这么做? 虽然能通,但存在三大弊端:

  • 安全性极低:如果你写公网 IP 能通,说明全世界任何人只要知道你的 IP 和端口,都能直接访问你的内网后端,跳过了 Nginx 的 SSL 加密和防火墙保护。
  • 性能损耗:流量走了一圈网卡驱动,不如 127.0.0.1(回环地址)直接在内存中传递效率高。
  • 防火墙依赖:如果你哪天为了安全,在云服务安全组里禁用了 18081 的入站,写公网 IP 的方式就会立刻失效,而写 127.0.0.1 则永远不受影响。

搜索文章与随记

输入关键词搜索全部公开内容