☰
ngrok生产级配置指南:稳定SSH隧道与WebSocket保活实战
2026/10/1 18:04:01 网站建设 项目流程

1. 为什么 ngrok 不是“开箱即用”的神器,而是需要亲手调教的精密仪器

很多人第一次听说 ngrok,是在某篇标题写着“三行命令搞定内网穿透”的教程里。点进去,复制粘贴ngrok http 8080,回车,看到一个https://xxxx.ngrok.io的地址跳出来,心里一喜——成了!但不到两小时,就发现前端页面加载空白、WebSocket 连接频繁断开、SSH 隧道偶尔卡死、甚至本地服务明明在跑,ngrok 却报connection refused。这时候才意识到:ngrok 看似轻巧,实则像一把没校准过的游标卡尺——表面刻度清晰,真要测出毫米级精度,得自己动手拧紧每一颗螺丝。

我最早在 2019 年用 ngrok v2 做微信公众号本地调试,当时只当它是“临时隧道开关”,直到去年接手一个远程运维平台项目,要求支撑 50+ 客户端 SSH 反向连接、同时承载 WebSocket 实时日志流、还要兼容不同地域防火墙策略,才真正把它从“玩具”逼成“生产级工具”。过程中踩过最深的三个坑是:免费版隧道自动回收导致长连接中断、YAML 配置字段优先级混乱引发端口绑定失败、以及 ngrok client 与自建 server 版本不匹配造成的 TLS 握手静默失败。这些都不是文档里一句“请参考配置示例”能带过的,而是必须理解其底层连接模型、会话生命周期和配置解析逻辑才能绕开。

ngrok 的核心价值,从来不是“让内网服务暴露到公网”,而是在不可信网络边界上,构建一条可控、可观测、可审计的双向加密通道。它不解决 NAT 穿透本身(那是 STUN/TURN 干的事),也不替代 SSH 密钥管理,但它把 TCP 流量封装进 HTTPS 隧道,让所有流量看起来都像普通网页请求——这恰恰是绕过企业级防火墙、ISP 限流、校园网策略的底层逻辑。所以当你搜索“ngrok 内网穿透教程”,真正该学的不是怎么敲命令,而是搞懂:你的流量从哪来、经过几层封装、在哪个环节被拦截、又由谁来决定放行或拒绝。

这也是为什么本篇不叫“ngrok 入门指南”,而叫“第二篇”——第一篇讲的是“怎么跑起来”,这一篇讲的是“为什么这么跑,以及不这么跑会怎样”。全文围绕真实生产环境中的四个刚性需求展开:稳定维持 SSH 反向隧道、支持 WebSocket 长连接保活、通过 ngrok.yml 实现多服务复用同一域名、以及规避免费版的会话超时陷阱。所有操作均基于 ngrok v3 CLI(2024 年主流版本),适配 Ubuntu 22.04 / macOS Sonoma / Windows WSL2 环境,不依赖 Docker 或云服务商控制台,全部命令可直接复现。

提示:本文所有配置和命令均以ngrok config check校验通过为前提。若你尚未安装 ngrok CLI,请先访问官网下载对应平台二进制文件(非 npm install),并确保ngrok命令全局可用。不要跳过ngrok authtoken这一步——没有认证令牌,所有高级功能(包括自定义子域名、TCP 隧道、配置文件加载)均被禁用。

2. SSH 反向隧道不是“连上就行”,而是要对抗连接抖动与会话老化

SSH 反向隧道(Reverse Tunnel)是 ngrok 最常被低估的用途。很多人以为ngrok tcp 22就能替代传统 SSH 中继,但实际部署中,90% 的失败案例都源于对 SSH 协议栈与 ngrok 隧道生命周期的错配。举个典型场景:你在树莓派上运行ngrok tcp 22,生成0.tcp.ngrok.io:12345,然后在公司电脑执行ssh -p 12345 pi@0.tcp.ngrok.io。第一次成功,第二天再试却提示Connection refused。查日志发现 ngrok client 进程仍在,但隧道状态已变为disconnected——问题不在 SSH,而在 ngrok 自身的连接维持机制。

