Tunnel Localhost Without Port Forwarding
You built something on localhost:8000 and now someone else needs to see it. A client, a teammate, a webhook from Stripe that refuses to POST to your laptop.
The old answer was port forwarding. Log into your router, punch a hole in NAT, hope your ISP did not put you behind CGNAT, then discover your public IP rotates every few days anyway. The other answer was ngrok, which works right up until the free tier's URL changes on every restart and the session limit starts nagging you.
There is a third option that costs nothing, survives restarts, and runs on your own domain: Cloudflare Tunnel.
Why This Works
Port forwarding fails so often because it requires the outside world to initiate a connection into your network. Your router blocks that by default, and if your ISP uses carrier-grade NAT, you do not even own a routable address to forward to.
Cloudflare Tunnel inverts the direction. A small daemon on your machine, cloudflared, opens an outbound connection to Cloudflare's edge and holds it open. Traffic for your domain hits Cloudflare first, then rides back down that existing connection to your local server.
+------------------------------------------------------------------+ | THE DIRECTION FLIP | +------------------------------------------------------------------+ | Port forwarding : Internet --> Router --> ??? (blocked by NAT) | | Cloudflare Tunnel: Your PC --> Cloudflare (outbound, fine) | | Visitor --> Cloudflare --> down the tunnel | +------------------------------------------------------------------+
Outbound connections are exactly what every firewall already allows, which is why this needs zero router configuration. Your machine's real IP is never exposed either, since Cloudflare's edge is the only address the public ever resolves.
You need two things: a Cloudflare account and a domain. If you do not have a domain, skip to the no-domain method at the end, which needs neither.
Step 1: Add Your Domain
Create an account at Cloudflare, then open the dashboard and go to Domains in the left panel.
Click Add a Domain in the top right corner.
Choose Connect a Domain, since you already own one and are only moving its DNS.
Enter your domain name and leave the rest of the settings at their defaults.
Pick the Free plan. Everything in this guide works on it. Cloudflare then scans your existing DNS records and pre-fills them, so review the list and click continue.
Step 2: Swap the Nameservers
Cloudflare now hands you two nameservers. Copy both somewhere safe, you are about to paste them into your registrar.
Log into wherever you bought the domain. The screenshots below use Hostinger, but the flow is nearly identical on Namecheap, GoDaddy, or Google Domains. Open your domain list and click Manage.
Find the DNS / Nameservers section.
Under DNS records, click Change Nameservers.
Select the option to use custom nameservers and clear out the registrar's defaults.
Paste in the two Cloudflare nameservers you copied a moment ago. It should look like this once both fields are filled.
Save.
The registrar warns that propagation takes up to 24 hours. In practice it is usually 5 to 10 minutes, occasionally a couple of hours. Go make coffee.
Back on Cloudflare, click I've updated my nameservers, then Check nameservers now to poll for the change.
When you see "Your domain is now protected by Cloudflare", DNS is live and you can move on.
If it still says pending after an hour, run
nslookup -type=ns yourdomain.comto see which nameservers the world actually sees. Registrars sometimes silently keep a third legacy nameserver in the list.
Step 3: Install cloudflared
Download the binary for your OS from the Cloudflare downloads page or the GitHub releases. This guide uses Windows, where you get a single .exe with nothing to install.
The downloaded file is named something like cloudflared-windows-amd64.exe. Rename it to cloud.exe so you are not typing forty characters every command. This is purely cosmetic, skip it if you prefer.
Open a terminal in that same folder. On Windows, shift + right click in the folder gives you Open in Terminal.
Confirm it runs:
cloud.exe --version
A version number means you are good.
Step 4: Create the Tunnel
Log in. This opens a browser window.
cloud.exe tunnel login
Pick your domain from the list.
Click Authorize.
The browser confirms success and you can close it.
The terminal confirms it too, and writes a certificate to .cloudflared in your home folder.
Now create the tunnel itself:
cloud.exe tunnel create tunnel1
tunnel1 is just a label, name it whatever you like. The output includes a tunnel ID, a UUID. Copy it, you need it in the config file shortly.
Then point your domain at that tunnel:
cloud.exe tunnel route dns tunnel1 yourdomain.com
This creates a CNAME record in Cloudflare automatically. If the command errors out, it is almost always a conflicting A record that Cloudflare added during the import in step 1. Go to Domain → DNS, delete that A record only, and run the route command again.
Step 5: Run a Local Server
You need an actual local server to tunnel to. Any one will do, your Next.js dev server, a Flask app, a Node API. For a quick sanity check, make an index.html:
<h1>Hello world!</h1>
Serve it in a new terminal, leaving the first one free:
python -m http.server
Visit localhost:8000 to confirm it works locally before you tunnel it. Debugging one broken thing beats debugging two.
Step 6: Write the Config File
Navigate to C:\Users\{username}\.cloudflared\ (or ~/.cloudflared/ on macOS and Linux) and create a file named config.yml:
tunnel: {tunnel_id} credentials-file: C:\Users\{username}\.cloudflared\{tunnel_id}.json ingress: - hostname: {domain_name} service: http://localhost:8000 - service: http_status:404
Replace all three placeholders with your real values: the UUID from step 4, your Windows username, and your domain.
Two things worth understanding here, because this is the file people get wrong:
ingressrules are evaluated top to bottom. The first matching hostname wins.- The last rule must be a catch-all with no
hostnamekey.http_status:404is the standard choice. Leave it out andcloudflaredrefuses to start.
That structure is also how you route multiple services through a single tunnel:
ingress: - hostname: app.yourdomain.com service: http://localhost:3000 - hostname: api.yourdomain.com service: http://localhost:8080 - service: http_status:404
Each extra hostname needs its own tunnel route dns command before it resolves.
Step 7: Run It
cloud.exe tunnel run tunnel1
When the logs show registered connections, your localhost is live on your domain. Open it from your phone on mobile data to prove it is genuinely leaving your network, and note the HTTPS certificate you got for free without touching Let's Encrypt.
Keep this terminal open. Closing it closes the tunnel. On Windows you can install it as a service with cloud.exe service install so it survives reboots.
The No-Domain Version
If you just want a link to paste into Slack and do not care about the URL, skip every step above:
cloud.exe tunnel --url http://localhost:8000
That prints a random trycloudflare.com URL wired straight to your local port. No account, no domain, no config file.
The tradeoff is that the URL is random and dies with the process, so it is throwaway by design. Perfect for a quick demo, useless for a webhook you want to keep registered.
When Things Break
| Symptom | Cause | Fix |
|---|---|---|
failed to add route | An A record already claims the hostname | Delete the A record in Domain → DNS, rerun the route command |
| Domain stuck on "pending nameserver update" | Registrar kept a stale nameserver | Verify with nslookup -type=ns yourdomain.com, clear leftovers |
| Error 1033 in the browser | Tunnel is not running | Start cloud.exe tunnel run tunnel1 |
| Error 502 | Tunnel is up, local server is not | Confirm the port in config.yml matches your dev server |
cloudflared exits at startup | Missing catch-all ingress rule | Add - service: http_status:404 as the final entry |
The 1033 versus 502 split is the useful diagnostic. 1033 means Cloudflare cannot find your tunnel, so the problem is on the cloudflared side. 502 means the tunnel connected but your app did not answer, so the problem is your port number or a server that is not actually running.
One Security Note
This publishes your local machine to the entire internet, which is precisely the point and precisely the risk. A dev server bound to 0.0.0.0 with debug mode on is now globally reachable.
Before you leave a tunnel running overnight, put Cloudflare Access in front of it. It is free for up to 50 users and adds an email or SSO check ahead of your origin, so a stray /admin route does not become someone else's afternoon.
Summary
- Register on Cloudflare and open the dashboard
- Add your domain under Add a Domain → Connect a Domain
- Replace your registrar's nameservers with Cloudflare's
- Click I've updated my nameservers and wait for activation
- Download the
cloudflaredbinary cloud.exe tunnel logincloud.exe tunnel create tunnel1cloud.exe tunnel route dns tunnel1 yourdomain.com- Create
config.ymlin the.cloudflaredfolder cloud.exe tunnel run tunnel1
Ten steps, zero router settings, zero dollars. Your laptop is now a web server on a real domain with real HTTPS, and your ISP never had to cooperate.