# lpm tunnel (/docs/infra/tunnel)



```bash
lpm tunnel <port-or-action> [domain]
```

Exposes a local port to the public internet through the LPM.dev Registry tunnel network. Used by [`lpm dev --tunnel`](/docs/dev/dev) under the hood, and directly when you need a public URL outside the dev server (running an MCP server locally, hosting a webhook receiver, sharing a preview).

Webhooks that arrive at the public URL are captured to disk. You can browse, filter, and replay them from the CLI or a browser inspector.

## Examples [#examples]

```bash
lpm tunnel                                # tunnel the only active lpm dev endpoint
lpm tunnel 4000                           # start on port 4000
lpm tunnel claim acme-api.lpm.llc         # claim a stable domain (Pro/Org)
lpm tunnel unclaim acme-api.lpm.llc       # release a claimed domain
lpm tunnel list                           # your claimed domains
lpm tunnel domains                        # available base domains
lpm tunnel inspect                        # captured webhook event log
lpm tunnel inspect -- --ui                # open browser inspector
lpm tunnel replay 3                       # replay event #3 to localhost
lpm tunnel log                            # browse event log
```

## Starting a tunnel [#starting-a-tunnel]

```bash
lpm dev
lpm tunnel

# Or target an explicit server:
lpm tunnel 3000
```

Returns an `https://` URL that proxies to `http://127.0.0.1:3000/`. Requires a refresh-backed LPM.dev Registry login session — `lpm tunnel` rejects `--token`, `LPM_TOKEN`, and CI-only tokens for relay-facing actions. Free users get an ephemeral random domain on every run; Pro and Org users can claim a stable one (see below).

With no numeric port, `lpm tunnel` discovers the verified endpoint published by a running `lpm dev` session. This preserves the child server's loopback address, port, and base path, so Vite on `5173` or a second Vite process on `5174` works without restating the port. No active session is a clear error. If multiple dev sessions are active, pass the intended port explicitly; LPM CLI never guesses between them.

Tunnel and replay forwarding require a plain HTTP child. LPM CLI never disables certificate verification for an arbitrary framework HTTPS server. If the child advertises HTTPS, disable framework-level HTTPS and use `lpm dev --https`: LPM CLI terminates trusted browser-facing TLS while the tunnel safely forwards to the verified HTTP child.

```bash
lpm tunnel 3000 acme-api.lpm.llc          # use a claimed domain
lpm tunnel 3000 --tunnel-auth             # require an auth token to access
lpm tunnel 3000 --auto-ack                # 200 OK while local server is down
lpm tunnel 3000 --session "stripe-test"   # name the session in the inspector
```

The domain must include a base — `lpm.fyi` (free + Pro/Org) or `lpm.llc` (Pro/Org). `lpm tunnel 3000 acme` (no dot) fails with `Missing base domain` and suggests the available bases; `lpm tunnel domains` lists every enabled base with the plan tier required.

`--auto-ack` is useful when you're not running a server but need webhook providers to keep their endpoint active — providers typically deactivate endpoints after repeated 5xx responses.

Once connected, the startup block prints the public URL, inspector URL, plan, expiry when the relay advertises one, active limits, current accepted/included request usage, overage state, session, complete local URL, and domain. `--json` keeps `local_port` for compatibility and adds `local_url` so scripts retain the scheme, IPv4/IPv6 authority, and base path; it also includes the additive `limits` and `usage` objects. Current CLI versions print live usage warnings when the relay sends allowance or overage threshold notices. Press `o` to open the inspector when it is running, or `q` to quit the tunnel. Incoming requests stream as compact rows such as `→ POST /hooks/stripe 201 42ms`.

## Request allowance [#request-allowance]

| Plan | Included requests                  | Account-wide rate      | Concurrent tunnels | After allowance                 |
| ---- | ---------------------------------- | ---------------------- | ------------------ | ------------------------------- |
| Free | 20,000/UTC month                   | 4,000/min              | 1                  | hard stop                       |
| Pro  | 100,000/billing period             | 20,000/min             | 3                  | $1/100,000 proportional overage |
| Org  | 100,000/billed seat/billing period | 20,000/min/billed seat | 10                 | $1/100,000 proportional overage |