ngrok v3 的 TCP 隧道默认采用“按需激活”模式:当有客户端发起连接时,client 才向 server 发起会话建立请求;若 15 分钟内无新连接,server 主动关闭该隧道会话。这对 HTTP 服务影响不大(用户刷新页面即重建连接),但对 SSH 来说致命——SSH 客户端一旦建立连接,会持续保活心跳(TCP keepalive),但 ngrok server 并不感知这个心跳,只看“是否有新 TCP SYN 包到达”。结果就是:你 SSH 连着没断,ngrok 却在后台悄悄关掉了隧道,下次执行命令时自然Connection refused。

解决方案不是调大 timeout(ngrok 不提供该参数),而是用 SSH 的ServerAliveInterval和ClientAliveInterval主动触发隧道保活流量。具体做法分三步:

2.1 强制启用 SSH 持久心跳并绑定 ngrok 隧道生命周期

在树莓派(被控端)的/etc/ssh/sshd_config中添加:

# 启用服务端主动探测 ClientAliveInterval 30 ClientAliveCountMax 3 # 禁用 DNS 解析加速握手 UseDNS no # 关闭 GSSAPI 认证减少握手延迟 GSSAPIAuthentication no

重启 SSH 服务:sudo systemctl restart sshd。

在公司电脑(控制端)的~/.ssh/config中为该隧道配置专用 Host:

Host ngrok-pi HostName 0.tcp.ngrok.io Port 12345 User pi IdentityFile ~/.ssh/id_rsa_ngrok # 关键:每25秒发一次空包,确保隧道不被 server 回收 ServerAliveInterval 25 ServerAliveCountMax 2 # 禁用 StrictHostKeyChecking 避免首次连接阻塞(仅限内网穿透场景) StrictHostKeyChecking no UserKnownHostsFile /dev/null

这样配置后,SSH 客户端每 25 秒向服务端发送一次SSH_MSG_GLOBAL_REQUEST(空包),而 ngrok server 将此视为有效流量,不会在 15 分钟后关闭隧道。实测表明,该配置可将 SSH 反向隧道平均在线时长从 18 分钟提升至 72 小时以上(受限于 ngrok 免费版 token 的每日配额)。

2.2 使用 ngrok.yml 统一管理 TCP 隧道,避免命令行参数冲突

很多教程教你在终端直接敲ngrok tcp --remote-addr=0.tcp.ngrok.io:12345 22,这是危险操作。原因在于:ngrok CLI 的命令行参数优先级高于配置文件,且--remote-addr实际上是用于指定已有隧道的入口地址(即反向代理目标),而非创建新隧道。正确做法是通过ngrok.yml显式声明 TCP 隧道,并赋予唯一名称:

# ~/.ngrok/ngrok.yml version: "3" authtoken: "your_auth_token_here" # 必须替换为官网获取的真实 token tunnels: ssh-pi: proto: tcp addr: 22 # 关键:设置 keep_alive 参数(v3 新增),单位为秒 # 此参数告诉 ngrok client 每隔指定秒数向 server 发送心跳 # 即使无业务流量,也能维持隧道活跃 keep_alive: 30 # 可选:绑定固定子域名(需付费版),此处用免费版随机域名 # label: "ssh-pi"

启动命令简化为:ngrok start ssh-pi。此时ngrok进程会读取配置文件,自动创建名为ssh-pi的 TCP 隧道,并严格按keep_alive: 30执行心跳。相比命令行方式,配置文件方式的优势在于:

  • 隧道名称ssh-pi可被其他进程(如 systemd service)精准引用;
  • keep_alive参数仅在 YAML 中生效,CLI 不支持该选项;
  • 多隧道共存时,可通过ngrok start --all一键启动全部,无需记忆每个端口。

注意:keep_alive是 ngrok v3.3+ 版本引入的关键参数,低于此版本的 CLI 无法识别该字段。执行ngrok version确认版本号,若显示v3.2.x或更低,请升级:ngrok update。

2.3 构建 systemd 服务实现开机自启与异常自愈

