ngrok 本地服务安全暴露与流量策略手册
先判断 ngrok 解决的是哪一段问题
ngrok agent 在开发机上发起出站连接,为本地 HTTP 服务建立一个公网 Endpoint。公网请求先经过 Endpoint 的 Traffic Policy,再经 agent 到达本地 upstream。先让只含合成数据的 upstream 在回环地址工作,后面才能区分本地服务错误和入口错误。普通 Linux 用户在终端一执行:
NGROK_WEB_DIR="$(mktemp -d /tmp/ngrok-web.XXXXXX)"
printf 'NGROK_WEB_DIR=%s\n' "$NGROK_WEB_DIR"
printf 'tunnel-ok\n' >"$NGROK_WEB_DIR/index.html"
python3 -m http.server 8080 --bind 127.0.0.1 --directory "$NGROK_WEB_DIR"服务保持前台运行,另开终端验证:
curl -q --noproxy '*' --fail-with-body -i http://127.0.0.1:8080/预期 HTTP 200,响应体是 tunnel-ok。本地请求失败时,先检查 8080 是否被占用以及终端一是否仍运行;不能借助公网隧道修复本机端口。此时没有为开发机开放入站端口,但后面一旦建立 Endpoint,就会出现一条经 ngrok 到达本地服务的新路径。
因此,数据库、Redis、消息队列、Docker socket、管理后台、Actuator 和带真实用户数据的服务不应成为临时 upstream。“只在本机运行”从来不是鉴权策略;一旦 Endpoint 建立,这个假设就不再成立。
建立可重复的最小实验
在 Linux amd64 主机上先运行 uname -m,预期 x86_64;ARM64 或其他系统从 ngrok 官方下载页选择匹配的包,不能运行下列 amd64 命令。Linux amd64 官方 stable 包会随发行更新,下载后记录实际 ngrok version 和本地摘要;这里验证的 agent 为 v3.39.11,不能从可变下载地址推定以后仍是这个版本。普通用户在独占目录下载和安装,不覆盖已有的 ~/.local/bin/ngrok:
NGROK_DL_DIR="$(mktemp -d /tmp/ngrok-download.XXXXXX)"
NGROK_ARCHIVE="$NGROK_DL_DIR/ngrok-v3-stable-linux-amd64.tgz"
if test -e "$HOME/.local/bin/ngrok" || test -L "$HOME/.local/bin/ngrok"; then
printf '已有 ngrok,请先核对来源与版本,停止覆盖\n' >&2
elif curl -q --fail-with-body --location --connect-timeout 10 --max-time 180 \
--output "$NGROK_ARCHIVE" \
https://bin.ngrok.com/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz &&
test "$(tar -tzf "$NGROK_ARCHIVE")" = ngrok &&
tar -xzf "$NGROK_ARCHIVE" -C "$NGROK_DL_DIR"; then
install -d -m 0700 "$HOME/.local/bin"
install -m 0755 "$NGROK_DL_DIR/ngrok" "$HOME/.local/bin/ngrok"
export PATH="$HOME/.local/bin:$PATH"
ngrok version
sha256sum "$NGROK_ARCHIVE"
else
printf '下载或归档检查失败,停止安装;核对网络与官方包\n' >&2
fi网络受限时,可经获准的企业软件源或离线分发取得同平台发行包;比较来源、实际版本和发行方提供的摘要后再安装,不把某个第三方镜像当成唯一入口。终端输出应是 v3 版本;安装失败、输出为旧 v2 或命中了 PATH 中的另一个二进制时,先用 command -v ngrok 核对实际路径。注册已授权的 ngrok 账号,从控制台取得专用 agent authtoken;API key 用于管理 API,不能代替 agent 身份。不要把真实 token 粘进仓库或聊天记录。先以不回显输入把 authtoken 放进本次终端环境:
read -rsp 'ngrok agent authtoken: ' NGROK_AUTHTOKEN
printf '\n'
export NGROK_AUTHTOKEN
test -n "$NGROK_AUTHTOKEN" && ngrok version此环境变量只供本次 shell 的子进程读取,同一用户及高权限进程仍可能查看环境;共享 runner 应由其 secret store 注入,不用交互输入。ngrok config add-authtoken 则会把值持久写入本机配置,只适用于能妥善保护该文件的个人开发机。两者都不能替代 Endpoint 的访客认证。
本地静态服务终端保持运行,agent 安装、配置和启动都在第二个终端完成。不执行无策略的 ngrok http 8080;先把下一节的认证动作绑定在 Endpoint 上。
用 Agent Config v3 固化端点
在运行 agent 的终端为配置创建独占私有目录,切换进去后用本机编辑器保存下面的 ngrok.yml;不要放在 Git 工作区。先运行 openssl rand -hex 24 取得一枚只用于本次实验的 48 位十六进制密码,复制结果替换 REPLACE_WITH_RANDOM_TEST_PASSWORD,不能直接保留占位符上线。NGROK_AUTHTOKEN 仍由前一步的终端环境提供;agent 保存日志和本机检查界面的配置,不存实际 authtoken。入口权限用 Basic Auth 动作先在同一份配置中建立,随后才启动 Endpoint:
NGROK_CONFIG_DIR="$(mktemp -d /tmp/ngrok-config.XXXXXX)"
cd "$NGROK_CONFIG_DIR"
umask 077
openssl rand -hex 24
${EDITOR:-vi} ngrok.ymlversion: 3
agent:
log: stdout
log_format: json
web_addr: 127.0.0.1:4040
inspect_db_size: 52428800
endpoints:
- name: webhook-dev
description: "sandbox webhook"
traffic_policy:
on_http_request:
- actions:
- type: basic-auth
config:
credentials:
- "webhook-dev:REPLACE_WITH_RANDOM_TEST_PASSWORD"
upstream:
url: http://127.0.0.1:8080
protocol: http1version: 3 选择当前配置模型;全局 agent 行为位于 agent,公开入口位于 endpoints。旧 tunnels 结构处于弃用路径,不宜继续扩散到新模板。name 是启动和审计身份,upstream.url 决定本地目标,protocol 决定 agent 到上游的 HTTP 版本。没有配置固定 Endpoint URL 时,平台可以分配随机地址。固定域名需要先在账号中取得相应资源及配额,不能把随机地址交给长期回调方。
web_addr 是本地检查接口,应绑定回环地址,因为它会展示请求与转发状态。inspect_db_size 限制 agent 用来保存检查请求的内存;开发时保留适量证据即可,不能把完整请求体长期积存在共享机器。确认密码占位符已被替换、文件只允许当前用户读取,再按名称启动:
chmod 600 ./ngrok.yml
if ! test "$(stat -c %a ./ngrok.yml)" = 600; then
printf '配置权限异常,停止开放 Endpoint\n' >&2
elif grep -q 'REPLACE_WITH_RANDOM_TEST_PASSWORD' ./ngrok.yml; then
printf '先替换实验密码占位符,暂不开放 Endpoint\n' >&2
else
ngrok config check --config ./ngrok.yml
fi预期显示 Valid configuration file at ./ngrok.yml;解析失败时核对 YAML 缩进,不能跳过检查。仍在同一个已注入 NGROK_AUTHTOKEN 的终端启动:
: "${NGROK_AUTHTOKEN:?authtoken 为空,先完成交互输入}"
ngrok start webhook-dev --config ./ngrok.yml配置被解析后,agent 仍需凭真实 authtoken 与平台连接,账号配额也可能阻止 Endpoint 创建。终端出现公网 HTTPS 地址后,在允许直连的另一网络上设置 NGROK_URL='https://实际输出的域名',例如移动网络或隔离测试机。下面只指定用户名,由 curl 交互询问测试密码;若测试网络必须经过企业代理,应按批准的代理路径另做请求并记录路径,不能用此直连命令推断代理下的行为:
: "${NGROK_URL:?先填入 agent 输出的完整 https 地址}"
curl -q --noproxy '*' --fail-with-body --max-time 15 \
--user webhook-dev -i "$NGROK_URL/"预期 HTTP 200、响应体 tunnel-ok,终端一同时记录一次 / 请求。保留 agent,停止本地 HTTP 服务后重复同一地址的授权请求;若 Endpoint 仍在线,预期出现 upstream 错误,而不是 Basic Auth 的 401。此反例只破坏本地端口;恢复 upstream 后同一凭据应再次读回 tunnel-ok。公网实测需要真实账号和网络,本机配置校验不能替代这些观察。把一份 on_http_request 文件放在目录里不会自动生效;策略必须写入 Endpoint 的 traffic_policy,或通过明确引用的策略文件和启动参数挂载。
字段与启动语义应以 Agent Config v3 和 Agent CLI 为准;入口动作的当前能力则核对 Traffic Policy。团队模板可以固定验证项,不能把某次安装时看到的默认值永久写成产品事实。
Traffic Policy 是入口权限,不是业务身份
公网 URL 不充当访问密码。第一次实验的 Basic Auth 密码位于 0600 的本地 ngrok.yml,只适用于个人短时调试;共享团队配置应将密码迁入 ngrok Vault,避免多人分发含密码的配置文件。按 Vaults 与 Secrets在获准账号中创建 vault dev-tunnel,记录返回的 vault ID,再在其中建立 secret basic-password;API 管理需单独的 API key,agent authtoken 不能代替它。官方 CLI 的 secrets create --value 会把值置于命令参数,执行身份与历史记录必须先受控;共享机应交由已批准的秘密自动化完成,不把真实密码留在命令示例。建立后将 credentials 改为 "webhook-dev:${secrets.get('dev-tunnel', 'basic-password')}",校验、重启这一条 Endpoint,并重做匿名 401 与授权 200 对照。secrets.get() 在策略执行时按名字取值;不存在的 vault/secret 不会被自动创建,不能为了绕过错误直接删除认证动作。
Traffic Policy 的 on_http_request 在入口收到 HTTP 请求后执行规则,能在请求进入 agent 和 upstream 前认证或拒绝;on_http_response 则发生在 upstream 已返回之后,不能用响应阶段才补前置身份检查。上述 Basic Auth 只验证调用者知道入口密码,不验证 Webhook 消息由约定平台签发。策略阶段随协议变化:TCP/TLS 入口没有 HTTP 请求阶段,不能把这份 HTTP Basic Auth 规则原样拿去保护任意 TCP 服务。
先用合成静态服务核对边缘认证。匿名请求不使用 --fail-with-body,让 curl 在收到 401 时仍保留传输成功退出码,再检查状态:
status=$(curl -q --noproxy '*' --silent --show-error --max-time 15 \
--output /dev/null --write-out '%{http_code}' "$NGROK_URL/") &&
test "$status" = 401退出码为零且 status=401,同时终端一没有对应访问日志,才符合边缘拒绝的预期。curl 本身失败、状态为 000、收到 200 或本地出现匿名访问记录时,应停止 Endpoint,分别查 URL、会话连通和策略绑定。再用已授权用户名与密码请求同一 URL,预期 200 且 upstream 留下访问日志;错误密码仍应为 401。静态文件实验没有 Webhook 验签逻辑;第二层签名正反例要在真实测试应用接入后完成。
若调用方不能发送 Basic Auth,例如只发送平台签名的 Webhook,就不能要求它携带实验账号,更不能直接删除认证动作后开放匿名 upstream。ngrok 的 Verify Webhook 对受支持提供方可在入口校验原始消息的签名;以 GitHub 为例,在获准账号的 Vault 中准备与该 Webhook 配置一致的 webhook-vault/github-secret,停止当前 agent,把原来的 basic-auth 动作替换为下面的动作,而非追加第二条同名 traffic_policy:
traffic_policy:
on_http_request:
- actions:
- type: verify-webhook
config:
provider: github
secret: "${secrets.get('webhook-vault', 'github-secret')}"这一段位于同一个 endpoints 项内,name、upstream 和版本不变。重新运行 ngrok config check --config ./ngrok.yml 并启动命名 Endpoint;无签名、错签名或请求体被改动时,官方动作默认以 403 拒绝,upstream 不应留下该请求。有效签名要由已授权的 GitHub 测试 Webhook 发送真实测试事件,核对平台投递记录与本地请求日志;静态文件服务只会对 POST 报不支持,不能把它的响应当成业务处理成功。没有真实提供方、签名 secret 和专用测试接收器时,只能核对配置与官方语义,不宣称端到端验签已通过。
签名可在可信边缘验证,也可在应用按原始请求体自行验证;若应用还有绕开 ngrok 的入口,不能仅信任边缘结果。无论验签位置在哪里,接收器仍要按提供方事件 ID 幂等处理重试,并按适用协议核对时间窗与重放条件。Basic Auth 和 Webhook 签名解决的是不同问题,是否叠加取决于调用方能否发送两类凭据。
检查流量时先控制证据面
ngrok 的本地检查界面和账号支持的 Traffic Inspector 可查看请求路径、状态、耗时,并在受控场景下重放。agent 运行期间,在同一开发机的浏览器打开 http://127.0.0.1:4040,检查刚才获准的 200 请求是否有路径、状态和 upstream 记录;如果界面打不开,先核对 agent 仍在运行以及 web_addr 是否绑定回环地址。被入口策略挡下的 401 不会转发到本地 upstream,不能以本地检查界面缺少它为由判断测试失败;应结合外部 curl 状态、边缘侧记录和 upstream 日志判断。平台的 Full Capture 启用后会保存请求与响应的 header 和 body,其中可能有 Authorization、Cookie、签名和业务内容。即使策略引用的 secret 存在 vault,实际请求中的敏感字段仍可能进入捕获记录。Vault 保护静态策略里的密码,不会自动脱敏流量。
排障前准备合成数据,把捕获窗口缩到必要的正反请求。共享记录优先保留 request ID、路径模板、边缘状态码、upstream 状态码、耗时和字节数;导出完整报文前先做字段级脱敏。inspect_db_size 限制的是 agent 的内存检查库,不是一个默认落盘目录;终止 agent 会结束这份本地内存状态,另行导出的文件与平台侧 Traffic Inspector 数据仍要按各自保留策略处理。无需本地检查时可将 inspect_db_size 设为 -1 关闭该检查库,并重新校验配置。
如果重放操作会触发写入、发信、扣款或构建,应先把 upstream 切到无副作用的夹具服务。工具能重放不代表业务允许重放;可观察性功能本身也需要权限与变更边界。
把运行状态交给进程管理器
临时手工会话结束时,停止前台 agent;若 ngrok 被接入 systemd、容器或开发脚本,启动定义必须显式记录配置路径、Endpoint 名称、日志输出和 secret 注入方式。ngrok service 命令安装的服务按配置执行 start --all,因此不能把同一份含多个 Endpoint 的实验配置直接交给后台服务,也不能在尚未完成身份与守护策略时设置自动重启。健康检查不能只看进程存在,还要确认 agent 已连接、Endpoint 在线并且 upstream 可达。
仓库可以保存不带秘密的模板:
config/tunnel/ngrok.example.yml
scripts/tunnel/start-ngrok-webhook
scripts/tunnel/stop-ngrok-webhook
docs/runbooks/ngrok-webhook.md本地覆盖文件、token 和流量捕获应排除在版本控制之外:
.ngrok/
config/tunnel/ngrok.local.yml
config/tunnel/*token*
var/ngrok-inspect/启动脚本应先请求 127.0.0.1:8080 上的业务健康端点,再启动唯一的 Endpoint,并记录操作者、用途、预定退出条件与进程身份。若随机地址开始被 CI、客户演示或多个第三方长期依赖,须先建立有明确所有者、稳定域名、访客身份和容量预算的入口,再切换回调方;不能把临时地址的偶然稳定当成服务承诺。
容量与故障要沿链路观察
隧道增加了边缘处理、agent 控制连接以及 agent 到 upstream 的排队。文件上传、长连接、流式响应与突发 Webhook 会消耗并发、带宽、内存和账号配额。容量判断应观察端到端延迟分位数、边缘状态码、upstream 状态码、活跃连接、重连次数、传输字节与策略拒绝次数,而不是依据一次浏览器访问的体感。
| 现象 | 优先证据 | 常见边界 |
|---|---|---|
| 公网域名无法建立连接 | Endpoint 状态、账号事件、DNS | Endpoint 或边缘 |
公网返回 502 | agent 会话、本地 curl、upstream 日志 | agent 到本地服务 |
| 未授权请求进入应用 | Traffic Policy 绑定与边缘日志 | 入口策略 |
| 合法请求在应用被拒绝 | 原始请求验签与时间窗 | 业务身份 |
| 关闭后地址仍可访问 | 重复 agent、后台服务、另一个 Endpoint | 生命周期回收 |
关闭动作必须从外部证明
先把第三方回调切回占位地址或删除测试订阅,再于 agent 终端按 Ctrl+C;如果实际由 systemd、容器或 IDE 任务启动,须停止那个已核对身份的管理对象并检查它不会自动拉起。Agent Endpoint 随创建它的 agent 进程退出而结束;保留的自定义域名、Vault/Secret、导出的流量文件等账号资源则分别核对归属后再处理,不对共享域名做批量删除。从外部网络请求旧地址并观察本地服务日志:响应可能是连接失败或平台错误页,不能预设固定状态码;关键是旧请求不再进入本次 upstream,控制台也不再显示这一 Agent Endpoint 在线。
回环服务也用 Ctrl+C 停止。本次终端一打印的 NGROK_WEB_DIR、agent 终端的 NGROK_CONFIG_DIR 和 NGROK_DL_DIR 只用于删除各自明确列出的实验文件,不对父目录递归清理:
# upstream 所在终端:先确认该进程已停止
if [[ "${NGROK_WEB_DIR:-}" == /tmp/ngrok-web.* ]] && test -O "$NGROK_WEB_DIR"; then
rm -f -- "$NGROK_WEB_DIR/index.html" && rmdir -- "$NGROK_WEB_DIR"
fi# agent 所在终端:先确认 agent 已停止,且两个路径属于本次实验
if [[ "${NGROK_CONFIG_DIR:-}" == /tmp/ngrok-config.* ]] && test -O "$NGROK_CONFIG_DIR"; then
cd /tmp && rm -f -- "$NGROK_CONFIG_DIR/ngrok.yml" && rmdir -- "$NGROK_CONFIG_DIR"
fi
if [[ "${NGROK_DL_DIR:-}" == /tmp/ngrok-download.* ]] && test -O "$NGROK_DL_DIR"; then
rm -f -- "$NGROK_DL_DIR/ngrok" "$NGROK_DL_DIR/ngrok-v3-stable-linux-amd64.tgz" &&
rmdir -- "$NGROK_DL_DIR"
fi
unset NGROK_AUTHTOKEN安装到 ~/.local/bin/ngrok 的二进制没有自动删除;若该二进制已成为其他任务的依赖,保持原样。对仅供本次实验的 authtoken 和 Basic Auth 密码按账号权限撤销或轮换,外部 Traffic Inspector 记录按团队保留策略单独处理。
如果旧地址仍把请求转入本地,应检查另一个终端、systemd、容器、IDE 任务和共享开发机上的重复 agent;若平台只是保留域名或返回错误页,则不把它误判为 upstream 仍暴露。退出记录写清 Endpoint 名称、操作者、剩余账号资源、凭证轮换和外部请求结果。
权威资料与规范地址
安装、版本与产品能力
| 查阅内容 | 官方地址 |
|---|---|
| ngrok 官方下载页 | https://ngrok.com/download/linux |
| Linux amd64 官方 stable 包 | https://bin.ngrok.com/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz |
配置、API 与协议行为
| 查阅内容 | 官方地址 |
|---|---|
| Basic Auth 配置 | https://ngrok.com/docs/traffic-policy/actions/basic-auth/ |
| Agent Config v3 | https://ngrok.com/docs/agent/config/v3 |
| Agent CLI | https://ngrok.com/docs/agent/cli |
| Traffic Policy | https://ngrok.com/docs/traffic-policy |
| Traffic Policy 阶段与执行顺序 | https://ngrok.com/docs/gateway/traffic-policy/how-it-works |
| Verify Webhook 动作 | https://ngrok.com/docs/gateway/traffic-policy/actions/verify-webhook |
| Agent Endpoint 生命周期 | https://ngrok.com/docs/ai-gateway/guides/creating-endpoints |
安全、运行维护与故障处理
| 查阅内容 | 官方地址 |
|---|---|
| Vaults 与 Secrets 的创建步骤 | https://ngrok.com/docs/traffic-policy/secrets |
| 流量捕获与数据处理 | https://ngrok.com/security |