Every plan also applies 600 requests/minute to each visitor IP within the account. Limits and usage are shared across the account's domains; three Pro tunnels do not receive three separate allowances. Pro and Org overage is enabled by default and can be turned off from the tunnel dashboard for a hard stop.

An admitted HTTP request counts once even if localhost later fails or times out. A successful public WebSocket upgrade counts once; its frames do not count. Public WebSockets require a current protocol-v2 CLI connection; tunnels connected by an older protocol-v1 client return `426 Upgrade Required` before quota admission. See [Request usage and overage](/docs/infra/tunneling#request-usage-and-overage) for exclusions and billing-period behavior.

## Claiming a stable domain [#claiming-a-stable-domain]

```bash
lpm tunnel claim acme-api.lpm.llc
lpm tunnel claim acme-api.lpm.llc --org acme    # claim under an org
lpm tunnel list                                  # show claimed
lpm tunnel unclaim acme-api.lpm.llc              # release
```

Claimed domains persist across runs — the same URL keeps working as long as you hold the claim. Discover available base domains with `lpm tunnel domains`.

## Inspecting and replaying webhooks [#inspecting-and-replaying-webhooks]

```bash
lpm tunnel inspect                # show captured events (terminal table)
lpm tunnel inspect -- --ui        # open the inspector in a browser
lpm tunnel inspect -- --last 10
lpm tunnel inspect -- --status 4xx
lpm tunnel inspect -- --filter stripe
lpm tunnel log                    # event log browse view

lpm tunnel replay 3               # re-deliver event #3 to the local server
lpm tunnel replay 3 -- --port 4000
```

Captured webhook bodies, headers, and statuses live under the project's tunnel log directory. Local `inspect`, `log`, and `replay` flags are forwarded after `--`. Replays use an explicit `--port`, or the single active `lpm dev` endpoint when no replay port is given. Ports must be in `1..=65535`; zero, negative, and overflowing values fail instead of wrapping.

## Inspector port [#inspector-port]

The inspector (web UI) starts automatically alongside the tunnel and binds to a free ephemeral port — the URL is printed on startup. Pass an explicit port if you want a stable bookmark, or `--no-inspect` to skip the inspector:

```bash
lpm tunnel 3000                       # inspector at http://127.0.0.1:<auto-picked>
lpm tunnel 3000 --inspect-port 4500   # inspector at http://127.0.0.1:4500
lpm tunnel 3000 --no-inspect          # skip the inspector entirely
```

The auto-pick keeps the inspector race-free against the dev server's own port and any other local service. An explicit non-zero `--inspect-port N` is strict — if `N` is already in use, the command fails with a clear diagnostic instead of silently rolling to another port.

`lpm dev --tunnel` accepts the same `--inspect-port` and `--no-inspect` flags and surfaces the inspector URL in the startup banner. During multi-service startup the inspector reports that it is waiting for the dev server instead of showing a guessed port; after ownership verification its status includes the resolved `local_url`. The tunnel footer's `o` key opens the inspector in a browser.

## Flags [#flags]

| Flag                 | Effect                                                                             |
| -------------------- | ---------------------------------------------------------------------------------- |
| `--org <SLUG>`       | Operate against an organization (claim/unclaim/list)                               |
| `--tunnel-auth`      | Require an auth token to access the tunnel URL (Pro/Org only)                      |
| `--auto-ack`         | Return 200 OK to webhooks when the local server is down                            |
| `--session <NAME>`   | Name this tunnel session in the inspector                                          |
| `--no-inspect`       | Disable the auto-started inspector UI                                              |
| `--inspect-port <N>` | Bind the inspector to exactly this port (default: auto-pick a free ephemeral port) |

Trailing arguments are forwarded to inspect / replay / log subcommands after `--` (for example: `lpm tunnel inspect -- --last 10`, `lpm tunnel replay 3 -- --port 4000`).

Plus the [global flags](/docs/commands#global-flags).

## See also [#see-also]

* [`lpm dev --tunnel`](/docs/dev/dev) — start a tunnel as part of the dev pipeline
* [Tunneling](/docs/infra/tunneling) — how the tunnel network and capture system work
* [`lpm cert`](/docs/infra/cert) — local HTTPS for non-tunnel localhost