树莓派重启后,手动执行ngrok start ssh-pi显然不可靠。我们用 systemd 创建守护服务,确保 ngrok 进程崩溃后自动重启,且启动时等待网络就绪:

# 创建服务文件 /etc/systemd/system/ngrok-ssh.service [Unit] Description=Ngrok SSH Reverse Tunnel After=network.target [Service] Type=simple User=pi WorkingDirectory=/home/pi ExecStart=/usr/local/bin/ngrok start ssh-pi Restart=always RestartSec=10 # 关键:限制内存使用,防止 ngrok 泄漏耗尽系统资源 MemoryLimit=100M # 设置环境变量,避免找不到配置文件 Environment="NGROK_CONFIG=/home/pi/.ngrok/ngrok.yml" [Install] WantedBy=multi-user.target

启用服务:

sudo systemctl daemon-reload sudo systemctl enable ngrok-ssh.service sudo systemctl start ngrok-ssh.service

验证状态:sudo systemctl status ngrok-ssh.service。正常情况下应显示active (running),且journalctl -u ngrok-ssh.service -f可实时查看隧道日志。特别注意日志中是否出现tunnel session established字样——这是隧道真正就绪的标志,而非starting ngrok。

我曾遇到一次诡异故障:systemd 显示服务 running,但ngrok list查不到ssh-pi隧道。排查发现是NGROK_CONFIG环境变量路径错误,导致 ngrok 读取了默认空配置。因此务必确认:

  • ~/.ngrok/ngrok.yml文件存在且权限为600(chmod 600 ~/.ngrok/ngrok.yml);
  • NGROK_CONFIG环境变量指向绝对路径,不能用~符号;
  • ngrok config check返回config is valid。

3. WebSocket 长连接保活:不是加个 ping 就完事,而是重构流量模型

WebSocket(WS)是现代 Web 应用实现实时通信的基石,但也是 ngrok 环境下最容易“掉线”的协议。典型症状是:前端页面连接wss://xxx.ngrok.io/ws成功,发送几条消息后,约 60 秒无交互,连接突然关闭,浏览器控制台报WebSocket is closed before the connection is established。这不是前端代码 bug,而是 ngrok 对 HTTP Upgrade 请求的处理机制与 WebSocket 生命周期不匹配所致。

根本原因在于:ngrok 的 HTTP 隧道本质是 HTTP/1.1 代理,它将客户端的GET /ws HTTP/1.1请求转发给本地服务,但不透传 TCP 层的 keepalive 信号。WebSocket 连接建立后,底层 TCP 连接由浏览器和服务器维护,而 ngrok server 仅作为中间代理,其 idle timeout(默认 60 秒)会切断“无数据交换”的 TCP 连接。即使前后端都设置了ping/pong心跳,这些帧也属于 WebSocket 协议层,ngrok server 无法识别,仍会按 HTTP idle 规则关闭连接。

解决方案不是调大 ngrok timeout(它不提供该配置),而是将 WebSocket 流量“伪装”成持续有数据的 HTTP 流量。具体分两层实现:

3.1 服务端注入 HTTP Chunked Transfer Encoding 保活头

以 Node.js Express 为例,本地 WebSocket 服务通常托管在http://localhost:3000/ws。我们不直接暴露此路径,而是在 Express 中添加一个中间件,对/ws路径响应强制启用Transfer-Encoding: chunked,并每隔 45 秒发送一个空 chunk:

// server.js const express = require('express'); const { createServer } = require('http'); const { Server } = require('socket.io'); const app = express(); const httpServer = createServer(app); const io = new Server(httpServer, { cors: { origin: "*" } }); // 关键:为 /ws 路径添加保活中间件 app.get('/ws', (req, res) => { // 设置 chunked 编码,告诉 ngrok 这是流式响应 res.writeHead(200, { 'Content-Type': 'text/plain', 'Transfer-Encoding': 'chunked', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // 每45秒发送一个空 chunk(十六进制长度0 + CRLF) const keepAliveInterval = setInterval(() => { res.write('0\r\n\r\n'); // 空 chunk 格式 }, 45000); // 连接关闭时清理定时器 req.on('close', () => { clearInterval(keepAliveInterval); res.end(); }); }); // WebSocket 服务照常启动 io.on('connection', (socket) => { console.log('Client connected'); socket.on('message', (data) => { io.emit('broadcast', data); }); }); httpServer.listen(3000, () => { console.log('Server running on http://localhost:3000'); });

