Nginx + FRP 端口收敛实战指南
Nginx + FRP 端口收敛实战指南
通过 TLS SNI 与 Nginx stream 模块实现内网服务的优雅暴露
基于 FRP v0.61+ / Nginx 1.25+
1. 问题背景与端口收敛概念
1.1 为什么需要端口收敛?
在私有部署场景中,我们经常面临这样的困境:
暴露前(混乱):
┌─────────────────────────┐
│ 公网 IP / 云服务器 │
├─────────────────────────┤
│ :80 → HTTP │
│ :443 → HTTPS │
│ :2222 → SSH │
│ :3306 → MySQL (危险!) │
│ :6379 → Redis (危险!) │
│ :8080 → API-1 │
│ :8081 → API-2 │
│ :9200 → ElasticSearch │
│ :27017→ MongoDB │
│ ...更多端口... │
└─────────────────────────┘
每个服务一个端口,安全隐患多,管理复杂
核心问题:
- 端口暴露过多 = 攻击面大
- 每个服务单独配置证书、权限复杂
- 云服务器安全组规则难以维护
- NAT 端口映射数量有限(云厂商通常限制 20-50 个)
1.2 什么是端口收敛?
端口收敛(Port Consolidation) 是将多个内部服务归一化到少量(甚至 1-2 个)公网端口的技术。
暴露后(收敛):
┌─────────────────────────┐
│ 公网 IP / 云服务器 │
├─────────────────────────┤
│ :443 → 统一入口 │
│ ↘ SNI 路由 │
│ ├→ api.example.com → :8080
│ ├→ db.example.com → :3306
│ ├→ cache.example.com → :6379
│ └→ web.example.com → :80
│ :2222 → 仅 SSH(严格限制)│
└─────────────────────────┘
1.3 本文目标
基于 TLS SNI 识别 + Nginx stream 模块四层代理,实现:
- 仅暴露
443(HTTPS) 和2222(SSH) 两个端口 - 通过域名自动路由到不同内网服务
- 所有流量加密,证书统一管理
- FRP 作为底层穿越隧道
2. 技术原理:TLS SNI 与 Nginx stream 模块
2.1 TLS SNI(Server Name Indication)
SNI 是 TLS 协议扩展,客户端在 TLS 握手初期就发送域名:
Client Server
│ │
│──── ClientHello ────────────────│
│ extensions[]: │
│ server_name: api.xxx.com│ ← 明文传输!
│ │
│◄─── ServerHello + Certificate ─│
│ │
关键点:
- SNI 在 TLS 握手
ClientHello阶段以明文传输 - 即使流量加密,域名也会暴露在 TLS 头中
- 利用这一特性,可以在 不解密 的情况下识别目标服务
- nginx stream 模块正是基于此实现四层智能路由
2.2 Nginx stream 模块 vs http 模块
Nginx 有两个核心模块:
| 特性 | http 模块 |
stream 模块 |
|---|---|---|
| 层级 | OSI 第七层(应用层) | OSI 第四层(传输层) |
| 协议 | HTTP/1.x, HTTP/2 | TCP 任意协议 |
| 能力 | URL 路由、证书、gzip | SNI 路由、SSL握手、TCP 转发 |
| 延迟 | 高(完整 HTTP 解析) | 低(SNI 直接路由) |
| 配置 | server {} 块 |
stream {} 块 |
2.3 工作原理图解
用户请求: https://api.example.com:443
│
▼
┌─────────────────────────────────┐
│ Nginx (stream 模块) │
│ 端口: 0.0.0.0:443 │
│ │
│ 读取 ClientHello 的 SNI │
│ server_name: api.example.com │
│ │
│ 比对 map 规则 │
│ └─── 匹配: api.example.com │
│ → proxy_pass 127.0.0.1:9443│
│ │
│ 未匹配 → default │
│ → proxy_pass 127.0.0.1:9080│
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ FRP (frpc) 隧道 │
│ 127.0.0.1:9443 ↔ 内网:443 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 内网服务(需自行配置证书) │
│ :443 API Backend │
│ :3306 MySQL │
│ :6379 Redis │
└─────────────────────────────────┘
2.4 为什么不直接用 Nginx http 模块?
HTTP 模块也可以做反向代理,但:
| 场景 | http 模块 | stream + SNI |
|---|---|---|
| 非 HTTP 服务(MySQL/Redis) | ❌ 无法直接代理 | ✅ 原生支持 |
| SNI 路由(不解密直接转发) | ❌ 需要终止 TLS | ✅ 原生支持 |
| WebSocket | ✅ 支持 | ✅ 支持 |
| gRPC | ✅ 支持 | ✅ 支持 |
| 性能开销 | 较高(完整 HTTP 解析) | 更低 |
结论:混合架构才是最优解
- HTTP/HTTPS Web 服务 →
http模块(需要 URL 路由时) - 所有服务统一入口 →
stream模块 + SNI 路由 - SSH 等非 TLS 服务 →
stream模块单独端口
3. 最终架构图与数据流
3.1 完整架构图
┌──────────────────────────────────────────────────────────────────────────────┐
│ 公网服务器 (VPS/云) │
│ │
│ 443/tcp ────────────────────────────────┐ │
│ 2222/tcp ───────────────────┐ │ │
│ │ │ │
│ ┌───────────┴───────────┴───────────┐ │
│ │ Nginx (stream) │ │
│ │ │ │
│ │ stream { │ │
│ │ server { │ │
│ │ listen 443; │ │
│ │ ssl_preread on; │ │
│ │ proxy_pass ...; │ │
│ │ } │ │
│ │ } │ │
│ └───────────┬───────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ SNI 路由 SSH 转发 │
│ (根据域名) (直接转发) │
│ │
│ 22 ◄────────────────────────────────────────────────────────── SSH │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
│
┌───────────┴───────────┐
│ FRP (frpc) │
│ TCP 隧道 + TLS 加密 │
└───────────┬───────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 内网机器 1 │ │ 内网机器 2 │ │ 内网机器 3 │
│ │ │ │ │ │
│ :443 API │ │ :3306 MySQL│ │ :6379 Redis│
│ :80 Web │ │ :22 SSH │ │ │
└────────────┘ └────────────┘ └────────────┘
3.2 数据流详解
场景一:访问 https://api.example.com
1. 用户浏览器
└─► api.example.com:443 (TLS ClientHello with SNI: api.example.com)
2. 公网 Nginx (stream)
└─► 读取 SNI → 匹配 map → proxy_pass → 127.0.0.1:9443 (FRP隧道入口)
3. 公网 FRP Server (frps)
└─► 接收加密流量 → 转发给内网 FRP Client
4. 内网 FRP Client (frpc)
└─► 解密 → 转发给内网服务 10.0.0.2:443
5. 内网 API 服务(需配置 api.example.com 的证书)
└─► 处理请求 → 响应原路返回
场景二:SSH 登录内网机器
1. 用户 SSH 客户端
└─► 公网IP:2222
2. 公网 Nginx (stream - SSH server 块)
└─► 直接 TCP 代理 → 127.0.0.1:2223 (FRP SSH 隧道) → 内网 10.0.0.2:22
3. 内网机器 SSH 服务
└─► 验证密钥 → 建立会话
4. 详细配置步骤
4.1 环境说明
| 角色 | 主机 | 组件 |
|---|---|---|
| 公网服务器 | 1.2.3.4 (VPS) |
Nginx, FRP Server (frps) |
| 内网机器 | 192.168.1.100 (家庭/公司内网) |
FRP Client (frpc), 各种服务 |
4.2 公网服务器配置
步骤 1:安装 Nginx(包含 stream 模块)
# Ubuntu/Debian
sudo apt update
sudo apt install nginx -y
# 验证 stream 模块已启用(更准确的检查方式)
nginx -V 2>&1 | grep -- '--with-stream'
# 输出包含 "--with-stream" 即成功
# CentOS/RHEL
sudo yum install nginx -y
步骤 2:下载 FRP(Server 端)
cd /tmp
wget https://github.com/fatedier/frp/releases/download/v0.61.1/frp_0.61.1_linux_amd64.tar.gz
tar -xzf frp_0.61.1_linux_amd64.tar.gz
sudo mv frp_0.61.1_linux_amd64 /opt/frps
cd /opt/frps
步骤 3:配置 FRP Server (/opt/frps/frps.toml)
# frps.toml - FRP Server 配置 (v0.61+ 标准语法)
bindAddr = "0.0.0.0"
bindPort = 7000
# Web 管理界面(可选)
webServer.addr = "0.0.0.0"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "your_admin_password"
# 认证配置
[auth]
method = "token"
token = "your_very_strong_token_here_at_least_32_chars"
# TLS 传输配置(强制加密)
[transport]
tls.enable = true
tls.certFile = "/opt/frps/tls/frp.crt"
tls.keyFile = "/opt/frps/tls/frp.key"
# 心跳与多路复用
heartbeatInterval = 30
heartbeatTimeout = 90
tcpMux = true
# 安全:代理端口仅绑定本地(防止暴露到公网)
proxyBindAddr = "127.0.0.1"
说明:
proxyBindAddr = "127.0.0.1"使得 frps 只监听本地地址转发流量,避免代理端口(如 9443)被公网直接访问。tcpMux大幅减少连接数,提高穿透稳定性。
步骤 4:生成 FRP TLS 证书(自签名)
sudo mkdir -p /opt/frps/tls
cd /opt/frps/tls
# 生成自签名证书(有效期10年)
openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 \
-subj "/CN=*.frp.internal" \
-keyout frp.key -out frp.crt
sudo chmod 600 frp.key frp.crt
步骤 5:配置 Nginx stream(核心配置)
创建独立的配置文件(推荐):
sudo nano /etc/nginx/conf.d/stream-sni.conf
完整配置(已验证语法正确):
# /etc/nginx/conf.d/stream-sni.conf
stream {
# 日志格式 - 包含 SNI 信息
log_format sni '$remote_addr:$remote_port [$time_local] '
'$protocol -> $upstream_addr '
'sni="$ssl_preread_server_name" '
'bytes_s=$bytes_sent bytes_r=$bytes_received '
'upstream_connect_time=$upstream_connect_time';
access_log /var/log/nginx/stream_sni.log sni;
error_log /var/log/nginx/stream_sni.error.log warn;
# SNI → 本地 FRP 代理端口映射(直接返回 IP:Port,无需 upstream 块)
map $ssl_preread_server_name $target_backend {
api.example.com 127.0.0.1:9443;
db.example.com 127.0.0.1:9444;
cache.example.com 127.0.0.1:9445;
web.example.com 127.0.0.1:9446;
default 127.0.0.1:9080; # 未匹配的域名走默认
}
# ===== HTTPS 统一入口(SNI 路由) =====
server {
listen 443 reuseport;
ssl_preread on; # 关键:开启 SNI 读取
proxy_connect_timeout 10s;
proxy_timeout 3600s;
proxy_pass $target_backend;
}
# ===== SSH 独立入口 =====
server {
listen 2222;
proxy_connect_timeout 10s;
proxy_timeout 1800s;
proxy_pass 127.0.0.1:2223; # FRP 映射的 SSH 端口
}
}
重要说明:
- 此配置为 TLS 透传(Passthrough),Nginx 仅读取 SNI,不终止 TLS 连接。
- 内网目标服务(如
api.example.com:443)必须自行配置该域名的有效证书,否则浏览器会报证书错误。 - 若需要记录真实客户端 IP,可配合
proxy_protocol(见第 10 节扩展)。
步骤 6:启动 FRP Server
# 创建 systemd 服务
sudo nano /etc/systemd/system/frps.service
[Unit]
Description=FRP Server
After=network.target
[Service]
ExecStart=/opt/frps/frps -c /opt/frps/frps.toml
Restart=on-failure
RestartSec=5s
User=root
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable frps
sudo systemctl start frps
sudo systemctl status frps
4.3 内网机器配置
步骤 1:下载 FRP Client
cd /tmp
wget https://github.com/fatedier/frp/releases/download/v0.61.1/frp_0.61.1_linux_amd64.tar.gz
tar -xzf frp_0.61.1_linux_amd64.tar.gz
sudo mv frp_0.61.1_linux_amd64 /opt/frpc
cd /opt/frpc
步骤 2:配置 FRP Client (/opt/frpc/frpc.toml)
# frpc.toml - FRP Client 配置 (v0.61+ 标准语法)
serverAddr = "1.2.3.4"
serverPort = 7000
# 认证(必须与服务端一致)
[auth]
method = "token"
token = "your_very_strong_token_here_at_least_32_chars"
# TLS 传输配置
[transport]
tls.enable = true
# 自签名证书需指定 CA/证书文件
tls.trustedCaFile = "/opt/frpc/tls/frp.crt"
tcpMux = true
heartbeatInterval = 30
heartbeatTimeout = 90
# ===== 定义代理(注意使用 [[proxies]] 官方复数形式)=====
[[proxies]]
name = "ssh-tunnel"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 2223 # 对应 Nginx 2222 代理的目标
[[proxies]]
name = "api-https"
type = "tcp"
localIP = "127.0.0.1"
localPort = 443 # 内网 API 服务的 HTTPS 端口
remotePort = 9443 # 对应 Nginx map 中的 127.0.0.1:9443
[[proxies]]
name = "mysql"
type = "tcp"
localIP = "192.168.1.101" # 若 MySQL 在其他机器
localPort = 3306
remotePort = 9444
[[proxies]]
name = "redis"
type = "tcp"
localIP = "127.0.0.1"
localPort = 6379
remotePort = 9445
[[proxies]]
name = "web-http"
type = "tcp"
localIP = "127.0.0.1"
localPort = 80
remotePort = 9446
注意:
tls.trustedCaFile指向服务端证书的 CA 或自签名证书本身,用于客户端验证服务端身份。- 若内网服务分布在多台机器,修改对应
localIP即可。
步骤 3:复制 TLS 证书到内网机器
# 在公网服务器导出证书
sudo cat /opt/frps/tls/frp.crt
# 通过安全方式(scp/rsync)传输到内网机器
# 假设内网机器 IP 为 192.168.1.100
scp /opt/frps/tls/frp.crt user@192.168.1.100:/tmp/
# 在内网机器上放置证书
sudo mkdir -p /opt/frpc/tls
sudo mv /tmp/frp.crt /opt/frpc/tls/
sudo chmod 644 /opt/frpc/tls/frp.crt
步骤 4:启动 FRP Client
sudo nano /etc/systemd/system/frpc.service
[Unit]
Description=FRP Client
After=network.target
[Service]
ExecStart=/opt/frpc/frpc -c /opt/frpc/frpc.toml
Restart=on-failure
RestartSec=5s
User=root
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable frpc
sudo systemctl start frpc
sudo systemctl status frpc
4.4 多内网机器扩展
当有多个内网机器时,在每台机器上运行独立的 frpc,并分配不同的 remotePort。例如:
机器 A (192.168.1.100) - frpc 配置:
[[proxies]]
name = "machine-a-api"
type = "tcp"
localIP = "127.0.0.1"
localPort = 443
remotePort = 9443
机器 B (192.168.1.101) - frpc 配置:
[[proxies]]
name = "machine-b-mysql"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3306
remotePort = 9444
公网 Nginx 的 map 保持不变,只要 remotePort 与 proxy_pass 的后端端口一致即可。
5. 遇到的典型问题及解决方法
问题 1:SNI 路由不生效,流量全部打到 default
症状:curl -v --resolve api.example.com:443:1.2.3.4 https://api.example.com 访问到默认后端。
排查步骤:
# 1. 确认客户端发送了 SNI(抓包)
sudo tcpdump -i any -nn 'tcp port 443' -A | grep -i "server.name"
# 2. 查看 Nginx stream 日志(已配置 SNI 字段)
sudo tail -f /var/log/nginx/stream_sni.log
# 3. 确认 ssl_preread 已开启
grep -A5 "listen 443" /etc/nginx/conf.d/stream-sni.conf
常见原因:
- 客户端使用 IP 直接访问 HTTPS(浏览器地址栏输入
https://1.2.3.4不会发送 SNI) - Nginx 中
ssl_preread on;未配置或拼写错误 map中的域名与实际 SNI 不匹配(注意大小写,Nginx 默认区分,可用~*修饰符)
解决方法:
# 使用不区分大小写的匹配
map $ssl_preread_server_name $target_backend {
~*^api\.example\.com$ 127.0.0.1:9443;
default 127.0.0.1:9080;
}
问题 2:FRP TLS 握手失败
症状:
frpc[12345]: [WARN] tls: failed to dial remote: tls: first record does not look like a TLS handshake
原因:服务端未开启 TLS,客户端强制 TLS 连接。
解决方法:确保服务端 [transport] 中 tls.enable = true,且证书路径正确。若使用自签名,客户端必须指定 tls.trustedCaFile。
问题 3:MySQL 连接被拒绝(字符编码/协议问题)
症状:ERROR 2013: Lost connection to MySQL server at 'reading initial communication packet'
原因:Nginx stream 代理 MySQL,但 FRP 端口映射错误或 MySQL 绑定了本地地址。
排查:
# 确认 FRP 代理状态
sudo journalctl -u frpc -f
# 确认 Nginx 的 proxy_pass 端口与 FRP remotePort 一致
# 例如 Nginx 中 proxy_pass 127.0.0.1:9444,FRP 中 remotePort = 9444
解决方法:统一端口配置;若 MySQL 只监听 127.0.0.1,需改为 0.0.0.0 或内网 IP。
问题 4:SSH 连接超时
症状:ssh: connect to host 1.2.3.4 port 2222: Connection timed out
排查:
# 1. 确认防火墙放行
sudo iptables -L -n | grep 2222
# 或
sudo ufw status
# 2. 确认云安全组放行 2222/tcp
# 3. 确认 Nginx stream 的 SSH server 块配置正确
问题 5:gRPC 或 HTTP/2 无法正常工作(证书错误)
症状:gRPC 客户端报错 SSL_ERROR_BAD_CERT_DOMAIN 或 http2: unexpected greeting。
根本原因:由于本方案是 TLS 透传,Nginx 不终止 TLS,内网服务必须自己配置与访问域名匹配的证书。如果内网服务使用自签名证书或证书域名与 api.example.com 不符,浏览器或 gRPC 客户端会拒绝连接。
解决方法:
- 为内网服务(如内网 Nginx、API 进程)配置正确的证书(可用 Let’s Encrypt 或内部 CA)。
- 测试时可用
curl -k忽略证书错误,但生产环境必须使用有效证书。
6. 验证方法
6.1 基础连通性测试
# 公网服务器本地测试
nc -zv 127.0.0.1 2222
# 公网测试 SSH 端口
nc -zv 1.2.3.4 2222
# 公网测试 HTTPS 端口(验证 SNI 路由)
openssl s_client -connect 1.2.3.4:443 -servername api.example.com
6.2 SNI 路由验证
# 测试特定域名的 SNI 路由(使用 curl 指定域名)
curl -I https://api.example.com \
--resolve api.example.com:443:1.2.3.4 \
-w "\n%{http_code}\n" 2>/dev/null
# 查看 Nginx 日志确认 SNI 值
tail -f /var/log/nginx/stream_sni.log
6.3 FRP 隧道验证
# 查看 FRP 连接状态(如果开启了 webServer)
curl -s http://1.2.3.4:7500/api/status | jq
# 或在公网服务器查看日志
sudo journalctl -u frps -f
# 在内网机器查看客户端状态
/opt/frpc/frpc status -c /opt/frpc/frpc.toml
6.4 端到端服务测试
# 测试 API 服务
curl https://api.example.com/api/v1/health \
--resolve api.example.com:443:1.2.3.4
# 测试 MySQL 连接(注意使用 Nginx 暴露的端口? 不,应直接连接 FRP 端口?)
# 实际上 MySQL 已通过 SNI 路由,访问 1.2.3.4:443 并发送 db.example.com SNI 即可
mysql -h 1.2.3.4 -P 443 -u root -p --ssl-mode=REQUIRED
# 测试 SSH 隧道
ssh -p 2222 -o StrictHostKeyChecking=no user@1.2.3.4
6.5 日志分析
# Nginx stream 访问日志
tail -f /var/log/nginx/stream_sni.log
# FRP Server 日志
journalctl -u frps -f
# FRP Client 日志
journalctl -u frpc -f
7. 扩展与备选方案
7.1 多域名泛解析 SNI 路由
map $ssl_preread_server_name $target_backend {
~^(?<app>.+)\.example\.com$ 127.0.0.1:9${app}_port; # 动态映射(需配合脚本)
default 127.0.0.1:9080;
}
更实用的方式:使用 map 配合正则和变量,但需提前定义好端口映射。
7.2 记录真实客户端 IP(Proxy Protocol)
若内网服务支持 Proxy Protocol(如 Nginx、HAProxy),可获取真实客户端 IP。
步骤:
- 公网 Nginx stream 配置添加
proxy_protocol on; - FRP 代理配置添加
proxyProtocolVersion = "v2" - 内网 Nginx 或服务监听时启用
proxy_protocol
示例(仅展示增量配置):
# Nginx stream server 块中
server {
listen 443 reuseport proxy_protocol; # 接收 PROXY 协议
ssl_preread on;
proxy_pass $target_backend;
}
# frpc.toml 的 proxy 条目中
[[proxies]]
name = "api-https"
type = "tcp"
localIP = "127.0.0.1"
localPort = 443
remotePort = 9443
proxyProtocolVersion = "v2" # 发送 PROXY 协议给内网
内网 Nginx 配置:
server {
listen 443 ssl http2 proxy_protocol;
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
...
}
7.3 替代方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Nginx + FRP(本文) | 成熟稳定,SNI 路由灵活 | 配置较复杂 | 追求性价比,多服务场景 |
| Cloudflare Tunnel | 零配置,CDN 加速 | 依赖 CF,流量经过第三方 | 快速部署,不在意延迟 |
| WireGuard + Nginx | 内网 VPN 更安全 | 不支持 SNI 路由 | 纯内网访问,无公网暴露 |
| HAProxy + FRP | 性能更高 | 配置更复杂 | 超高并发场景 |
| Traefik | 自动 HTTPS,Let’s Encrypt | 主要面向 HTTP | 纯 Web 服务 |
8. 安全建议
8.1 必做安全措施
✅ 1. FRP 强制 TLS
- frps.toml: [transport] tls.enable = true
- frpc.toml: [transport] tls.enable = true
- 所有流量加密,防止中间人攻击
✅ 2. FRP 强认证 Token
- auth.token = "长随机字符串(≥32 字符)"
- 使用密码管理器生成
✅ 3. 限制 FRP 代理端口仅本地监听
- frps.toml: proxyBindAddr = "127.0.0.1"
- 防止代理端口直接暴露公网
✅ 4. SSH 密钥登录
- 禁止密码登录
- 使用 ED25519 或 RSA-4096 密钥
✅ 5. 云服务器安全组最小化
- 只放行 443/tcp, 2222/tcp
- 按需放行 ICMP(ping)
- 绝对不放行 MySQL/Redis 端口
✅ 6. Nginx 限流(防止 DDoS)
8.2 Nginx 安全配置
stream {
# 连接限流
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
listen 443 reuseport;
ssl_preread on;
limit_conn conn_limit 100;
proxy_connect_timeout 15s;
proxy_timeout 3600s;
proxy_buffer_size 64k;
}
server {
listen 2222;
limit_conn conn_limit 10;
proxy_connect_timeout 10s;
proxy_timeout 1800s;
}
}
8.3 日志轮转(防止磁盘爆满)
创建 /etc/logrotate.d/nginx-frp:
/var/log/nginx/stream_sni.log
/var/log/nginx/stream_sni.error.log
{
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 nginx adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}
FRP 日志(若使用 systemd 管理的 journald 会自动轮转,无需额外配置)。
8.4 定期维护
# 每月更新 FRP/Nginx
sudo apt update && sudo apt upgrade nginx frp
# 定期检查证书有效期
sudo find /opt -name "*.crt" -exec openssl x509 -noout -dates -in {} \;
# 查看异常登录
sudo journalctl -u frps | grep -i "authentication\|fail\|error"
9. 总结与核心要点
9.1 一图总结
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 用户视角 │ │ 公网 VPS │ │ 内网视角 │
│ │ │ │ │ │
│ HTTPS://api │ ───► │ Nginx:443 │ ───► │ FRP Client │
│ .example.com│ │ (SNI路由) │ │ │
│ │ │ │ │ │
│ SSH 公网IP │ ───► │ Nginx:2222 │ ───► │ FRP Client │
│ :2222 │ │ (TCP转发) │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
:443 TLS :443/:22
:2222 解密/转发 /:3306/:6379
9.2 核心要点速查表
| 要点 | 说明 |
|---|---|
| 只暴露 2 个端口 | 443 (HTTPS) + 2222 (SSH) |
| SNI 是关键 | TLS ClientHello 中的 server_name 字段路由 |
| ssl_preread on | 必须开启才能用 stream 模块做 SNI 路由 |
| FRP 强制 TLS | tls.enable = true |
| Token 认证 | FRP 双向认证 token |
| proxyBindAddr = “127.0.0.1” | 防止 FRP 代理端口暴露公网 |
| 内网服务需自备证书 | 透传模式下 Nginx 不提供证书 |
| map 直接返回 IP:Port | 避免 upstream 混用错误 |
| 测试要完整 | 从公网测试,不要只本地测试 |
9.3 快速命令清单
# 重启 Nginx
sudo nginx -t && sudo systemctl reload nginx
# 重启 FRP Server
sudo systemctl restart frps
# 重启 FRP Client
sudo systemctl restart frpc
# 查看 FRP 状态
curl -s http://1.2.3.4:7500/api/status
# 测试 SNI 路由
openssl s_client -connect 1.2.3.4:443 -servername api.example.com
# 查看连接状态
ss -tlnp | grep -E "443|2222"
9.4 适用场景判断
用这套方案 ✅ 如果:
- 有多台内网服务需要暴露
- 云服务器端口数有限制
- 想统一管理证书(内网服务各自证书,公网不存私钥)
- 有一定 Linux 运维能力
考虑备选方案 🔄 如果:
- 只是临时测试 → Cloudflare Tunnel
- 纯 Web 服务且想自动化证书 → Let’s Encrypt + Nginx http 模块
- 需要最高性能 → HAProxy + FRP
- 完全不信任公网 → WireGuard 全隧道
10. 生产环境额外注意事项
-
证书归属:本方案为 TLS 透传,Nginx 不持有任何域名证书。请确保内网服务(如
api.example.com:443)已正确配置api.example.com的证书(可用 Let’s Encrypt 或内部 CA)。 -
SNI 盲区处理:某些老旧客户端或不支持 SNI 的工具可能不发送
server_name,建议default目标指向一个友好的提示服务(如返回 “SNI required” 的静态页面)。 -
FRP 心跳与 TCP 多路复用:已在配置中加入
heartbeatInterval、heartbeatTimeout和tcpMux,可有效应对家庭宽带 NAT 超时问题。 -
日志轮转:务必配置 logrotate(见 8.3),否则高流量下
/var/log/nginx/stream_sni.log可能撑爆磁盘。 -
系统限制:在
/etc/systemd/system/frps.service和frpc.service中已添加LimitNOFILE=1048576,防止高并发下文件描述符耗尽。 -
公网安全组:仅需开放
443/tcp和2222/tcp。不可开放 FRP 的7000端口(管理端口)或代理端口(如9443)到公网。 -
web服务:若你需要在同一台公网服务器上额外运行 Nginx
http模块,反向代理某些 Web 服务,并且希望对外表现为标准 443 端口(实际监听非标端口如 8443),请添加以下配置:
server {
listen 127.0.0.1:8443 ssl http2;
server_name files.example.com;
# 阻止 Nginx 在自动生成的重定向中加入端口号(8443)
port_in_redirect off;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080;
}
}
文档版本:v2.0
适用 FRP 版本:v0.61+
适用 Nginx 版本:1.25+ (stream 模块已稳定)
最后更新:2026-04-26
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)