Learn
How do you share a local site with a client?
Run a reverse tunnel client on the machine the site is running on. It opens an outbound connection to a public server, which gives you an HTTPS URL and forwards every request to your local port. Send the client that link; they open it in any browser and see exactly what you are running.
How do you share a local site with someone else?
Three things have to be true: the site has to be running, something has to give it a public address, and the client has to be able to reach that address from their own network. Your machine is almost certainly behind NAT, so nothing on the internet can open a connection to it, which is why handing out your IP address does not work. A reverse tunnel solves that from the inside, because your machine dials out and the tunnel server answers on the public side on your behalf.
Nothing about how your app runs changes. It keeps listening on localhost, and requests arrive down the connection you already opened.
Why not just deploy to a staging server?
Staging is the right answer for anything that has to stay up without you sitting at the machine. It is the wrong answer for a ten minute review of a branch you are still editing, because every look costs a build and a deploy, and the client is always seeing a snapshot rather than the current state. Staging also brings its own hosting bill, secrets, database and access story, which is a lot of work to carry for one round of feedback.
The useful split is simple. Share the running process while the work is in flux, and deploy once it has stopped changing every few minutes.
What about screenshots or a screen share?
Screenshots answer whether it looks right. They do not answer whether it feels right, which is usually the question a client is really asking, and they cannot be opened on the client's own phone or their own slow connection. A screen share is closer, but it is synchronous: you both have to be free at the same time, the client cannot click anything themselves, and when the call ends there is nothing left to go back to.
A link the client can open on their own time gets you the feedback you would not otherwise hear, usually the day after the call.
| Deploy to staging | Screen share | Tunnel to localhost | |
|---|---|---|---|
| Setup before the first look | Build, host, DNS, TLS | A calendar invite | One command |
| Shows the code you are editing now | Only after a deploy | Yes | Yes |
| Client can click around alone | Yes | No | Yes |
| Works on the client's phone | Yes | Awkward at best | Yes |
| Still there tomorrow | Yes | No | Only while you are connected |
Does the client have to install anything?
No. What you are sending is an ordinary HTTPS URL, so it opens in any browser, on any network, including their phone on cellular data. There is no client for them to install, no VPN to join and no allowlist to be added to, which matters more than it sounds: most clients cannot install software on a work laptop even if they want to.
That is also the difference between a tunnel and a VPN. A VPN puts the client inside your network; a tunnel puts one port of yours on the public internet and leaves everything else alone.
How do you stop the link leaking?
Treat the link as the credential, because that is what it is. A random tunnel hostname is impractical to guess, but it is not a secret: a forwarded email, a shared Slack channel or a synced browser history is enough for it to reach someone you did not send it to. So put a real gate in front of anything that matters, and keep production data out of the database you are serving from.
Then end the session when the review is over. While no client is connected there is nothing behind the URL, which makes stopping the tunnel the fastest revocation you have.
Gate it in the app
A basic auth check in your dev server, or the login your app already has. A few lines of middleware is enough for a review link.
Keep crawlers out
Send an X-Robots-Tag: noindex header from your local server. Nothing should be linking to the URL, but shared links end up in odd places.
Stop it when you are done
Closing the tunnel takes the site off the internet immediately. There is no cache to clear and no deploy to roll back.
What happens when you close your laptop?
The share goes with it. Your server stops when the machine sleeps and the tunnel connection drops, so the link fails until you are awake and reconnected. A client who tries the link at nine in the evening does not conclude that your laptop is shut; they conclude the work is broken.
There is a second problem on random URLs. Once a hostname is released it can be handed to a different connection later, so an old link may eventually resolve to a tunnel that is not yours. If the client is going to keep the link past today, share a name you actually hold; that is what a reserved subdomain is for.
Related reading
How to expose localhost to the internet
The mechanics underneath this page, with the four ways to do it compared honestly: a tunnel, SSH forwarding, port forwarding on your router, and deploying.
What is a reserved subdomain?
Why a URL that changes on every restart is fine for a quick look and wrong for a link a client has bookmarked for a month.
More of these in Learn.
How does tunnels.io share a local site?
Install the client, run tunnels http 3000 against whatever port your site is on, and send the client the HTTPS URL it prints. TLS is terminated at the edge, so there is no certificate for you to issue and nothing for the client to click past: no plan, including the free one, puts an interstitial or warning page in front of your site.
The free Basic plan is $0, with 4 GB of transfer a month, one tunnel and a random URL that changes on every connection, which is enough for a same-day look. Pro is $4 a month or $40 a year and adds up to three reserved subdomains, so a client can keep one address for the length of a project, along with 128 GB, five tunnels and TCP. Gold at $10 a month brings your own custom domains, which is the version a client reads back to you without asking what it is.
Questions
Can I share localhost with a client without deploying anything?
Yes. A reverse tunnel gives your running local server a public HTTPS URL without a build, a host, a DNS record or a certificate. The site the client sees is the process on your machine, so a save and a refresh is the whole deployment pipeline.
Is it safe to send a client a tunnel URL?
It is safe enough for work in progress if you treat the link as public. Anyone who ends up with the URL can open it, so put a login or a basic auth prompt in front of anything sensitive and keep real customer data out of the database you are serving. Stopping the tunnel takes the site offline instantly, which is the cleanest way to end access.
Will the link still work after I close my laptop?
No. Nothing is behind the URL while your machine is asleep, so requests fail until the client reconnects. On a plan that issues a random URL you also get a different address on the next connection, so a link shared yesterday will not come back; a reserved subdomain returns the same hostname every time.
Can the client open the link on their phone?
Yes, it is a normal public HTTPS URL, so it works on a phone on cellular data, on a tablet, or on a machine in another country. That makes it the fastest way to check a responsive layout on hardware you do not own. Nothing has to be installed on the device.
How much does it cost to share a local site this way?
Nothing to start. The free Basic plan is $0 and gives you 4 GB of transfer a month, one tunnel and a random URL, with no interstitial page in front of your site. Pro at $4 a month or $40 a year is where reserved subdomains, 128 GB and five concurrent tunnels start, which is the tier that makes sense once clients are bookmarking links.