把服务部署在家里的机器或者公司内网服务器上之后,最头疼的问题往往不是服务本身,而是外面的人根本访问不到它。运营商不给公网 IP,路由器后面又是层层 NAT,这时候反向隧道就派上用场了。反向隧道的思路很简单:内网机器主动去连接一台有公网 IP 的服务器,把这条连接“占住”,公网服务器再把外部请求通过这条连接转发回内网。而用 Docker 来搭建整个链路,可以做到环境隔离、配置即代码、随时销毁重建,比直接在系统里装软件省心得多。

反向隧道的核心原理
要理解反向隧道,得先明白为什么正向连接行不通。内网机器访问公网没有任何障碍,因为 NAT 会自动维护一张映射表;但反过来,公网机器主动连内网时,NAT 上没有对应的映射记录,数据包根本不知道该转发给谁。所以解决方向不是想办法穿透 NAT,而是让内网机器主动发起连接,把这条已经建立的 TCP 连接当成一条“管道”,公网侧的流量顺着管道流回去。
SSH 的 -R 参数就是最经典的反向隧道实现。内网机器执行 ssh -R 8080:localhost:80 user@公网服务器 后,公网服务器的 8080 端口收到的所有请求,都会通过这条 SSH 连接转发给内网机器的 80 端口。整个过程不需要在公网服务器上做任何额外配置,只要它能跑 SSH 服务就行。这个特性让 SSH 反向隧道成为最轻量的方案,缺点是连接稳定性依赖 SSH 客户端的重连能力,断线后需要机制自动恢复。
如果需要转发多个端口、区分多台内网机器,或者想要图形化面板管理,frp 这类专用工具就更合适了。frp 分为 frps(服务端,跑在公网机器)和 frpc(客户端,跑在内网机器)两个角色,配置文件声明式地定义端口映射关系,功能比 SSH 隧道丰富得多,支持 TCP、UDP、HTTP、HTTPS 等多种协议。两种方案各有适用场景,下面分别演示 Docker 部署方式。
方案一:用 Docker 跑 SSH 反向隧道
传统做法是在内网机器上装 autossh 做持久化反向连接,其实用 Docker 完全可以替代,而且更干净。核心思路是构建一个只包含 ssh 客户端的镜像,把密钥和启动脚本挂载进去,配合 restart 策略实现断线自动拉起。下面是一个可用的 Dockerfile:
# 基于轻量 Alpine 镜像 FROM alpine:3.19 # 安装 ssh 客户端和 autossh RUN apk add --no-cache openssh-client autossh # 自动重连脚本 COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]
entrypoint.sh 的内容很简短,autossh 会监控 SSH 连接状态,一旦断开就自动重试:
#!/bin/sh autossh -M 0 \ -o "ServerAliveInterval=30" \ -o "ServerAliveCountMax=3" \ -o "StrictHostKeyChecking=no" \ -N -R 8080:localhost:80 \ -p 22 user@your-public-server.com
这里有几个细节值得注意。-M 0 表示关闭 autossh 自带的监控端口,改用 SSH 协议自身的 KeepAlive 机制,这是目前更推荐的做法;ServerAliveInterval=30 让客户端每 30 秒发一次心跳,连续 3 次没响应就判定断线并重连。运行容器时记得把私钥通过 volume 挂载进去,并加上 --restart=always,这样宿主机重启后隧道也能自动恢复。
还有一个非常容易踩的坑:默认情况下,公网服务器上的 sshd 只允许反向端口绑定在 127.0.0.1 上,也就是说只有公网服务器本机能访问这个端口,外部依然访问不到。解决办法是编辑公网服务器的 /etc/ssh/sshd_config,把 GatewayPorts 改成 clientspecified 或者 yes,然后在 -R 参数里显式指定绑定地址,例如 -R 0.0.0.0:8080:localhost:80,改完记得重启 sshd 服务。
方案二:用 Docker 部署 frp 实现内网穿透
当需求复杂起来,比如要同时暴露 Web 服务、远程桌面、数据库等多个端口,frp 的声明式配置就体现出优势了。服务端用一行 Docker 命令即可启动:
docker run -d --name frps --restart=always \ -p 7000:7000 -p 7500:7500 -p 8080:8080 \ -v /opt/frp/frps.toml:/etc/frp/frps.toml \ snowdreamtech/frps
其中 7000 是 frps 与 frpc 之间的通信端口,7500 是监控面板端口,8080 是预留给内网服务对外暴露的端口。frps.toml 配置也很简单:
bindPort = 7000 # 监控面板 webServer.port = 7500 webServer.user = "admin" webServer.password = "你的密码" # 开启 token 认证,防止被陌生人白嫖 auth.token = "一串足够长的随机字符串"
内网机器这边的 frpc 容器对应配置如下,假设要把内网一台机器上的 Web 服务暴露出去:
serverAddr = "your-public-server.com" serverPort = 7000 auth.token = "与服务端一致的token" [[proxies]] name = "my-web" type = "tcp" localIP = "192.168.31.100" localPort = 80 remotePort = 8080
启动 frpc 容器时有个网络模式的选择问题。如果内网服务就跑在同一台机器上,用默认的 bridge 模式、把 localIP 写成 host.docker.internal 或宿主机内网 IP 即可;但如果 frpc 需要访问局域网里其他设备的服务,建议直接用 --network=host 模式,让容器共享宿主机网络栈,省去容器内 DNS 解析和路由的麻烦。frpc 自带断线重连机制,配合 --restart=always 基本可以做到无人值守长期运行。
安全加固与常见问题排查
隧道打通等于给内网开了一扇门,安全措施必须跟上。首先是认证层面,SSH 方案务必禁用密码登录、只允许密钥认证;frp 方案则一定要设置 auth.token,网上大量被扫穿的 frp 服务就是因为用了默认配置。其次是暴露面控制,能在 frps 前面套一层 Nginx 反向代理并加 HTTPS 就尽量加,监控面板端口不要随意对全网开放,可以用防火墙限制来源 IP。
排查问题时可以按链路顺序逐段检查:先确认 frpc 日志里显示 login 成功,说明内外网之间的控制通道是通的;再从公网服务器本机执行 curl 127.0.0.1:8080 验证端口转发是否生效;最后从外部设备访问公网 IP 测试。如果本机通而外部不通,大概率是云服务器安全组没放行对应端口,这在阿里云、腾讯云上是高频踩坑点,很多人查了半天配置,最后发现是安全组规则漏了。
还有一个细节是容器时区。frp 的日志时间戳默认是 UTC,排查问题时容易产生“日志时间对不上”的困惑,可以在 docker run 时加上 -e TZ=Asia/Shanghai 解决。整体来说,Docker 化的反向隧道方案把整条链路拆成了几个可独立管理的容器单元,出问题时可以单独重启某一环,配置文件也能纳入 Git 管理,比传统的裸机部署方式可维护性高出不少。无论是临时调试还是长期运行,这套方案都值得作为标准实践沉淀下来。
Docker反向隧道内网穿透frp修改时间:2026-09-04 13:18:38