lpm tunnel
Expose a local HTTP service through a public HTTPS URL, with request capture and replay.
lpm tunnel gives a public HTTPS URL to a local HTTP service.
Use it for webhook development, API callbacks, remote-device tests, and temporary previews.
For a repeatable project workflow, set a claimed domain in lpm.json and run lpm dev.
When a service already runs or you manage capture history, use standalone lpm tunnel.
lpm tunnel [<port>|<action>] [target] [flags]Quickstart with lpm.json
When a project needs the same public URL across development sessions, use lpm.json.
First, claim the domain once:
lpm login
lpm tunnel claim acme-api.lpm.llcThen add the domain to lpm.json, next to package.json:
{
"$schema": "https://cli.lpm.dev/schemas/lpm.json",
"tunnel": {
"domain": "acme-api.lpm.llc"
}
}Start the project:
lpm devThe domain field enables the tunnel automatically. You do not need to add --tunnel.
lpm dev starts the project and waits for its final HTTP endpoint. Then it opens the tunnel to that endpoint.
If services is absent, lpm dev runs the dev script from package.json.
Use this setup for stable webhook URLs, callback URLs, and preview links.
The tunnel value is an object, not a boolean value. The object currently accepts only domain.
For an assigned domain instead of a saved domain, omit the tunnel object:
lpm dev --tunnelFree users receive a random lpm.fyi domain. A claimed domain requires a Pro or Org plan.
Override or disable the project tunnel
The lpm dev flags override lpm.json for one run:
lpm dev --domain preview-api.lpm.llc # use another claimed domain and enable the tunnel
lpm dev --no-tunnel # disable the configured tunnel for this runThe --domain flag belongs to lpm dev. Standalone lpm tunnel accepts the domain as a positional argument.
Tunnel access and inspector flags also work with lpm dev:
lpm dev --tunnel-auth # protect the public URL with a session token
lpm dev --inspect-port 4500 # use a fixed browser-inspector port
lpm dev --no-inspect # keep capture but skip the browser inspectorMulti-service projects
If lpm.json contains one service, LPM CLI uses that service as the tunnel target.
If it contains multiple services, mark exactly one service as primary:
{
"$schema": "https://cli.lpm.dev/schemas/lpm.json",
"tunnel": {
"domain": "acme-api.lpm.llc"
},
"services": {
"web": {
"command": "vite",
"port": 5173,
"primary": true
},
"api": {
"command": "node api.js",
"port": 4000
}
}
}lpm devThe public URL routes to the final endpoint of web. The api service remains available to the project on its local endpoint.
See the lpm.json tunnel reference for the field definition.
One-time tunnel
Start your local HTTP service on port 3000. Then open the tunnel:
lpm login
lpm tunnel 3000The command prints a public URL and forwards requests to http://127.0.0.1:3000/.
While you use the URL, keep the command open. Press q or Ctrl+C to stop the tunnel.
Examples
lpm dev # use tunnel.domain from lpm.json
lpm dev --tunnel # use an assigned domain for this run
lpm dev --no-tunnel # skip the lpm.json tunnel for this run
lpm tunnel # tunnel the only active lpm dev endpoint
lpm tunnel 4000 # tunnel an explicit local port
lpm tunnel start acme-api.lpm.llc # tunnel the active dev endpoint with a claimed domain
lpm tunnel 4000 acme-api.lpm.llc # use a claimed domain
lpm tunnel claim acme-api.lpm.llc # claim a personal domain
lpm tunnel claim staging.lpm.fyi --org acme # claim an organization domain
lpm tunnel unclaim acme-api.lpm.llc # release a claimed domain
lpm tunnel list # list personal domain claims
lpm tunnel list --org acme # list organization domain claims
lpm tunnel domains # list available base domains
lpm tunnel inspect --last 10 # show recent captured requests
lpm tunnel inspect --ui # open the browser inspector
lpm tunnel replay --last --port 4000 # replay the newest capture
lpm tunnel log --status 5xx # show failed requestsChoose a command
| Goal | Command |
|---|---|
Start the tunnel from lpm.json | lpm dev |
| Start the project with an assigned domain | lpm dev --tunnel |
Disable the lpm.json tunnel for one run | lpm dev --no-tunnel |
Tunnel the only active lpm dev endpoint | lpm tunnel |
| Tunnel an explicit port | lpm tunnel 3000 |
| Tunnel the active endpoint with a claimed domain | lpm tunnel start acme-api.lpm.llc |
| Tunnel with a claimed domain | lpm tunnel 3000 acme-api.lpm.llc |
| Claim a stable domain | lpm tunnel claim <domain> |
| Release a claimed domain | lpm tunnel unclaim <domain> |
| List claimed domains | lpm tunnel list |
| List available base domains | lpm tunnel domains |
| Show captured requests | lpm tunnel inspect |
| Replay a captured request | lpm tunnel replay <number> |
| Browse or clear capture history | lpm tunnel log |
The CLI also accepts release, ls, and logs as aliases.
Authentication
The remote actions require a saved login session from lpm login.
This requirement applies to tunnel startup, domain claims, domain lists, and base-domain lists.
lpm login
lpm tunnel 3000The remote actions do not accept --token, LPM_TOKEN, or CI-only tokens.
When you revoke a CLI session in the registry dashboard, its active tunnels close after the registry processes the revocation. Run lpm login again before restarting the tunnel.
LPM CLI renews the saved session credential before it expires. Renewal keeps the same public URL, active connections, and original Free-session deadline. If renewal fails until the credential expires, the connection closes. A later reconnection can receive a new Free URL. Older relay versions refresh credentials through reconnection, which can close active requests and WebSockets.
The inspect, replay, and log actions use local files. These actions do not require a login.
Select the local service
Use an explicit port
If you know the local listener, pass its port:
lpm tunnel 3000An explicit port always targets http://127.0.0.1:<port>/. Valid ports are from 1 through 65535.
The local service must use plain HTTP. The public tunnel URL still uses HTTPS.
If your framework uses HTTPS, disable framework HTTPS. Use lpm dev --https for trusted local browser TLS.
Use an active lpm dev endpoint
Run these commands in different terminals:
# Terminal 1
lpm dev
# Terminal 2
lpm tunnelWithout a port, LPM CLI selects the only active lpm dev endpoint. It preserves the endpoint address, port, and base path.
If no active endpoint exists, pass a port. If multiple endpoints exist, pass the intended port.
Use lpm dev --tunnel to start the dev server and tunnel from one command.
The tunnel forwards HTTP requests and WebSocket connections. Empty WebSocket close frames remain empty, and normal close codes and reasons pass through.
Recipes
Receive webhooks on localhost
Run your webhook service and tunnel in different terminals:
# Terminal 1
lpm run dev
# Terminal 2
lpm tunnel 3000 --session stripe-testAdd the webhook path to the printed URL. For example, use https://<domain>/webhooks/stripe for a /webhooks/stripe route.
LPM CLI saves each request and local response in the current directory. The session name helps you find related requests later.
Replay a failed webhook
List recent captures. Then inspect and replay the selected entry:
lpm tunnel inspect --last 10
lpm tunnel inspect --detail 3
lpm tunnel replay 3 --port 3000Capture numbers use the newest-first list and start at 1.
Replay sends the original method, path, headers, and body to the selected local service.
If one lpm dev endpoint is active, you can omit the replay port:
lpm tunnel replay --lastKeep a webhook endpoint active
When the tunnel must accept requests without the local service, use --auto-ack:
lpm tunnel 3000 --auto-ackWhen LPM CLI cannot reach the local service, it returns 200 OK. It still saves the request for later inspection and replay.
CAUTION: The provider records the request as successful, although your local service did not process it.
Protect a preview URL
Pro and Org tunnels can require a per-session access token:
lpm tunnel 3000 acme-api.lpm.llc --tunnel-authThe startup output prints an X-Tunnel-Auth header and a browser URL. Treat the token and browser URL as secrets.
Use a fixed inspector port
The browser inspector starts with each tunnel and selects an available local port.
Use a fixed port for a stable local bookmark:
lpm tunnel 3000 --inspect-port 4500If port 4500 is busy, the command fails. Without this flag, an inspector startup error does not stop the tunnel.
To run without the browser inspector, use:
lpm tunnel 3000 --no-inspectWhen --no-inspect is active, capture continues.
Stable domains
Free tunnels receive a random lpm.fyi domain for each session. Stable domain claims require a Pro or Org plan.
List the enabled base domains before you select a name:
lpm tunnel domainsCurrent base domains are:
lpm.fyifor Free, Pro, and Org tunnels.lpm.llcfor Pro and Org tunnels.
A Free tunnel can use lpm.fyi for its random domain. A stable claim on either base still requires Pro or Org.
Claim and use a personal domain:
lpm tunnel claim acme-api.lpm.llc
lpm tunnel list
lpm tunnel 3000 acme-api.lpm.llcOrganization owners and administrators can manage organization domains:
lpm tunnel claim staging.lpm.fyi --org acme
lpm tunnel list --org acme
lpm tunnel 3000 staging.lpm.fyiThe subdomain must contain from 3 through 32 lowercase letters, numbers, or hyphens. It must start and end with a letter or number.
When you no longer need the claim, release the domain:
lpm tunnel unclaim acme-api.lpm.llc
lpm tunnel unclaim staging.lpm.fyi --org acmeThe release is immediate and does not stop for confirmation.
Plans and limits
| Tunnel type | Public domain | Session limit | Concurrent tunnels | Stable claims | Tunnel auth |
|---|---|---|---|---|---|
| Free personal | Random lpm.fyi | 1 hour | 1 | 0 | No |
| Pro personal | Account or claimed domain | No fixed limit | 3 | 3 | Yes |
| Org | Claimed organization domain | No fixed limit | 10 | 10 | Yes |
The startup output shows the current plan, expiry, active limits, and request usage.
Request allowances and rates apply across the account, not to each tunnel. See Request usage and overage.
Capture history
LPM CLI stores captured requests, responses, and sessions in .lpm/inspector.db under the current directory.
Captured data can include request bodies, response bodies, cookies, authorization headers, and signature headers.
Add .lpm/inspector.db* to .gitignore so that Git does not track the database or its temporary files.
Run tunnel history commands from the same directory that started the tunnel. This keeps each project history separate.
Inspect captures
lpm tunnel inspect # show the newest 20 captures
lpm tunnel inspect --last 10
lpm tunnel inspect --detail 3 # show request and response details
lpm tunnel inspect --filter stripe # filter by detected provider
lpm tunnel inspect --status 4xx # filter by HTTP status class
lpm tunnel inspect --status 404 # filter by exact statusThe status filter accepts 2xx, 3xx, 4xx, 5xx, error, err, or an HTTP status from 100 through 599.
Open the browser inspector
lpm tunnel inspect --ui
lpm tunnel inspect --ui --inspect-port 4500This action opens the history for the current directory. Press Ctrl+C to stop the inspector.
When a tunnel is active, press o in its terminal to open the same inspector.
Browse or clear the log
lpm tunnel log # show the newest 50 captures
lpm tunnel log --last 10
lpm tunnel log --filter github
lpm tunnel log --status 5xx
lpm tunnel log --clearCAUTION:
lpm tunnel log --cleardeletes captured requests and completed sessions for the current directory.
An active session remains available for new captures after a clear operation.
JSON and scripts
Use --json with local history actions or domain management:
lpm tunnel list --json
lpm tunnel inspect --last 10 --json
lpm tunnel replay --last --port 3000 --jsonLocal history actions work in non-interactive scripts without a login.
A remote tunnel still requires a saved interactive login session. The tunnel process also remains open until you stop it.
With --json, tunnel startup emits a JSON object after each successful connection. A temporary connection failure emits a retry object before the delay:
{
"schema_version": 1,
"success": false,
"event": "retry",
"error_code": "tunnel_retry",
"error": "disconnected, retrying in 2s... (relay unavailable)",
"retrying": true
}Read this output as a sequence of JSON objects. A retry event does not mean that the process exited. After a permanent failure or exhausted retries, the command emits its final error and exits with a nonzero status.
Flags
| Flag | Applies to | Effect |
|---|---|---|
--org <SLUG> | claim, unclaim, list | Manage domains for an organization. |
--tunnel-auth | Start | Require the generated access token. Pro and Org only. |
--auto-ack | Start | Provide a 200 OK fallback for an unavailable local service. |
--session <NAME> | Start | Name the capture session. |
--no-inspect | Start | Do not start the browser inspector. Capture continues. |
--inspect-port <PORT> | Start, inspect --ui | Bind the inspector to an exact port. |
--ui | inspect | Open the browser inspector. |
--last <N>, -n <N> | inspect, log | Show the newest N captures. |
--last, -n | replay | Replay the newest capture. |
--detail <N>, -d <N> | inspect | Show one capture by its 1-based list number. |
--filter <PROVIDER> | inspect, log | Show captures from one detected provider. |
--status <STATUS> | inspect, log | Show captures with one status or status class. |
--clear | log | Delete capture history for the current directory. |
--port <PORT>, -p <PORT> | replay | Send the replay to this local port. |
--json | All actions | Write machine-readable output. This is a global flag. |
See also
lpm dev --tunnel— start a tunnel as part of the dev pipeline- Tunneling — learn about domains, plans, request usage, and tunnel behavior
lpm cert— use trusted HTTPS for local development without a public tunnel