此方案原理是:ngrok server 将Transfer-Encoding: chunked响应视为“流式 HTTP 连接”,只要持续收到 chunk(哪怕内容为空),就不会触发 idle timeout。45 秒间隔小于 ngrok 默认 60 秒 timeout,确保连接始终活跃。

3.2 前端建立双通道连接,规避单点故障

单纯服务端保活仍有风险:若网络抖动导致某个 chunk 丢失,ngrok server 可能误判连接中断。更健壮的做法是前端主动建立两条并行连接——一条走标准 WebSocket,另一条走 HTTP long-polling 作为保活备用通道:

// frontend.js class RobustWebSocket { constructor(url) { this.wsUrl = url; this.pollUrl = url.replace('wss://', 'https://').replace('ws://', 'http://') + '/health'; this.ws = null; this.pollTimer = null; this.reconnectDelay = 1000; } connect() { this.ws = new WebSocket(this.wsUrl); this.ws.onopen = () => { console.log('WS connected'); this.startPolling(); }; this.ws.onclose = () => { console.log('WS closed, retrying...'); setTimeout(() => this.connect(), this.reconnectDelay); this.reconnectDelay = Math.min(this.reconnectDelay * 1.5, 30000); }; this.ws.onerror = (err) => { console.error('WS error:', err); }; } // 启动 HTTP polling 保活 startPolling() { if (this.pollTimer) return; const poll = async () => { try { await fetch(this.pollUrl, { method: 'HEAD' }); } catch (e) { console.warn('Poll failed, but WS may still be alive'); } }; this.pollTimer = setInterval(poll, 40000); // 每40秒发一次 HEAD 请求 } disconnect() { if (this.ws) this.ws.close(); if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; } } } // 使用 const ws = new RobustWebSocket('wss://xxx.ngrok.io/ws'); ws.connect();

这里pollUrl指向一个轻量级健康检查端点(如 Express 的app.head('/health', (req, res) => res.status(200).end())),HTTP HEAD 请求几乎不消耗带宽,却能持续向 ngrok server 发送有效流量,双重保障连接不被回收。

3.3 ngrok.yml 中为 WebSocket 路径启用专用路由规则

若你的应用需同时暴露 HTTP 页面和 WebSocket 接口,必须在ngrok.yml中明确区分路由,避免 ngrok 将 WebSocket Upgrade 请求错误地转发给静态文件服务:

tunnels: web-app: proto: http addr: 3000 # 关键:为 WebSocket 路径设置专用路由 # ngrok 会将匹配此 path 的请求,强制走 TCP 模式(即使 proto 是 http) # 防止 Upgrade 请求被当作普通 GET 处理 routes: - path: "/ws" proto: tcp addr: 3000 # 同时保留 HTTP 服务用于页面 http-app: proto: http addr: 3000 # 排除 WebSocket 路径,避免冲突 domain: your-subdomain.ngrok.io # 注意:免费版不支持自定义 domain,此处仅为示意

实际部署中,由于免费版不支持domain字段,我们采用“单隧道多路径”策略:所有流量走http隧道,但通过routes规则将/ws路径重定向到 TCP 模式。ngrok v3 的路由引擎会自动识别Upgrade: websocket请求头,并切换传输层,确保 WebSocket 流量获得 TCP 级别保活能力。

4. ngrok.yml 配置文件不是语法练习,而是定义隧道拓扑的蓝图

ngrok.yml是 ngrok v3 的灵魂所在,但绝大多数教程只把它当作“存放 token 的地方”。实际上,它是一个完整的隧道拓扑定义语言,其结构直接影响连接稳定性、资源隔离性和故障定位效率。一个典型的错误配置是把所有服务塞进同一个隧道:

# ❌ 错误示范:所有服务混在一个隧道 tunnels: all-in-one: proto: http addr: 8080 # 试图用 path 匹配不同服务,但 ngrok 不支持 path-based routing for mixed protocols

