Why nobody outside your network can reach localhost
A webhook provider needs a URL it can POST to, and your server is sitting on
localhost:3000. Or a teammate wants to click through the branch you’re
running, or your phone needs to hit the dev server to check a layout. The
server is up. Nothing outside your network can reach it.
Port forwarding on your router would work, but it exposes your home IP and
needs admin access you often don’t have. Cloudflare Tunnel takes the other
route. cloudflared, a small daemon you run next to your server, opens an
outbound connection to Cloudflare’s edge, and Cloudflare sends traffic back
through it. You open no inbound port and you don’t need a public IP.
It comes in two forms. A quick tunnel gives you a public HTTPS URL in one command, on a random hostname that dies when you stop the process. That’s the tool for a five-minute demo or a webhook test. A named tunnel gives you a stable hostname on your own domain that survives restarts, which makes it a small piece of real infrastructure.
How Cloudflare Tunnel works without an open port
The trick is a change of direction. Exposing a service normally means opening an inbound port: something on the internet starts a connection to your machine, so your machine needs a routable address and a firewall rule that lets the traffic in. Blocking exactly that is what NAT and most home and office firewalls are for.
cloudflared never listens for inbound connections. It dials out to
Cloudflare’s edge over ordinary outbound HTTPS, the same kind of connection
your browser opens all day, and keeps it open. Cloudflare then pushes requests
down that connection and reads the responses back, much like a server pushes
messages over a WebSocket to a browser that connected first. Your firewall
sees one outbound connection it already allows. That’s also why the tunnel
behaves the same behind NAT or on a laptop with no public IP: an outbound
connection doesn’t care.
The cost is that Cloudflare now sits in the path. Your tunnel hostname resolves to Cloudflare, which decrypts each request at its edge, then re-encrypts it and sends it down the tunnel to your daemon. That’s the same architecture as running behind a CDN or a reverse proxy: you give up direct access in exchange for a middlebox that terminates TLS and can apply access rules before anything reaches your machine.
How to create a quick tunnel with one command
Install cloudflared first: brew install cloudflared on macOS, the
.deb/.rpm package from Cloudflare’s repository on Linux, or the Windows
installer from the GitHub releases page. Then, with something already
listening on a local port:
| |
A few seconds later you get output like:
| |
That URL serves HTTPS with a certificate Cloudflare manages, so you never
touch TLS configuration. Give it a few seconds to start resolving. From then
on, anyone with the link reaches your localhost:3000 until you press Ctrl-C.
The hostname is then gone for good, and the next run generates a new one. You
don’t log in, create a DNS record, or need an account.
The limits are real, though. The hostname is random, so you can’t put it in a webhook config that has to survive a restart. Cloudflare caps quick tunnels at 200 concurrent requests and doesn’t support Server-Sent Events on them, so a long-lived SSE stream will drop. There’s also no uptime guarantee: Cloudflare’s terms treat quick tunnels as a testing convenience.
For debugging a webhook right now or letting your phone hit the dev server, that’s all you need. If it has to still be running tomorrow, set up a named tunnel.
How to set up a named tunnel on your own domain
A named tunnel needs a free Cloudflare account and a domain whose DNS is on Cloudflare. You do the setup once.
1. Authenticate cloudflared against your account:
| |
This opens a browser and asks you to pick the domain to authorize. It then
saves a certificate at ~/.cloudflared/cert.pem, which every later command
uses.
2. Create the tunnel and note its ID:
| |
This registers a tunnel named my-app and writes a credentials file,
<TUNNEL-ID>.json, to ~/.cloudflared/. That file is the tunnel’s identity.
Treat it like a secret: anyone holding it can run traffic through your tunnel.
3. Write the config. Create ~/.cloudflared/config.yml:
| |
ingress is an ordered list of hostname-to-service rules, matched top to
bottom. If you’ve configured nginx as a
reverse proxy in front of your apps,
it’s the same routing idea written in YAML instead of proxy_pass directives.
One tunnel can front several local services on different hostnames. The last
rule is mandatory. It’s the catch-all that returns 404 for any hostname the
rules above didn’t match, and cloudflared refuses to start without it.
4. Point DNS at the tunnel:
| |
This creates a CNAME record for app.example.com pointing at
<TUNNEL-ID>.cfargotunnel.com. It’s ordinary
DNS resolution, aimed at
Cloudflare’s tunnel infrastructure instead of a server IP. Run it once for
every hostname in your ingress list.
5. Run it:
| |
app.example.com now serves whatever listens on localhost:3000, over HTTPS.
Cloudflare terminates TLS at the edge, and your origin can stay plain HTTP
because the tunnel connection is already encrypted.
A tunnel running in a terminal dies with the terminal. To have it survive a reboot, install it as a system service:
| |
On Linux, run it with sudo, so $HOME becomes /root and cloudflared can
no longer find a config.yml you created as your own user. Point it at the file
explicitly instead:
| |
This copies your config to /etc/cloudflared/config.yml and registers
cloudflared with systemd (or launchd, or the Windows service manager), so it
starts at boot and restarts if it crashes.
When Cloudflare Tunnel is the wrong choice
A tunnel fits when traffic starts outside your network and has to reach a machine with no public IP, or one you don’t want to expose directly: a laptop, a home server, a box behind corporate NAT. It’s the wrong tool in these cases:
- You already have a server with a public IP. The tunnel adds Cloudflare’s
edge as a hop for no gain. A DNS
Arecord plus your own reverse proxy or TLS termination is simpler and has one less moving part to fail. - You need guaranteed low latency or you move large volumes of data. Every byte passes through Cloudflare’s network. For most web traffic that cost is negligible; for high-throughput or latency-sensitive workloads it isn’t.
- You need the service to stay up when a machine goes down. One
cloudflaredon one machine is a single point of failure, andservice installonly covers process restarts. If that host dies, the hostname goes dark. Redundancy means running the same tunnel from a second machine. - Your code trusts the TCP source address for security decisions.
Requests reach your origin from Cloudflare’s IP ranges. The real client IP
arrives in the
CF-Connecting-IPheader, and anything that reads the socket address will see Cloudflare’s edge instead.
Quick tunnel or named tunnel: which to start with
If you’re not sure which one you need, you need a quick tunnel. Run
cloudflared tunnel --url http://localhost:<port> against whatever you’re
working on now and open the URL. Move to a named tunnel when you hit one of
its two real limits: a hostname that has to outlive the terminal session, or a
service that has to keep running unattended.