Cloudflare Tunnel lets a machine that can't accept incoming connections (a home server behind your router, a laptop, a VPS with every port closed) serve websites on your domain. A small program, cloudflared, opens an outbound connection to Cloudflare, and Cloudflare sends your visitors' requests down it. You get HTTPS, DDoS protection and no open ports, for free.
What you need
- A domain using Cloudflare's nameservers (add it to Cloudflare and change the nameservers at your registrar)
- A Linux machine running your app on local ports, for example a website on
localhost:8080and an API onlocalhost:8000
1. Create the tunnel
In the Cloudflare dashboard, go to Networking → Tunnels (in the Zero Trust dashboard it's Networks → Tunnels ) and click Create a tunnel. Choose cloudflared , give it a name such as home-server, and save.
Cloudflare shows install commands for your operating system. On Debian or Ubuntu they add Cloudflare's package repository, install cloudflared, and register it as a service with your tunnel's token:
sudo cloudflared service install <TOKEN>
Within a few seconds the dashboard shows the connector as Connected. The token is a credential: anyone with it can run your tunnel, so don't paste it in public.
2. Route a hostname to a local port
Open the tunnel, go to Routes , click Add route and choose Published application (older dashboards call this Public hostnames → Add a public hostname ):
| Field | Example |
|---|---|
| Subdomain | app |
| Domain | example.com |
| Path | (empty) |
| Service URL | http://localhost:8080 |
Save. Cloudflare creates the DNS record for app.example.com itself (a proxied CNAME to your tunnel), and the site is live over HTTPS. No restart needed.
Add as many hostnames as you like to one tunnel, for example api.example.com → http://localhost:8000.
3. See real visitor IPs
Requests reach your app from cloudflared on the same machine, so your app sees 127.0.0.1 as every visitor's address. Cloudflare passes the real one in X-Forwarded-For and CF-Connecting-IP. Tell your server to trust them from localhost:
-
uvicorn (FastAPI):
--proxy-headers --forwarded-allow-ips 127.0.0.1 -
nginx:
set_real_ip_from 127.0.0.1; real_ip_header CF-Connecting-IP;
Without this, per-IP rate limits treat all your visitors as one.
Gotchas
-
Dashboard tunnels ignore local ingress rules. If you created the tunnel in the dashboard, editing
/etc/cloudflared/config.ymlchanges nothing: the routes come from the dashboard. Manage hostnames there. -
Restarting
cloudflareddrops every hostname on the tunnel for a few seconds. You don't need to restart it to add or change routes. -
Keep your firewall closed. The tunnel is outbound, so
ufw default deny incomingstays as it is. - Webhooks work. Stripe, Razorpay and Supabase hooks reach a tunneled API like any HTTPS URL, which also makes a tunnel handy for testing webhooks from your laptop.
-
Cloudflare settings apply. Caching rules, Bot Fight Mode and Cloudflare's managed
robots.txtaffect tunneled sites like any other. If search engines or AI crawlers can't see your site, check those first. - Protect private tools. Put admin hostnames behind Cloudflare Access so only your email can reach them.
Check it
curl -I https://app.example.com # should show server: cloudflare
sudo systemctl status cloudflared # connector running
Using it with FastAPI Supabase Starter
Serve the built frontend (for example with nginx on a local port) and run the API on 127.0.0.1:8000, then add two routes: your site's hostname to the frontend's port and api. to port 8000. Set API_URL, FRONTEND_URL and SITE_URL to the public HTTPS addresses, and keep FORWARDED_ALLOW_IPS at its localhost default so the request log and rate limit see real IPs. The full server setup is in Deploy FastAPI and React on a VPS.
Top comments (0)