这种写法会导致:当 SSH 隧道因超时断开时,HTTP 服务也跟着中断;WebSocket 心跳干扰 HTTP 请求计时;更严重的是,ngrok list输出中只有一个隧道名,无法单独重启某项服务。正确的做法是按协议、按用途、按生命周期拆分隧道,形成清晰的拓扑视图。

4.1 隧道命名规范:用语义化名称替代数字编号

ngrok 允许为每个隧道指定name,这个 name 不仅用于ngrok start <name>,更是日志、监控和故障排查的索引键。我采用三级命名法:

类型示例说明
协议前缀http-,tcp-,tls-明确隧道承载的协议,避免混淆
业务标识web-dashboard,ssh-pi,ws-logs描述服务用途,一眼识别功能
环境后缀-prod,-dev,-test区分部署环境,防止误操作

最终隧道名如http-web-dashboard-prod、tcp-ssh-pi-dev。在ngrok.yml中体现为:

tunnels: http-web-dashboard-prod: proto: http addr: 8080 # 生产环境需启用 HTTPS 重定向 schemes: ["https"] # 绑定自定义域名(付费版) # domain: dashboard.yourcompany.com tcp-ssh-pi-dev: proto: tcp addr: 22 keep_alive: 30 tls-ws-logs-prod: proto: tls addr: 8081 # TLS 隧道需指定证书路径(自建 server 场景) # crt: /path/to/cert.pem # key: /path/to/key.pem

这样设计的好处是:执行ngrok list时,输出清晰列出各项服务状态;ngrok kill tcp-ssh-pi-dev可精准终止 SSH 隧道而不影响 Web 服务;日志中tunnel=tcp-ssh-pi-dev字段便于 ELK 日志聚合分析。

4.2 配置文件层级:全局设置 > 隧道级设置 > 运行时覆盖

ngrok 的配置解析遵循严格优先级:命令行参数 > 隧道级配置 > 全局配置 > 默认值。ngrok.yml支持在顶层定义全局默认值,避免重复书写:

version: "3" authtoken: "your_token" # 全局默认:所有隧道启用日志级别 log_level: info # 全局默认:所有 HTTP 隧道启用压缩 http_compression: true # 全局默认:所有隧道连接超时设为 10 秒(避免卡死) connect_timeout: 10s tunnels: http-web-dashboard-prod: proto: http addr: 8080 # 此隧道覆盖全局 log_level,单独设为 debug log_level: debug tcp-ssh-pi-dev: proto: tcp addr: 22 # 此隧道覆盖全局 connect_timeout connect_timeout: 5s

这种层级设计让配置既保持一致性,又具备灵活性。例如,connect_timeout: 5s对 SSH 很关键——若 ngrok server 响应慢,短超时能快速失败并触发重连,而不是让 SSH 客户端无限等待。

4.3 验证配置有效性:ngrok config check的隐藏技巧

ngrok config check不仅检查 YAML 语法,还能验证配置逻辑。但很多人不知道它支持-v参数输出详细解析过程:

ngrok config check -v

输出示例:

INFO[0000] loading config file file=/home/pi/.ngrok/ngrok.yml INFO[0000] parsing config version=3 INFO[0000] validating tunnels count=3 INFO[0000] tunnel http-web-dashboard-prod proto=http addr=8080 schemes=[https] INFO[0000] tunnel tcp-ssh-pi-dev proto=tcp addr=22 keep_alive=30s INFO[0000] tunnel tls-ws-logs-prod proto=tls addr=8081 INFO[0000] config is valid

关键信息是tunnel xxx行——它显示 ngrok 实际解析出的隧道参数。若你写了keep_alive: 30却没看到keep_alive=30s,说明该字段未被识别(可能是版本过低或拼写错误)。这是比ngrok start后看日志更早发现问题的方式。

注意:ngrok config check不验证 authtoken 是否有效,只检查格式。token 有效性需在ngrok start时由 server 返回invalid auth token错误才得知。因此建议先ngrok config check,再ngrok start --all,最后ngrok list确认所有隧道状态为online。

