Tunneling
How public tunnel URLs, stable domains, request limits, access protection, and local capture work.
lpm tunnel connects a public HTTPS URL to one HTTP service on your computer. LPM CLI opens the connection from your computer.
Use a tunnel for webhooks, callbacks, remote-device tests, and temporary previews. A tunnel is a development tool, not permanent application hosting.
How a tunnel works
public client
│ HTTPS or WebSocket
▼
LPM.dev Registry tunnel service
│ open tunnel connection
▼
LPM CLI
│ local HTTP
▼
your local servicePublic visitors use HTTPS. LPM CLI forwards each request to the local service and returns the local response.
Public WebSocket connections use the same route. You do not need to open an inbound port on your router or firewall.
The tunnel lasts while the LPM CLI process remains open. When the process stops, the public URL stops forwarding requests.
Choose a workflow
| Goal | Workflow |
|---|---|
| Use the same domain for a project | Add tunnel.domain to lpm.json, then run lpm dev |
| Get an assigned domain for one development run | Run lpm dev --tunnel |
| Expose an existing local port | Run lpm tunnel <port> |
Expose the only active lpm dev endpoint | Run lpm tunnel |
| Use a claimed domain for one development run | Run lpm dev --domain <domain> |
| Use a claimed domain with a standalone tunnel | Run lpm tunnel <port> <domain> |
The lpm tunnel command page contains the complete syntax, recipes, actions, and flags.
Project configuration
Use lpm.json for a stable project workflow:
{
"$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. The tunnel object currently accepts only domain.
LPM CLI waits for the final local endpoint before it opens the tunnel. This behavior supports development servers that change their initial port.
Use a different claimed domain for one run:
lpm dev --domain preview-api.lpm.llcDisable the project tunnel for one run:
lpm dev --no-tunnelIf lpm.json contains multiple services, mark exactly one service as primary. LPM CLI sends tunnel traffic to the final endpoint of that service.
Domain types
The LPM.dev Registry provides two tunnel base domains:
| Base domain | Availability | Use |
|---|---|---|
lpm.fyi | Free, Pro, and Org | Free random domains and stable claims |
lpm.llc | Pro and Org | Stable claims |
Run lpm tunnel domains to list the base domains that are currently enabled.
Free random domains
A Free tunnel receives a random 10-character subdomain on lpm.fyi. The subdomain contains lowercase letters and numbers.
The domain lasts for one tunnel session. A new connection receives a new random domain.
Pro personal domains
A Pro personal tunnel without an explicit domain uses <username>.lpm.fyi. This domain remains the same while the username remains the same.
A Pro user can also claim up to three stable domains. Claims can use lpm.fyi, lpm.llc, or both.
Organization domains
An Org account can claim up to ten stable domains. The claim belongs to the organization, not to the member who uses it.
Organization owners and administrators can manage organization claims. Any eligible member can connect with a claimed organization domain.
Claim a stable domain
Claim a personal domain:
lpm tunnel claim acme-api.lpm.llcClaim an organization domain:
lpm tunnel claim staging.lpm.fyi --org acmeA claimed subdomain must contain from 3 through 32 lowercase letters, numbers, or hyphens. It must start and end with a letter or number.
Claims are separate for each base domain. For example, acme.lpm.fyi and acme.lpm.llc use two claim slots.
A reconnect to the same claimed domain replaces the old session slot. This replacement does not temporarily use an additional concurrent-tunnel slot.
See the stable-domain commands for listing, use, and release examples.
Plans and limits
Tunnel limits apply to the selected personal or organization account.
| Limit | Free | Pro | Org |
|---|---|---|---|
| Concurrent tunnels | 1 | 3 | 10 |
| Included requests | 20,000 per UTC month | 100,000 per billing period | 100,000 per billed seat and billing period |
| Account request rate | 4,000 per minute | 20,000 per minute | 20,000 per billed seat and minute |
| Per visitor IP | 600 per minute | 600 per minute | 600 per minute |
| Maximum request body | 10 MB | 100 MB | 100 MB |
| Maximum session length | 1 hour | No fixed limit | No fixed limit |
| Stable domain claims | 0 | 3 | 10 |
--tunnel-auth | No | Yes | Yes |
| After the included allowance | Hard stop | Proportional overage | Proportional overage |
The Org concurrent-tunnel and domain-claim limits do not increase with the seat count.
The startup output reports the active plan, session expiry, limits, request usage, and overage state.
Request usage
The included allowance belongs to the account, not to each tunnel. Multiple tunnels consume the same account allowance.
One accepted HTTP request counts once. If the local service returns an error or is unavailable, the request still counts.
A successful public WebSocket upgrade also counts once.
Messages on an open WebSocket do not consume more tunnel requests. Rejected authentication, interstitial pages, and rejected oversized requests do not count.
Free usage resets at the start of each UTC month. A Free tunnel stops accepting requests after the account uses its included allowance.
Pro and Org usage follows the billing period. Paid overage costs $1 for each 100,000 requests and uses proportional billing.
Complimentary Pro accounts without a subscription receive the Pro allowance each UTC calendar month. These accounts retain Pro domain and connection limits. They stop accepting requests at the included allowance because paid overage requires a subscription.
Paid overage is enabled by default. You can disable it in the personal or organization tunnel dashboard.
If paid overage is disabled, the tunnel stops accepting requests at the included allowance. A change to overage affects only future requests.
Authentication and access protection
Tunnel startup and domain actions require a saved lpm login session. --token, LPM_TOKEN, and CI-only tokens cannot replace this session.
The local inspect, replay, and log actions do not require a login. These actions use the capture database in the current project.
Public access
A tunnel URL accepts public requests by default. Application authentication remains the responsibility of your local service.
Pro and Org tunnels can add tunnel-level access protection:
lpm tunnel 3000 acme-api.lpm.llc --tunnel-authLPM CLI creates a token for the session. The startup output shows an X-Tunnel-Auth header and a browser URL that contains the token.
Treat the header token and browser URL as secrets. The tunnel service rejects requests that do not contain the correct token.
Free browser interstitial
The first browser visit to a Free random domain shows a Visit Site page. This page reduces phishing abuse on temporary domains.
The page appears only for requests that accept HTML. API clients and webhook providers reach the local service without this page.
After the visitor continues, the browser receives a host-only cookie for that domain and session. The cookie expires with the session.
The interstitial is not application authentication. It does not restrict API or webhook access.
Local HTTP and public HTTPS
An explicit port always selects http://127.0.0.1:<port>/. The public tunnel URL still uses HTTPS.
Without a port, lpm tunnel 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.
lpm dev --https remains compatible with tunnels. LPM CLI tunnels the underlying local HTTP endpoint while it provides trusted browser HTTPS locally.
Do not give a standalone tunnel a framework HTTPS port. Use a plain HTTP child service for tunnel forwarding.
Server-sent events
Responses with Content-Type: text/event-stream reach visitors as the local service sends data.
The tunnel waits up to 30 seconds for response headers. An open event stream can continue beyond 30 seconds.
Slow readers limit forwarding, and a visitor disconnect closes the local request.
Tunnel revocation and the Free-session deadline still close active streams.
This behavior requires a relay that supports streamed responses.
Request capture and privacy
LPM CLI captures each forwarded request and local response in the current project:
<project>/.lpm/inspector.dbThe database can contain full headers, request bodies, response bodies, and session details. The tunnel service does not store these request bodies.
Protect the database as sensitive development data. Do not commit it to source control.
Live capture memory has fixed limits. The request buffer retains up to 1,000 requests or 64 MiB.
The WebSocket buffer retains up to 5,000 events or 32 MiB.
Each captured WebSocket frame keeps a 64 KiB preview and records the original frame size. The inspector marks a partial preview as truncated.
For a completed event stream, the inspector retains up to 64 KiB of response data and marks a partial capture. An interrupted stream may have no completed capture.
The database writer queues up to 256 captures and 64 MiB. If either limit is full, LPM CLI drops new captures.
Session start and end records use a separate reliable path.
Slow browser streams can miss live events. The browser then reloads persisted history through the inspector API.
The browser inspector starts with a tunnel and selects an available local port. --no-inspect disables the browser inspector, but capture continues.
Use the same capture history from the terminal or browser:
lpm tunnel inspect --last 10
lpm tunnel inspect --ui
lpm tunnel replay --last --port 3000
lpm tunnel log --status 5xxReplay sends the saved method, path, headers, and body to the selected local service. This feature helps you repeat a failed webhook during development.
Session names group related captures:
lpm tunnel 3000 --session stripe-testlpm tunnel log --clear clears current capture history and completed sessions for the project. It does not remove preserved legacy capture files.
Auto-acknowledge webhooks
If a webhook endpoint must accept requests while the local service is unavailable, use --auto-ack:
lpm tunnel 3000 --auto-ackLPM CLI returns 200 OK and saves the request for later replay.
CAUTION: The provider records the request as successful, but your local service did not process it.
Reliability and recovery
A missed relay heartbeat triggers reconnection. LPM CLI reports the retry instead of exiting successfully. Credential renewal keeps a healthy tunnel connected without resetting the Free one-hour limit. At that limit, the tunnel stops and asks you to start a new session.
| Symptom | Cause | Recovery |
|---|---|---|
| The public URL stops | The LPM CLI process stopped | Start the tunnel again |
| A Free URL stops after one hour | The Free session expired | Start a new session and use the new random URL |
| A request cannot reach the local service | The service is stopped or uses another port | Start the service or select the correct port |
Bare lpm tunnel cannot select an endpoint | No active endpoint exists, or multiple endpoints exist | Pass the intended port |
| The inspector port is busy | An explicit --inspect-port is already in use | Select another port or omit the flag |
| Tunnel authentication fails | The saved login session is unavailable or expired | Run lpm login, then start the tunnel again |
| The relay certificate pin does not match | The relay certificate changed or the connection is unsafe | Make sure that the certificate change is expected before you replace the pin |
Relay certificate pins
LPM CLI stores one certificate pin for each relay host:
~/.lpm/relay-pins/relay.lpm.fyi
~/.lpm/relay-pins/<other-host>The first connection stores the pin. Later connections to the same host must present the same certificate identity.
CAUTION: Do not remove a pin until you make sure that the relay certificate changed as expected. Removing it trusts the next certificate.
If the certificate change is expected, remove the exact pin that the error names. Then connect again:
rm ~/.lpm/relay-pins/relay.lpm.fyi
lpm tunnel 3000Advanced relay configuration
The default relay URL is wss://relay.lpm.fyi/connect. Most users do not need to change it.
Use an environment variable for one process:
LPM_TUNNEL_RELAY=wss://relay-eu.lpm.fyi/connect lpm tunnel 3000Use ~/.lpm/config.toml for a persistent override:
[tunnel]
relay-url = "wss://relay-eu.lpm.fyi/connect"The precedence is LPM_TUNNEL_RELAY, ~/.lpm/config.toml, then the built-in default. An empty value moves to the next source.
Use only a trusted relay endpoint. A relay receives the public traffic that LPM CLI forwards to your local service.
Each relay host has a separate certificate pin. A relay override does not reuse the pin for another host.
CI and long-running services
A tunnel needs an open process to keep the connection active. This model does not fit a typical CI job or permanent service.
For automated webhook tests, use provider delivery logs or a deployed test endpoint. Use LPM CLI tunnels for interactive development.
See also
lpm tunnel- command syntax, recipes, domain actions, capture commands, and flagslpm dev- start a project and its tunnel togetherlpm.jsontunnel configuration - configure a stable project domain- Authentication - understand the saved session requirement
lpm cert- use trusted HTTPS without a public tunnel