内网穿透方案对比:frp、ngrok 与反向 SSH 隧道

家里有台机器想从外面访问、本地开发要收 webhook、临时给同事演示——这些都需要「内网穿透」。方案很多,选对很重要。

一、先想清楚需求

场景推荐方案
临时演示本地服务(几分钟)SSH 隧道 / ngrok
长期暴露家中服务frp(自建)
收第三方 webhook 调试ngrok / cloudflared
公司内网机器远程维护frp + 严格鉴权
不想折腾、要稳定域名Cloudflare Tunnel

二、方案一:SSH 反向隧道(零安装)

只要有一台公网 VPS 和 SSH,不需要装任何东西。

bash
# 在内网机器上执行:把公网机的 8080 转到本机 3000
ssh -N -R 8080:127.0.0.1:3000 user@公网VPS -p 2222

# 之后访问 公网VPS:8080 就等于访问内网机器的 3000

公网 VPS 上要允许:

nginx
# /etc/ssh/sshd_config
GatewayPorts clientspecified    # 允许绑定非 localhost
bash
sudo sshd -t && sudo systemctl reload ssh

更稳的写法(加上重连):

bash
# 用 autossh 自动重连
sudo apt install -y autossh
autossh -M 0 -N -R 8080:127.0.0.1:3000 user@公网VPS -p 2222 \
  -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3"

优点:零依赖、几分钟搞定。缺点:断线要重连、只有一个端口、不方便管理。

三、方案二:frp(自建,最灵活)

frp 分服务端(frps,跑在公网机)和客户端(frpc,跑在内网机)。

服务端 /etc/frp/frps.toml

yaml
bindPort = 7000

# 鉴权,一定要设
auth.method = "token"
auth.token = "换成一串随机长字符串"

# 面板(可选,记得改密码)
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "强密码"
nginx
# systemd 托管 frps
[Unit]
Description=frp server
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=always

[Install]
WantedBy=multi-user.target

客户端 frpc.toml

yaml
serverAddr = "公网VPS的IP"
serverPort = 7000
auth.method = "token"
auth.token = "与服务端一致"

[[proxies]]
name = "home-web"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3000
remotePort = 8080

# 也可以转发 HTTP,配合域名
[[proxies]]
name = "home-http"
type = "http"
localPort = 8080
customDomains = ["home.bzii.cn"]

配合 Nginx 反代 + HTTPS:

nginx
server {
    listen 443 ssl http2;
    server_name home.bzii.cn;
    ssl_certificate     /etc/letsencrypt/live/home.bzii.cn/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/home.bzii.cn/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

四、方案三:Cloudflare Tunnel(免公网 IP)

如果你连公网 VPS 都没有,或者不想开端口,cloudflared 是最省事的:

bash
# 安装 cloudflared,登录后创建隧道
cloudflared tunnel login
cloudflared tunnel create home
cloudflared tunnel route dns home home.bzii.cn

# 配置 ~/.cloudflared/config.yml
yaml
tunnel: <隧道ID>
credentials-file: /root/.cloudflared/<隧道ID>.json

ingress:
  - hostname: home.bzii.cn
    service: http://localhost:3000
  - service: http_status:404
bash
cloudflared tunnel run home
# 或用 systemd 托管
sudo cloudflared service install

优点:免费、自带 HTTPS、不需要公网 IP、不需要开防火墙端口。出站连接即可。

五、安全是重点

把内网服务暴露到公网,等于给家里开了一扇门。必须做:

  1. 强鉴权:token 用 openssl rand -hex 32 生成,别用简单密码;
  2. 只转发必要端口,数据库、Redis、SSH 一律不转发;
  3. 服务端只监听必要地址,frps 的 7000 端口只对客户端 IP 放行:
bash
sudo ufw allow from <客户端出口IP> to any port 7000
  1. 暴露的服务本身要有认证,别让一个没密码的管理界面直接上公网;
  2. 加一层 HTTPS,明文传输密码等于送人;
  3. 日志留着,定期看有没有陌生连接。
bash
# 生成强 token
openssl rand -hex 32

六、对比表

方案需要公网机成本稳定性配置难度
SSH 隧道0一般(断线重连麻烦)
frpVPS 费用
ngrok免费额度有限
Cloudflare Tunnel免费
Tailscale / ZeroTier免费额度很好

补充一句:如果只是你自己要访问家里的机器,用 Tailscale 这类组网工具比穿透更合适——它不把服务暴露给公网,只是把你的设备组成一个虚拟局域网,安全性高一个量级。

七、我的选择

  • 自己远程访问家里 NAS / 开发机 → Tailscale
  • 要给别人访问的临时演示 → SSH 隧道 或 ngrok;
  • 长期对外的小服务 → frp,前面套 Nginx + HTTPS。

穿透的本质是「让没有公网 IP 的服务被访问到」。想清楚是「给自己用」还是「给别人用」,方案自然就出来了。