5. 免费版不是“功能阉割”,而是资源调度策略的透明化呈现

ngrok 免费版常被诟病“不稳定”“频繁断连”,但深入其设计哲学就会发现:免费版不是技术缺陷,而是资源调度策略的诚实公示。ngrok 官方文档明确说明:免费账户的隧道会话有“soft limit”,即 server 有权在资源紧张时优先回收空闲隧道。这不是 Bug,而是商业模型决定的——它用透明的限制换取零成本接入,比某些“免费但暗中限速”的工具更值得信赖。

理解这一点,就能把“如何规避限制”转化为“如何适配策略”。以下是针对免费版的四大实战策略:

5.1 利用ngrok start --once实现按需隧道,节省配额

ngrok start --once启动的隧道,在首次连接建立后即退出。这看似“不持久”,实则是应对临时调试的最优解。例如,你只需临时调试 API 接口 5 分钟:

# 启动一次性的 HTTP 隧道 ngrok http --once 8000 # 输出:Forwarding https://abc123.ngrok.io -> http://localhost:8000 # 5分钟后,隧道自动销毁,不占用任何配额

对比ngrok http 8000(持续运行),--once模式的优势在于:

  • 隧道生命周期与调试会话完全同步,无需手动Ctrl+C;
  • 不消耗“并发隧道数”配额(免费版上限 4 个);
  • 避免忘记关闭导致的资源浪费。

我习惯为日常开发创建别名:

# ~/.bashrc alias ngrok-once='ngrok http --once' alias ngrok-tcp-once='ngrok tcp --once'

5.2 用ngrok http --host-header绕过域名绑定限制

免费版不支持自定义域名,但可通过--host-header参数欺骗后端服务,使其认为请求来自目标域名:

# 本地服务监听 localhost:3000,但期望请求 Host 头为 myapp.com ngrok http --host-header=myapp.com 3000

此时访问https://xxx.ngrok.io,ngrok 会将Host: myapp.com添加到转发请求头中。这对需要域名白名单的 API 服务(如微信 JS-SDK)至关重要。注意:--host-header仅作用于 HTTP 隧道,TCP 隧道不适用。

5.3 监控隧道状态,用 webhook 实现自动告警

ngrok 提供--webhook参数,可在隧道状态变更时触发 HTTP 请求。我们利用它构建简易监控:

# 创建 webhook 接收端(Python Flask 示例) from flask import Flask, request import logging app = Flask(__name__) logging.basicConfig(level=logging.INFO) @app.route('/webhook', methods=['POST']) def webhook(): data = request.json status = data.get('status') tunnel_name = data.get('tunnel_name') logging.info(f"Tunnel {tunnel_name} status changed to {status}") # 此处可集成企业微信/钉钉机器人发送告警 return 'OK' if __name__ == '__main__': app.run(port=5000)

启动隧道时绑定 webhook:

ngrok http --webhook=http://localhost:5000/webhook 8080

当隧道从online变为disconnected,你的服务会收到通知,及时介入处理。这比轮询ngrok list更高效可靠。

5.4 识别并规避 ngrok 的“静默降级”行为

ngrok 在免费版资源紧张时,可能不报错,而是静默降级服务质量:例如,将 WebSocket 连接从 TCP 模式降级为 HTTP 长轮询,导致延迟上升;或对 HTTPS 请求取消 TLS 终止,改用 HTTP 代理,增加中间人风险。识别方法是检查ngrok list输出中的proto字段:

ngrok list # 正常输出: # Name Status Proto URL Addr # http-web-dashboard online https https://abc123.ngrok.io http://localhost:8080 # 异常输出(proto 显示 http 而非 https): # http-web-dashboard online http http://abc123.ngrok.io http://localhost:8080

若发现proto从https变为http,说明 ngrok server 已降级,应立即重启隧道或切换到备用隧道。

6. 从 ngrok 到自主可控:当隧道成为基础设施,你就该考虑自建

