Tunnel Guides
Give a Local App a Public HTTPS URL
Share an app running on your machine at a public HTTPS address with a trusted certificate. Open it on a phone, send it to a client, and control who gets in.
Summary
https://acme-storefront.eu.localport.dev. The address stays the same every time you start it, the certificate is publicly trusted, and you decide who can open it. Nothing on your router or firewall changes.What you will have at the end#
A web app running on your laptop answers at a public HTTPS address. A client opens it on their phone, behind a password you set. The address is yours to keep, so the link you send today still works next week.
This guide uses a Vite app on port 5173 as the running example. Any web server works the same way: a Next.js, Django, Rails, Laravel or Go app follows the same steps with its own port.
Before you start#
You need three things:
- A Localport account. Create one at dashboard.localport.io.
- The Localport agent on the machine that runs your app. Follow Installation, then run
localport versionto confirm it. - Your app running locally. For a Vite project that is
npm run dev, which serves onhttp://localhost:5173.
How a public URL reaches your app#
The Localport agent opens outbound connections from your machine to Localport on port 443. A visitor's request arrives at your public address, travels down that connection, and the agent delivers it to localhost:5173. Your network never accepts an inbound connection, so there is no port to forward and no firewall rule to add.
Localport serves the address over HTTPS with a publicly trusted certificate. Your app keeps serving plain HTTP on localhost. It needs no code change and no library.
Set up your public URL#
Step 1: Create a tunnel#
A tunnel is the address and the settings that go with it. It lives in your account until you delete it.
Open dashboard.localport.io, go to Tunnels and click Create tunnel. Enter a Name, such as storefront. The name is a label for you and is not part of the address.
Choose a Region: EU, US or Asia Pacific. Pick the one closest to the people who will open the link. Leave Delivery on Standard, so one machine receives every request sent to the address. Leave Force HTTPS on, so plain HTTP requests are redirected to HTTPS. Then click Create Tunnel.
The name, the delivery mode and Force HTTPS can be changed later in the tunnel's settings. Changing the region needs the agent stopped first, and it changes the region part of the address. Detailed guide: HTTP Tunnels.
Step 2: Copy the command#
Creating the tunnel opens the Connect step. Later, while the tunnel is not running, its row in the Tunnels list shows a Connect button that reopens this step.
Under Preset, select Vite. The protocol becomes http and the port becomes 5173. Leave Local Address on localhost and copy the command.
localport http 5173 --token tok_k8f2 --region eu
http is the protocol, 5173 is the port your app listens on, --token identifies the tunnel, and --region matches the region you chose. The token is a credential. Treat it like a password.
Step 3: Start the tunnel#
Paste the command into a terminal on the same machine as your app and press Enter.
localport http 5173 --token tok_k8f2 --region eu
The terminal turns into a live status panel. Forwarding is your public address. Local is where requests are delivered. Each request appears as it arrives, with its time, method, path, status and duration.
The address answers for as long as the command runs. Press Ctrl+C to stop it. Run the same command again and the same address returns.
Step 4: Allow the address in your dev server#
Open the Forwarding address in a browser. A Vite dev server answers with an error saying the host is not allowed.
The error comes from Vite. Localport delivers each request with its original Host header, acme-storefront.eu.localport.dev. Vite answers only for localhost and IP addresses until you add another hostname. This check protects a dev server from DNS rebinding attacks, so keep it on and add your address to it.
Add the exact hostname to vite.config.js:
export default {
server: {
allowedHosts: ['acme-storefront.eu.localport.dev'],
},
}
Restart the dev server and reload the page.
Add your own hostname, never the whole zone
eu.localport.dev. An entry of .eu.localport.dev allows all of them. List your exact hostname, or your own domain once you attach one.Other dev servers run the same check under a different setting. Each one takes the hostname with no https:// and no port.
| Framework | Setting | Where |
|---|---|---|
| Vite, SvelteKit, Astro, Nuxt | server.allowedHosts | vite.config.js, or the framework's vite block |
| Next.js | allowedDevOrigins | next.config.js |
| webpack-dev-server | devServer.allowedHosts | webpack.config.js |
| Django | ALLOWED_HOSTS | settings.py |
| Rails | config.hosts | config/environments/development.rb |
A Django form that posts data also needs the full origin, with its scheme, in CSRF_TRUSTED_ORIGINS: https://acme-storefront.eu.localport.dev.
Every header your app receives is listed in HTTP Tunnels.
Step 5: Open it on a phone#
Turn off Wi-Fi on your phone and open the address over mobile data. The request now crosses the public internet, the way a client's request does.
The address is HTTPS with a trusted certificate. Browsers make the camera, geolocation and service workers available only to HTTPS pages and to localhost. A phone that opens your laptop's local network address over plain HTTP gets none of them. On the tunnel address they work as they do in production.
WebSocket connections pass through the tunnel, and responses are sent as your app writes them. Live reload, chat and streamed responses behave as they do locally.
Step 6: Choose your subdomain#
Every tunnel starts with a generated name. Set your own before you send the link anywhere, so the address carries a name the reader recognises.
Open the tunnel and go to Settings. Enter a name in Subdomain. The dashboard checks availability as you type. Save the change.
A subdomain is 12 characters or more. It is unique across every region. Update the Vite allowedHosts entry to the new name.
Step 7: Control who can open it#
A tunnel address is public. Anyone holding it can reach your app while the agent runs. Before you send it to a client, put a control in front of it. Each control applies to every request and takes effect on a running tunnel.
For a client review, use a password. In the tunnel's Settings, turn on Basic Authentication, enter a Username and a Password, and save. The browser asks for both before your app loads. Send the password to the client separately from the link. Detailed guide: Basic Auth.
For an office or a known network, use an IP allowlist. Enter the addresses or CIDR ranges in Inbound IP Whitelist, comma separated, and save. Requests from any other address are refused before they reach your app. Detailed guide: IP Allowlists.
The two combine. With both set, a visitor needs an allowed address and the password.
If your app already asks for a login, check Conflicts with application-level authentication before you turn on Basic Authentication.
Step 8: Serve it on your own domain#
A domain you own can serve the tunnel as well, for example preview.acme.com. The localport.dev address keeps answering, and the controls from Step 7 apply to both. You add the domain once, create one DNS record at your DNS provider, and attach the domain to the tunnel. Localport obtains and renews the certificate for it.
Add the new hostname to allowedHosts as well. Detailed guide: Custom Domains.
Step 9: Start it the same way every time#
A config file holds the tunnel's setup, so you start it with one short command. The file refers to the token by a variable name, so the token stays out of the file and out of the process list.
Create localport.yaml next to your project:
version: 1
tunnels:
- name: storefront
token: ${LOCALPORT_TOKEN_STOREFRONT}
upstream: http://localhost:5173
Provide the token through the environment, from your secret manager, a shell profile only your user can read, or a service unit. Then start every tunnel in the file:
localport connect --config localport.yaml
upstream takes the protocol and the port, and the port is required. ${LOCALPORT_TOKEN_STOREFRONT} is read from the environment, and the agent stops with an error when it is unset. Add more entries under tunnels to run an API and a frontend side by side, each with its own token. Detailed guide: Configuration.
To keep a tunnel running on a server after you log out, run the agent as a service. The agent ships a hardened systemd unit that reads the token from a file only the service can read. Detailed guide: Installation.
Who can open your URL#
- Anyone with the address, until you add a control. Basic Authentication, the IP allowlist, Header Authentication and Webhook Verification each narrow it. They are checked on every request.
- Only your token can serve the address. An agent needs the tunnel's token to connect. Keep the token out of shared command lines: on a machine other people use, set
LOCALPORT_TOKENor pointLOCALPORT_TOKEN_FILEat a file only your user can read. See Environment Variables. - A leaked token can be replaced. Rotate next to the Token field in the tunnel's settings issues a new token, and the old one stops authenticating at once. The address stays the same.
- A token can be tied to your networks. Network IPs in the tunnel's settings limits where the tunnel can start from. An agent elsewhere is refused, even with the right token. See IP Allowlists.
- Traffic content is not logged. Localport records connection metadata, such as the visitor's address and the bytes transferred. It does not record request or response bodies. See Security.
- HTTPS ends at Localport. Localport holds the certificate for your address and forwards each request to your app over the agent's encrypted connection. Your app receives plain HTTP on localhost.
Limitations#
- One agent serves a Standard tunnel at a time. A second agent started with the same token replaces the first. To deliver one address to several machines, use a Fanout Tunnel.
- HTTP tunnels reach your app over HTTP/1.1. gRPC needs HTTP/2 end to end, so it needs a TCP or TLS tunnel.
- Tunnels carry HTTP, TCP and TLS. UDP is not carried.
- The address answers only while the agent runs. When the agent stops, the address closes each connection and browsers report that the site cannot be reached. Nothing about the tunnel is deleted.
Frequently asked questions#
How do I expose localhost to the internet?
Run the Localport agent next to your app with the command the dashboard gives you, for example localport http 5173 --token tok_k8f2 --region eu. Your app gets a public HTTPS address with a trusted certificate. No router change, port forwarding or deployment is needed, because the agent connects outward on port 443.
Why does my dev server say the host is not allowed?
Your dev server checks the Host header and refuses names it does not know. Localport forwards the original host, so the server sees your tunnel's hostname. Add that exact hostname to the server's allowed-hosts setting: server.allowedHosts in Vite, allowedDevOrigins in Next.js, ALLOWED_HOSTS in Django, config.hosts in Rails.
Does my public URL change when I restart?
No. The address belongs to the tunnel, not to the session. It stays the same through restarts, reboots and network changes until you change the subdomain or the region.
Is my local app public once the tunnel is running?
Yes, until you add a control. Anyone with the address can reach the app while the agent runs. Turn on Basic Authentication or an IP allowlist in the tunnel's settings to limit who gets in. Both apply to a running tunnel without a restart.
Does it work behind CGNAT, a corporate firewall or a phone hotspot?
Yes, in most networks. The agent makes outbound connections on port 443, the same kind a browser makes. No inbound connection reaches your network, so carrier-grade NAT does not block it. A firewall that blocks outbound port 443 or intercepts TLS can still stop the agent.
Do WebSockets and hot reload work through the tunnel?
Yes. WebSocket upgrades pass through, and responses are sent to the visitor as your app writes them. Live reload, chat and server-sent events behave as they do on localhost.
Can two developers run the same tunnel at once?
No. A Standard tunnel has one active agent, and a second agent with the same token replaces the first. Give each developer their own tunnel, or change the tunnel's delivery mode to Fanout so one address reaches every connected machine.
What do I do if my token leaks?
Open the tunnel's Settings, click Rotate next to the Token field and confirm with Rotate Token. Localport issues a new token, and the old one stops authenticating at once. Your address and settings stay the same. Restart the agent with the new token wherever you run it.
Can Localport see my traffic?
Localport terminates HTTPS for your address and forwards each request to your app. Traffic content is not logged. Localport records connection metadata such as the visitor's address and the bytes transferred. The agent that runs on your machine is open source, so you can read what it does.
What to read next#
- HTTP Tunnels. Every setting and header for a web tunnel.
- Basic Auth. Password-protect an address.
- Custom Domains. Serve a tunnel on a domain you own.
- Test Stripe, GitHub and Slack Webhooks Locally. Receive real provider events on your machine.
- Configuration. Run several tunnels from one file.
- CLI. Every command and flag.