欢迎光临

VPS自建内网穿透与反向代理完全指南:FRP + Nginx + TLS生产级部署实战

为什么需要内网穿透

在日常开发和运维中,我们经常遇到这样的场景:本地开发了一个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建立连接,注册需要暴露的服务信息

工作流程如下:

  1. frpc启动后,向frps注册一个或多个代理规则(proxy)
  2. frps为每个代理规则分配一个端口(或使用配置的域名)
  3. 外部用户访问VPS的对应端口/域名
  4. frps将请求通过已建立的控制连接转发给frpc
  5. 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包含敏感信息,务必做好防护:

  • 绑定到
    1
    127.0.0.1

    ,不对外暴露

  • 如需远程查看,通过SSH隧道:
    1
    ssh -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"

总结

总结与最佳实践

通过本文的完整部署,你已经拥有了一套生产级的内网穿透系统。以下是最关键的实践要点:

  1. 安全优先:始终使用TLS加密传输,敏感服务使用STCP而非TCP,令牌定期轮换
  2. 分层防御:UFW防火墙 + Nginx限流 + FRP认证 + TLS加密,四层防护缺一不可
  3. 监控先行:接入Prometheus + Grafana,设置关键指标告警,问题发现要快于用户反馈
  4. 自动化运维:健康检查脚本、日志轮转、证书自动续签,减少手动操作
  5. 带宽控制:为每个代理设置合理的带宽限制,避免单用户占用过多资源
  6. 备份配置:frps.toml和所有frpc.toml都应纳入版本控制,灾难恢复时能快速重建

内网穿透是一把双刃剑——它打通了内外网的边界,也意味着安全责任更加重大。每次新增代理规则时,都要问自己:这个服务真的需要对外暴露吗?暴露的范围是否可以更小?只有在安全与便利之间找到平衡,才能让内网穿透真正为你所用。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » VPS自建内网穿透与反向代理完全指南:FRP + Nginx + TLS生产级部署实战
分享到: 更多 (0)