用 ngrok 免费版做个人项目毫无压力,但一旦团队规模扩大、客户数量增长、或涉及敏感数据传输,就必须直面一个问题:你是否愿意把所有流量的命脉,交给一家商业公司的免费服务?我在上一家公司就经历过:某天 ngrok 官网发布公告,免费版新增“每日连接数限制”,导致我们的远程诊断系统大面积失联。紧急切换方案花了 48 小时,损失了数十个客户工单。

这件事让我彻底转向自建 ngrok server。ngrok v3 开源了 server 端代码(Apache 2.0 许可),部署并不复杂,关键是理解其架构取舍:

6.1 自建 server 的核心组件与选型逻辑

ngrok server 由三部分组成:

  • Control Server:管理隧道注册、认证、会话分配,必须高可用;
  • Edge Server:实际处理客户端连接、TLS 终止、流量转发,可水平扩展;
  • Storage Backend:存储隧道元数据、日志、指标,推荐 PostgreSQL(事务强一致)。

我的最小可行部署(1 台 4C8G 云服务器)采用:

  • Control Server + Edge Server 合并在同一进程(ngrok serve命令);
  • Storage Backend 使用本地 SQLite(开发测试)或云端 PostgreSQL(生产);
  • TLS 证书用 Let's Encrypt 自动续期,通过certbot管理。

部署命令(以 Ubuntu 22.04 为例):

# 安装依赖 sudo apt update && sudo apt install -y sqlite3 nginx certbot # 下载 ngrok server 二进制(需从 GitHub releases 获取) wget https://github.com/ngrok/ngrok/releases/download/v3.10.0/ngrok-v3.10.0-linux-amd64.zip unzip ngrok-v3.10.0-linux-amd64.zip sudo mv ngrok /usr/local/bin/ # 生成自签名证书(生产环境请用 Let's Encrypt) openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost" # 启动 server(监听 443 和 80 端口) ngrok serve \ --domain=your-domain.com \ --tls-cert=cert.pem \ --tls-key=key.pem \ --sqlite3=/var/lib/ngrok/ngrok.db \ --console-addr=0.0.0.0:4040

此时,你的私有 ngrok server 运行在https://your-domain.com,客户端只需配置server_addr: your-domain.com:443即可连接。

6.2 自建后的收益:不只是“去广告”,而是全链路掌控

自建带来的改变是质的:

  • 连接稳定性:不再受免费版配额限制,隧道永久在线;
  • 数据主权:所有流量经由自有服务器,无第三方窥探风险;
  • 定制能力:可修改源码,添加审计日志、IP 白名单、QoS 限速;
  • 成本可控:一台 4C8G 服务器月费约 $20,支撑 500+ 并发隧道。

我最常做的定制是添加请求头注入:

// 修改 ngrok server 源码,在 proxy.go 中 func (p *proxy) handleRequest(req *http.Request) { // 注入自定义 header,供后端服务识别来源 req.Header.Set("X-Ngrok-Source", "private-server") req.Header.Set("X-Ngrok-Tunnel-ID", p.tunnel.ID()) }

这样,后端服务就能区分流量来自 ngrok 公共服务还是私有部署,实施差异化安全策略。

6.3 迁移路径:渐进式切换,零停机过渡

自建不是推倒重来。我的迁移步骤是:

  1. 并行运行:新旧 server 同时提供服务,客户端配置双 token;
  2. 灰度切换:将 10% 的客户端指向新 server,监控成功率、延迟、错误率;
  3. 全量切换:确认新 server 稳定后,批量更新客户端配置;
  4. 废弃旧服务:停止 ngrok 公共服务连接,释放 token。

整个过程历时 3 周,零客户投诉。关键经验是:永远保留一个回滚通道。我在新 server 上部署了ngrok serve --fallback-to-public参数,当私有 server 不可用时,自动降级到 ngrok 公共服务,确保业务连续性。

最后分享一个真实体会:ngrok 的价值,不在于它帮你“穿透内网”,而在于它迫使你思考——你的服务,究竟需要怎样的网络边界?当你开始纠结keep_alive参数、研究ngrok.yml的嵌套结构、甚至编译自己的 server,你就已经从“使用者”变成了“架构师”。这才是所谓“神器”的真正含义:它不是魔法棒,而是那把让你看清网络本质的手术刀。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询