expose-localhost

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Expose Localhost

暴露 Localhost

Expose a local service to the public internet using ngrok. Optionally add OAuth, OWASP protection, or rate limiting via Traffic Policy.
使用 ngrok 将本地服务暴露到公共互联网。可选择通过 Traffic Policy 添加 OAuth、OWASP 防护或速率限制。

Prerequisites

前置条件

  • ngrok CLI installed (
    ngrok
    command available)
  • Auth token configured (
    ngrok config add-authtoken <TOKEN>
    )
  • 已安装 ngrok CLI(
    ngrok
    命令可用)
  • 已配置 Auth token(
    ngrok config add-authtoken <TOKEN>
    )

Workflow

工作流

Step 1: Pre-flight & Configuration

第 1 步:预检与配置

Silently verify ngrok is ready:
bash
ngrok config check
If auth token missing, tell user to run:
ngrok config add-authtoken <TOKEN>
(get token at https://dashboard.ngrok.com/get-started/your-authtoken)
Then ask all questions upfront before doing anything:
Before I expose your service, I need a few details:

1. **Port**: I see your app runs on port 3000. Is that correct?
2. **Domain**: Use your dev domain, or do you have a custom domain?
3. **Access control**: Open access, or require login (Google/GitHub/etc.)?
4. **Save config?**: One-time setup, or save for reuse?
Do NOT mention cloud endpoints, reserved domains, or internal endpoints — those are advanced concepts the user shouldn't need to think about.
Detecting the port: Check
package.json
scripts for
--port
,
.env
for
PORT=
,
docker-compose.yml
for port mappings.
Is it even HTTP? If the user says SSH, RDP, a database, or names a port like 22, 3389, 5432, or 3306, this is a raw TCP service — skip to Non-HTTP Services (TCP). The domain and access-control questions above do not apply there.
Domains: Most ngrok accounts have a free static dev domain (e.g.,
something.ngrok-free.dev
). Running
ngrok http PORT
uses it automatically. Users can also provide a custom domain configured in the ngrok dashboard. Some accounts (especially new ones) may not have a dev domain yet — if ngrok fails with
ERR_NGROK_15013
, tell the user: "You don't have a dev domain yet. Claim your free one at https://dashboard.ngrok.com/domains — then we can try again."
If user requests OAuth, also ask: "Should only specific people be able to access it? I can restrict by email address or email domain."
After gathering answers, confirm and get a Y/n before proceeding.
静默验证 ngrok 是否已准备就绪:
bash
ngrok config check
如果缺少 Auth token,请让用户运行:
ngrok config add-authtoken <TOKEN>
(可从 https://dashboard.ngrok.com/get-started/your-authtoken 获取 token)。
然后,在执行任何操作之前一次性询问所有问题:
Before I expose your service, I need a few details:

1. **Port**: I see your app runs on port 3000. Is that correct?
2. **Domain**: Use your dev domain, or do you have a custom domain?
3. **Access control**: Open access, or require login (Google/GitHub/etc.)?
4. **Save config?**: One-time setup, or save for reuse?
不要提及云端点、保留域名或内部端点——这些是高级概念,用户不应需要考虑这些内容。
检测端口:检查
package.json
scripts 中是否有
--port
、
.env
中是否有
PORT=
,以及
docker-compose.yml
中的端口映射。
这是 HTTP 服务吗? 如果用户提到 SSH、RDP、数据库,或说出
22
、
3389
、
5432
、
3306
这样的端口,则这是原始 TCP 服务——跳转到非 HTTP 服务(TCP)。域名和访问控制问题不适用于该场景。
域名:大多数 ngrok 账号都有免费的静态开发域名(例如
something.ngrok-free.dev
)。运行
ngrok http PORT
会自动使用它。用户也可以提供在 ngrok dashboard 中配置的自定义域名。部分账号(尤其是新账号)可能还没有开发域名——如果 ngrok 出现
ERR_NGROK_15013
,告诉用户:“你还没有开发域名。请在 https://dashboard.ngrok.com/domains 领取免费域名,然后我们可以重试。”
如果用户要求 OAuth,还要询问:“是否只允许特定人员访问?我可以按电子邮件地址或电子邮件域名进行限制。”
收集完答案后,确认并获得 Y/n 再继续。

Step 2: Start the Tunnel

第 2 步:启动隧道

No security (simplest)

不启用安全措施(最简单)

bash
ngrok http {PORT} &
sleep 3
curl -s http://localhost:4040/api/tunnels | grep -o '"public_url":"[^"]*"' | head -1
With a specific domain, add
--url https://{DOMAIN}
.
bash
ngrok http {PORT} &
sleep 3
curl -s http://localhost:4040/api/tunnels | grep -o '"public_url":"[^"]*"' | head -1
使用特定域名时,添加
--url https://{DOMAIN}
。

With security (Traffic Policy)

使用安全措施(Traffic Policy)

Create the traffic policy file first, then start ngrok with it.
OAuth-only (default when user requests auth):
yaml
on_http_request:
  - actions:
      - type: oauth
        config:
          provider: google
Replace
google
with the chosen provider (google, github, microsoft, gitlab, linkedin, twitch).
If the user requests OAuth, default to OAuth-only. Do NOT add OWASP or rate limiting unless explicitly asked — OAuth already blocks unauthenticated access.
OAuth with email restriction — use a separate rule with a CEL expression to deny non-matching emails. Do NOT add an
allow
field to the OAuth action.
Single email:
yaml
on_http_request:
  - actions:
      - type: oauth
        config:
          provider: google
  - expressions:
      - "actions.ngrok.oauth.identity.email != 'user@example.com'"
    actions:
      - type: deny
        config:
          status_code: 403
Email domain:
yaml
on_http_request:
  - actions:
      - type: oauth
        config:
          provider: google
  - expressions:
      - "!actions.ngrok.oauth.identity.email.endsWith('@your-company.com')"
    actions:
      - type: deny
        config:
          status_code: 403
Multiple emails — name the identity explicitly on the left of
in
:
yaml
  - expressions:
      - "!(actions.ngrok.oauth.identity.email in ['a@x.com', 'b@x.com'])"
    actions:
      - type: deny
        config:
          status_code: 403
Shared secret (when the person won't log in with an IdP — an external client, a reviewer without a company account):
yaml
on_http_request:
  - actions:
      - type: basic-auth
        config:
          credentials:
            - "reviewer:{PASSWORD}"
Hand them the URL and the password over a channel you already trust. If they have a stable IP,
restrict-ips
is an alternative with nothing to share.
Open-access hardening (no auth, but wants protection):
yaml
on_http_request:
  - actions:
      - type: rate-limit
        config:
          name: default-rate-limit
          algorithm: sliding_window
          capacity: 200
          rate: "60s"
          bucket_key:
            - conn.client_ip
      - type: owasp-crs-request
        config:
          on_error: halt

on_http_response:
  - actions:
      - type: owasp-crs-response
        config:
          on_error: halt
After writing the policy file, start ngrok:
bash
ngrok http {PORT} --traffic-policy-file .ngrok/traffic-policy.yml &
sleep 3
curl -s http://localhost:4040/api/tunnels | grep -o '"public_url":"[^"]*"' | head -1
Add
--url https://{DOMAIN}
if using a specific domain.
Sharing a dev server? Vite, Next, and webpack reject requests whose Host header isn't localhost, and their hot-reload websockets need the ngrok domain allowed. Set this up before handing over the URL — see
references/TROUBLESHOOTING.md
.
先创建 Traffic Policy 文件,然后使用它启动 ngrok。
仅 OAuth(用户要求身份验证时的默认选项):
yaml
on_http_request:
  - actions:
      - type: oauth
        config:
          provider: google
将
google
替换为选定的提供商(google、github、microsoft、gitlab、linkedin、twitch)。
如果用户要求 OAuth,默认使用仅 OAuth 方案。不要添加 OWASP 或速率限制,除非用户明确要求——OAuth 已经可以阻止未经身份验证的访问。
OAuth 与电子邮件限制——使用带 CEL expression 的独立规则拒绝不匹配的电子邮件。不要在 OAuth 操作中添加
allow
字段。
单个电子邮件:
yaml
on_http_request:
  - actions:
      - type: oauth
        config:
          provider: google
  - expressions:
      - "actions.ngrok.oauth.identity.email != 'user@example.com'"
    actions:
      - type: deny
        config:
          status_code: 403
电子邮件域名:
yaml
on_http_request:
  - actions:
      - type: oauth
        config:
          provider: google
  - expressions:
      - "!actions.ngrok.oauth.identity.email.endsWith('@your-company.com')"
    actions:
      - type: deny
        config:
          status_code: 403
多个电子邮件——在
in
左侧明确指定 identity:
yaml
  - expressions:
      - "!(actions.ngrok.oauth.identity.email in ['a@x.com', 'b@x.com'])"
    actions:
      - type: deny
        config:
          status_code: 403
共享密钥(当对方不会使用 IdP 登录时——例如外部客户端或没有公司账号的审阅者):
yaml
on_http_request:
  - actions:
      - type: basic-auth
        config:
          credentials:
            - "reviewer:{PASSWORD}"
通过你已经信任的渠道将 URL 和密码交给对方。如果对方使用固定 IP,也可以使用
restrict-ips
,无需共享任何凭据。
开放访问加固(无 Auth,但需要防护):
yaml
on_http_request:
  - actions:
      - type: rate-limit
        config:
          name: default-rate-limit
          algorithm: sliding_window
          capacity: 200
          rate: "60s"
          bucket_key:
            - conn.client_ip
      - type: owasp-crs-request
        config:
          on_error: halt

on_http_response:
  - actions:
      - type: owasp-crs-response
        config:
          on_error: halt
写入策略文件后,启动 ngrok:
bash
ngrok http {PORT} --traffic-policy-file .ngrok/traffic-policy.yml &
sleep 3
curl -s http://localhost:4040/api/tunnels | grep -o '"public_url":"[^"]*"' | head -1
使用特定域名时,添加
--url https://{DOMAIN}
。
共享开发服务器? Vite、Next 和 webpack 会拒绝 Host 请求头不是 localhost 的请求,而且其热重载 WebSocket 需要允许使用 ngrok 域名。在交付 URL 前先完成此设置——参见
references/TROUBLESHOOTING.md
。

Step 3: Handle Errors

第 3 步:处理错误

If a traffic policy action fails due to plan limitations:
  1. Tell the user which specific action requires an upgrade
  2. Offer to remove that action from the policy and retry
  3. Do NOT suggest switching to cloud endpoints as a workaround
如果某个 Traffic Policy 操作因套餐限制而失败:
  1. 告诉用户具体哪个操作需要升级套餐
  2. 提议从策略中移除该操作并重试
  3. 不要建议以 Cloud Endpoints 作为变通方案

Step 4: Persistent Configuration (If Requested)

第 4 步:持久化配置(如有请求)

Save these files to the project:
  • .ngrok/traffic-policy.yml
    — the policy (if security was configured)
  • .ngrok/expose.sh
    :
bash
#!/bin/bash
set -e
echo "Your service will be at: https://{DOMAIN}"
ngrok http {PORT} --url https://{DOMAIN} --traffic-policy-file .ngrok/traffic-policy.yml
Omit
--traffic-policy-file
if no policy. Omit
--url
if no specific domain.
Optionally add to
package.json
:
json
{ "scripts": { "tunnel": "bash .ngrok/expose.sh" } }
将这些文件保存到项目中:
  • .ngrok/traffic-policy.yml
    ——策略(如果配置了安全措施)
  • .ngrok/expose.sh
    :
bash
#!/bin/bash
set -e
echo "Your service will be at: https://{DOMAIN}"
ngrok http {PORT} --url https://{DOMAIN} --traffic-policy-file .ngrok/traffic-policy.yml
如果没有策略,省略
--traffic-policy-file
。如果没有特定域名,省略
--url
。
也可以选择添加到
package.json
:
json
{ "scripts": { "tunnel": "bash .ngrok/expose.sh" } }

Teardown

关闭隧道

bash
pkill ngrok
bash
pkill ngrok

Non-HTTP Services (TCP)

非 HTTP 服务(TCP)

For SSH, RDP, or a database, use
ngrok tcp
instead of
ngrok http
.
bash
ngrok tcp {PORT} &
sleep 3
curl -s http://localhost:4040/api/tunnels | grep -o '"public_url":"[^"]*"' | head -1
ngrok returns an address like
tcp://1.tcp.ngrok.io:12345
. The host and port are used separately by the client:
bash
undefined
对于 SSH、RDP 或数据库,请使用
ngrok tcp
而不是
ngrok http
。
bash
ngrok tcp {PORT} &
sleep 3
curl -s http://localhost:4040/api/tunnels | grep -o '"public_url":"[^"]*"' | head -1
ngrok 会返回类似
tcp://1.tcp.ngrok.io:12345
的地址。客户端需要分别使用其中的主机和端口:
bash
undefined

local sshd on port 22, reached through the address above

local sshd on port 22, reached through the address above

ssh -p 12345 {USER}@1.tcp.ngrok.io

Common ports: SSH `22`, RDP `3389`, PostgreSQL `5432`, MySQL `3306`.

**The address changes on every restart.** For one that persists, reserve it first.
ngrok assigns the hostname and port — unlike domains, you cannot choose them:

```bash
ngrok api reserved-addrs create --description "SSH for {NAME}" --region us
ngrok tcp {PORT} --url tcp://{ADDR}
Reserved TCP addresses need a paid plan and keep billing until deleted.
Access control is different here. OAuth and OWASP are HTTP-only and will not work. TCP policies run in the
on_tcp_connect
phase, where
restrict-ips
is the usual control:
yaml
on_tcp_connect:
  - actions:
      - type: restrict-ips
        config:
          enforce: true
          allow:
            - 203.0.113.0/24
Attach it the same way:
ngrok tcp {PORT} --traffic-policy-file .ngrok/traffic-policy.yml
ssh -p 12345 {USER}@1.tcp.ngrok.io

常用端口:SSH `22`、RDP `3389`、PostgreSQL `5432`、MySQL `3306`。

**每次重启时地址都会变化。** 如果需要持久地址,请先保留该地址。ngrok 会分配主机名和端口——与域名不同,你不能自行选择:

```bash
ngrok api reserved-addrs create --description "SSH for {NAME}" --region us
ngrok tcp {PORT} --url tcp://{ADDR}
保留的 TCP 地址需要付费套餐,并会持续计费,直至删除。
这里的访问控制方式不同。 OAuth 和 OWASP 仅适用于 HTTP,无法用于 TCP。TCP 策略在
on_tcp_connect
阶段运行,通常使用
restrict-ips
作为控制手段:
yaml
on_tcp_connect:
  - actions:
      - type: restrict-ips
        config:
          enforce: true
          allow:
            - 203.0.113.0/24
以相同方式附加:
ngrok tcp {PORT} --traffic-policy-file .ngrok/traffic-policy.yml

Cloud Endpoints (Advanced)

Cloud Endpoints(高级)

Only use if the user explicitly needs a URL that persists after the agent stops (e.g., webhooks, long-lived integrations).
Requires the ngrok MCP server or an API key. If the user manages ngrok as code, add the endpoint there instead. If not, and the MCP server is connected, use it and do not ask for an API key:
gw_reserve_domain
and
gw_create_cloud_endpoint
replace steps 1 and 2, and
delete_resource
replaces step 4. Otherwise the user sets an API key with
ngrok config add-api-key <KEY>
(from https://dashboard.ngrok.com/api-keys). See
ngrok-surfaces
for the order to pick in.
  1. Reserve domain:
    ngrok api reserved-domains create --domain "{DOMAIN}"
  2. Create cloud endpoint with a traffic policy that includes
    forward-internal
    as the last action:
bash
ngrok api endpoints create --url "https://{DOMAIN}" --bindings public --traffic-policy "$(cat .ngrok/traffic-policy.yml)"
The traffic policy must end with:
yaml
- type: forward-internal
  config:
    url: https://{NAME}.internal
  1. Start the internal agent:
    ngrok http {PORT} --url https://{NAME}.internal
  2. Teardown:
    ngrok api endpoints delete {ENDPOINT_ID}
仅当用户明确需要一个在 Agent 停止后仍然有效的 URL 时,才使用此功能(例如 webhook、长期运行的集成)。
这需要 ngrok MCP server 或 API key。如果用户以代码方式管理 ngrok,应改为在其中添加 endpoint。如果未以代码方式管理,但已连接 MCP server,则使用它且不要索要 API key:
gw_reserve_domain
和
gw_create_cloud_endpoint
替代第 1、2 步,
delete_resource
替代第 4 步。否则,用户应使用
ngrok config add-api-key <KEY>
设置 API key(可从 https://dashboard.ngrok.com/api-keys 获取)。有关选择顺序,请参阅
ngrok-surfaces
。
  1. 保留域名:
    ngrok api reserved-domains create --domain "{DOMAIN}"
  2. 创建 Cloud Endpoint,并让 Traffic Policy 将
    forward-internal
    作为最后一个操作:
bash
ngrok api endpoints create --url "https://{DOMAIN}" --bindings public --traffic-policy "$(cat .ngrok/traffic-policy.yml)"
Traffic Policy 必须以以下内容结尾:
yaml
- type: forward-internal
  config:
    url: https://{NAME}.internal
  1. 启动内部 Agent:
    ngrok http {PORT} --url https://{NAME}.internal
  2. 拆除 endpoint:
    ngrok api endpoints delete {ENDPOINT_ID}

Related Skills

相关 Skills

This skill covers giving a service a public URL. Neighbouring jobs have their own skills — hand off rather than reimplementing them here:
  • Auth, rate limits, IP rules, or a maintenance page in general —
    secure-endpoint
    . (Sharing a preview with one named person is handled here, in Step 2.)
  • It should receive webhooks —
    receive-webhooks
    verifies provider signatures at the edge and can keep the receiver off the public internet.
  • It is an MCP server —
    run-mcp-gateway
    handles per-provider credentials.
  • One endpoint per sandbox, tenant, or device, created by a controlplane —
    provision-sandbox-access
    .
  • Something is broken (
    ERR_NGROK_*
    , a 400 through the URL but not on localhost) —
    troubleshoot-ngrok
    .
  • Setup or auth problems —
    ngrok-setup
    .
For the underlying model — endpoint types, HTTP vs TCP, and the full Traffic Policy action set — see
ngrok-engine
. For applying config on a surface other than the CLI (SDKs, REST API, Terraform, Kubernetes), see
ngrok-surfaces
.
此 skill 涵盖为服务提供公共 URL。相邻任务有各自的 skill——应转交给它们,而不是在这里重新实现:
  • 一般需要 Auth、速率限制、IP 规则或维护页面——
    secure-endpoint
    。(在本 skill 的第 2 步中,也处理向指定人员共享预览。)
  • 需要接收 webhook——
    receive-webhooks
    会在边缘验证提供商签名,并让接收器无需暴露在公共互联网上。
  • 它是 MCP server——
    run-mcp-gateway
    处理各提供商的凭据。
  • 每个 sandbox、tenant 或 device 使用一个 endpoint,并由 controlplane 创建——
    provision-sandbox-access
    。
  • 出现故障(
    ERR_NGROK_*
    、通过 URL 访问返回 400 但在 localhost 上正常)——
    troubleshoot-ngrok
    。
  • 安装或 Auth 问题——
    ngrok-setup
    。
对于底层模型——endpoint 类型、HTTP 与 TCP,以及完整的 Traffic Policy 操作集——请参阅
ngrok-engine
。如需在 CLI 以外的 surface(SDK、REST API、Terraform、Kubernetes)上应用配置,请参阅
ngrok-surfaces
。