为什么需要内网穿透
在日常开发和运维中,我们经常遇到这样的场景:本地开发了一个Web应用想在手机上测试,家里NAS需要外网访问,公司内网的服务需要临时暴露给合作伙伴,或者物联网设备需要被远程管理。这些场景的核心问题是一样的——你的设备在NAT或防火墙后面,没有公网IP,外部无法主动连接进来。
内网穿透(NAT Traversal)技术正是为解决这一问题而生。它通过一台拥有公网IP的VPS作为中转枢纽,让位于内网的服务能够被外网稳定访问。在众多内网穿透方案中,FRP(Fast Reverse Proxy)因其配置简单、功能丰富、社区活跃而成为最受欢迎的开源方案。
本文将从零开始,在VPS上部署一套生产级的内网穿透系统,包含FRP服务端、Nginx反向代理、TLS加密、多协议支持以及完整的监控与安全加固方案。

FRP架构与工作原理
FRP采用经典的C/S架构,由两个核心组件构成:
- frps(服务端):运行在有公网IP的VPS上,负责监听来自客户端的连接请求,并将外部流量转发给对应的内网服务
- frpc(客户端):运行在内网机器上,主动向frps建立连接,注册需要暴露的服务信息
工作流程如下:
- frpc启动后,向frps注册一个或多个代理规则(proxy)
- frps为每个代理规则分配一个端口(或使用配置的域名)
- 外部用户访问VPS的对应端口/域名
- frps将请求通过已建立的控制连接转发给frpc
- frpc将请求转发给本地的实际服务
这种设计的巧妙之处在于:内网客户端主动外连,无需在防火墙上开放入站端口,完美绕过NAT限制。同时,控制连接是持久的,frps可以随时将新请求推送给frpc,实现了真正的反向代理。
支持的代理类型
FRP支持多种代理类型,覆盖了绝大多数使用场景:
| 类型 | 说明 | 典型场景 |
|---|---|---|
| tcp | TCP端口映射 | SSH、数据库、自定义TCP服务 |
| udp | UDP端口映射 | DNS、游戏服务器、VPN |
| http | HTTP反向代理 | Web应用、API服务 |
| https | HTTPS反向代理 | 安全Web服务 |
| stcp | 加密TCP(需密钥) | 安全访问内网服务 |
| xtcp | 点对点穿透 | 大文件传输、降低延迟 |

VPS服务端部署与配置
安装FRP服务端
在VPS上执行以下命令安装frps:
1
2
3
4
5
6
7
8
9
10 # 下载最新版本(请替换为实际最新版本号)
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
cp frp_0.61.1_linux_amd64/frps /usr/local/bin/
chmod +x /usr/local/bin/frps
# 验证安装
frps --version
配置frps.toml
从v0.52.0开始,FRP全面采用TOML格式配置。创建
|
1
|
/etc/frp/frps.toml
|
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 # frps.toml - 服务端配置
# 监听端口(frpc连接用)
bindPort = 7000
# 用于TCP代理的起始端口
bindPort = 7000
proxyBindAddr = "0.0.0.0"
# HTTP/HTTPS代理的虚拟主机端口
vhostHTTPPort = 8080
vhostHTTPSPort = 8443
# 认证令牌 - 务必使用强随机字符串
auth.method = "token"
auth.token = "your-strong-random-token-here-change-me-2024"
# Dashboard管理面板
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "secure-dashboard-password"
# 日志配置
log.to = "/var/log/frps.log"
log.level = "info"
log.maxDays = 7
# 性能优化
transport.maxPoolCount = 5
transport.tcpMux = true
transport.tcpMuxKeepaliveInterval = 60
# 安全设置
allowPorts = [
{ start = 6000, end = 6100 },
{ start = 2200, end = 2222 },
# SSH常用范围
{ start = 3306, end = 3306 },
# MySQL
{ start = 5432, end = 5432 },
# PostgreSQL
]
创建Systemd服务
创建
|
1
|
/etc/systemd/system/frps.service
|
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 [Unit]
Description=FRP Server Service
After=network.target
[Service]
Type=simple
User=nobody
Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
1
2
3
4
5 # 启动并设置开机自启
systemctl daemon-reload
systemctl enable frps
systemctl start frps
systemctl status frps

客户端配置与多场景实战
基础frpc.toml配置
在内网机器上安装frpc(与frps相同的安装包,只是使用frpc二进制),创建
|
1
|
/etc/frp/frpc.toml
|
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 # frpc.toml - 客户端配置
# 服务端地址
serverAddr = "your-vps-ip"
serverPort = 7000
# 认证令牌(与服务端一致)
auth.method = "token"
auth.token = "your-strong-random-token-here-change-me-2024"
# 日志
log.to = "/var/log/frpc.log"
log.level = "info"
log.maxDays = 3
# 心跳与重连
transport.heartbeatInterval = 30
transport.heartbeatTimeout = 90
# ============ 代理规则 ============
# 规则1: 暴露内网SSH
[[proxies]]
name = "ssh-home"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
# 规则2: 暴露内网Web服务
[[proxies]]
name = "web-app"
type = "http"
localIP = "127.0.0.1"
localPort = 3000
customDomains = ["app.yourdomain.com"]
# 规则3: 暴露内网HTTPS服务
[[proxies]]
name = "web-secure"
type = "https"
localIP = "127.0.0.1"
localPort = 8443
customDomains = ["secure.yourdomain.com"]
# 规则4: 加密TCP - 安全访问内网数据库
[[proxies]]
name = "mysql-stcp"
type = "stcp"
secretKey = "database-access-secret-key"
localIP = "127.0.0.1"
localPort = 3306
STCP安全访问
STCP(Secret TCP)是FRP的加密代理类型,只有知道密钥的客户端才能连接。对于数据库等敏感服务,强烈建议使用STCP而非普通TCP。
访问端需要配置一个STCP visitor:
1
2
3
4
5
6
7
8
9
10
11
12
13 # 访问端配置 - frpc_visitor.toml
serverAddr = "your-vps-ip"
serverPort = 7000
auth.method = "token"
auth.token = "your-strong-random-token-here-change-me-2024"
[[visitors]]
name = "mysql-visitor"
type = "stcp"
serverName = "mysql-stcp"
secretKey = "database-access-secret-key"
bindAddr = "127.0.0.1"
bindPort = 3306
这样访问端就可以通过
|
1
|
127.0.0.1:3306
|
连接到内网MySQL了。
点对点穿透(XTCP)
当两端都在NAT后面但至少一端NAT类型较松时,XTCP可以建立直连通道,流量不经过VPS中转:
1
2
3
4
5
6
7 # 内网服务端
[[proxies]]
name = "p2p-file-share"
type = "xtcp"
secretKey = "p2p-secret"
localIP = "127.0.0.1"
localPort = 8080
1
2
3
4
5
6
7
8 # 访问端
[[visitors]]
name = "p2p-visitor"
type = "xtcp"
serverName = "p2p-file-share"
secretKey = "p2p-secret"
bindAddr = "127.0.0.1"
bindPort = 8080
Nginx反向代理与TLS终结
直接暴露FRP的vhost端口不够优雅,也不便于管理多个域名。通过Nginx作为前端反向代理,我们可以实现:
- 统一的TLS证书管理(Let’s Encrypt自动续签)
- 基于域名的虚拟主机路由
- 访问日志、限流、IP黑名单等安全策略
- HTTP自动跳转HTTPS
Nginx配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 # /etc/nginx/sites-available/frp-proxy.conf
# HTTP跳转HTTPS
server {
listen 80;
server_name app.yourdomain.com secure.yourdomain.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS - app.yourdomain.com
server {
listen 443 ssl http2;
server_name app.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/app.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
# 代理到FRP的HTTP vhost端口
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 300s;
# 限流
limit_req zone=frp burst=20 nodelay;
}
}
# HTTPS - secure.yourdomain.com
server {
listen 443 ssl http2;
server_name secure.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/secure.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/secure.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass https://127.0.0.1:8443;
proxy_ssl_verify off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# 限流区域定义(放在http块中)
# limit_req_zone $binary_remote_addr zone=frp:10m rate=10r/s;
Let’s Encrypt自动证书
1
2
3
4
5
6
7
8
9
10
11
12 # 安装certbot
apt install -y certbot python3-certbot-nginx
# 首次获取证书
certbot certonly --webroot -w /var/www/certbot \
-d app.yourdomain.com \
-d secure.yourdomain.com \
--email admin@yourdomain.com \
--agree-tos --non-interactive
# 设置自动续签(certbot会自动添加cron)
certbot renew --dry-run

安全加固与防护策略
防火墙配置
只开放必要的端口,遵循最小权限原则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 # UFW防火墙规则
ufw default deny incoming
ufw default allow outgoing
# SSH(建议改端口)
ufw allow 2200/tcp
# FRP服务端端口
ufw allow 7000/tcp
# Nginx
ufw allow 80/tcp
ufw allow 443/tcp
# FRP TCP端口范围
ufw allow 6000:6100/tcp
# 启用防火墙
ufw enable
ufw status verbose
FRP Dashboard安全
FRP的Dashboard包含敏感信息,务必做好防护:
- 绑定到
1127.0.0.1
,不对外暴露
- 如需远程查看,通过SSH隧道:
1ssh -L 7500:127.0.0.1:7500 root@your-vps
- 设置强密码,定期更换
认证令牌管理
认证令牌是整个系统的安全基石,必须妥善管理:
1
2
3 # 生成强随机令牌
openssl rand -base64 32
# 示例输出: k8sJx2mP9vN3qR7wL5tY1aB4cD6eF0gH+iJkMnOpQrStUvWxYz
令牌管理最佳实践:
- 每个客户端使用不同的令牌(FRP v0.52+支持OIDC认证)
- 定期轮换令牌,更新所有客户端配置后重启
- 不要将令牌提交到Git仓库,使用环境变量或密钥管理工具
- 生产环境建议使用OIDC认证替代token认证
流量加密与审计
即使不使用STCP,FRP也支持TLS加密传输:
1
2
3
4
5 # frps.toml 添加
transport.tls.force = true
# frpc.toml 添加
transport.tls.enable = true
开启后,frpc与frps之间的所有通信都会经过TLS加密,防止中间人窃听。
监控与运维自动化
Prometheus监控集成
FRP从v0.52.0开始内置了Prometheus指标导出。在frps.toml中添加:
1
2
3
4
5 # frps.toml 添加
enablePrometheus = true
# 指标会通过Dashboard端口暴露
# 访问 http://127.0.0.1:7500/metrics
配置Prometheus采集:
1
2
3
4
5
6
7
8
9 # prometheus.yml
scrape_configs:
- job_name: 'frps'
static_configs:
- targets: ['127.0.0.1:7500']
metrics_path: '/metrics'
basic_auth:
username: admin
password: secure-dashboard-password
关键Grafana监控指标
以下指标建议在Grafana中设置告警:
| 指标 | 含义 | 告警阈值 |
|---|---|---|
| frps_proxy_count | 当前在线代理数 | 低于预期值 |
| frps_client_count | 当前连接客户端数 | 突增可能是攻击 |
| frps_proxy_traffic_in_bytes | 入站流量 | 超过带宽限制 |
| frps_proxy_traffic_out_bytes | 出站流量 | 超过带宽限制 |
| frps_proxy_last_start_time | 代理最近启动时间 | 频繁重启可能异常 |
自动健康检查脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 #!/bin/bash
# /usr/local/bin/frps-healthcheck.sh
FRPS_URL="http://127.0.0.1:7500/api/serverinfo"
AUTH_USER="admin"
AUTH_PASS="secure-dashboard-password"
ALERT_EMAIL="ops@yourdomain.com"
LOG="/var/log/frps-healthcheck.log"
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# 检查frps进程
if ! pgrep -x frps > /dev/null; then
echo "[$timestamp] CRITICAL: frps process not running!" >> $LOG
systemctl restart frps
echo "[$timestamp] Attempted restart" >> $LOG
exit 1
fi
# 检查API响应
response=$(curl -s -u "$AUTH_USER:$AUTH_PASS" "$FRPS_URL" 2>&1)
if [ $? -ne 0 ] || [ -z "$response" ]; then
echo "[$timestamp] WARNING: frps API not responding" >> $LOG
systemctl restart frps
exit 1
fi
# 检查在线代理数
proxy_count=$(echo "$response" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('proxies',[])))" 2>/dev/null)
if [ -z "$proxy_count" ] || [ "$proxy_count" -lt 1 ]; then
echo "[$timestamp] WARNING: No proxies registered" >> $LOG
fi
echo "[$timestamp] OK: frps running, $proxy_count proxies active" >> $LOG
添加到cron定时执行:
1
2 # 每5分钟检查一次
*/5 * * * * /usr/local/bin/frps-healthcheck.sh
日志轮转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 # /etc/logrotate.d/frp
/var/log/frps.log {
daily
missingok
rotate 7
compress
delaycompress
notifempty
create 0640 nobody adm
postrotate
systemctl reload frps > /dev/null 2>&1 || true
endpostrotate
}
/var/log/frpc.log {
daily
missingok
rotate 3
compress
delaycompress
notifempty
create 0640 nobody adm
}
性能调优与故障排除
连接数与带宽优化
FRP的性能瓶颈通常在VPS的网络带宽和文件描述符限制。优化方案:
1
2
3
4
5 # /etc/security/limits.d/frp.conf
nobody soft nofile 1048576
nobody hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 # /etc/sysctl.d/99-frp.conf
# TCP缓冲区
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# 连接队列
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# TCP Keepalive
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
# TIME_WAIT回收
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# 应用
sysctl -p /etc/sysctl.d/99-frp.conf
常见问题排查
问题1:客户端连接服务端失败
1
2
3
4
5
6
7
8
9
10
11 # 检查服务端是否运行
systemctl status frps
# 检查端口是否监听
ss -tlnp | grep 7000
# 检查防火墙
ufw status | grep 7000
# 检查认证令牌是否一致
grep token /etc/frp/frps.toml /etc/frp/frpc.toml
问题2:HTTP代理502错误
1
2
3
4
5
6
7
8
9 # 检查frps日志
tail -f /var/log/frps.log
# 确认frpc已注册HTTP代理
curl -s -u admin:secure-dashboard-password \
http://127.0.0.1:7500/api/proxy/http | python3 -m json.tool
# 检查域名是否正确解析
dig app.yourdomain.com +short
问题3:连接频繁断开
1
2
3
4
5
6 # frpc.toml 增加心跳和重连配置
transport.heartbeatInterval = 10
transport.heartbeatTimeout = 90
# frps.toml 增加心跳超时
transport.heartbeatTimeout = 90
问题4:带宽占用过高
在frpc.toml中为每个代理设置带宽限制:
1
2
3
4
5
6
7 [[proxies]]
name = "web-app"
type = "http"
localIP = "127.0.0.1"
localPort = 3000
customDomains = ["app.yourdomain.com"]
transport.bandwidthLimit = "10MB"
多租户与进阶配置
当你的VPS需要为多人或多团队提供穿透服务时,需要考虑多租户隔离:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 # frps.toml - 多租户配置
bindPort = 7000
vhostHTTPPort = 8080
vhostHTTPSPort = 8443
# 每个用户的子域名
subDomainHost = "proxy.yourdomain.com"
# 用户A只能使用web-a前缀的代理
# frpc_a.toml
[[proxies]]
name = "web-a"
type = "http"
localPort = 8080
subdomain = "usera"
# 访问地址: usera.proxy.yourdomain.com
# 用户B
# frpc_b.toml
[[proxies]]
name = "web-b"
type = "http"
localPort = 3000
subdomain = "userb"
# 访问地址: userb.proxy.yourdomain.com
使用范围端口映射
当需要映射一整段端口时(如游戏服务器),可以使用范围端口:
1
2
3
4
5
6 [[proxies]]
name = "game-ports"
type = "tcp"
localIP = "127.0.0.1"
localPort = "25565-25575"
remotePort = "25565-25575"
UDP服务穿透
DNS、游戏等UDP服务的穿透配置:
1
2
3
4
5
6
7
8
9
10
11
12
13 [[proxies]]
name = "dns"
type = "udp"
localIP = "127.0.0.1"
localPort = 53
remotePort = 5353
[[proxies]]
name = "game-udp"
type = "udp"
localIP = "127.0.0.1"
localPort = "27015-27020"
remotePort = "27015-27020"

总结与最佳实践
通过本文的完整部署,你已经拥有了一套生产级的内网穿透系统。以下是最关键的实践要点:
- 安全优先:始终使用TLS加密传输,敏感服务使用STCP而非TCP,令牌定期轮换
- 分层防御:UFW防火墙 + Nginx限流 + FRP认证 + TLS加密,四层防护缺一不可
- 监控先行:接入Prometheus + Grafana,设置关键指标告警,问题发现要快于用户反馈
- 自动化运维:健康检查脚本、日志轮转、证书自动续签,减少手动操作
- 带宽控制:为每个代理设置合理的带宽限制,避免单用户占用过多资源
- 备份配置:frps.toml和所有frpc.toml都应纳入版本控制,灾难恢复时能快速重建
内网穿透是一把双刃剑——它打通了内外网的边界,也意味着安全责任更加重大。每次新增代理规则时,都要问自己:这个服务真的需要对外暴露吗?暴露的范围是否可以更小?只有在安全与便利之间找到平衡,才能让内网穿透真正为你所用。
汤不热吧