DEV Community

Cover image for Cloudflare Tunnel: publish a home server or VPS without opening ports
Aravind Govindhasamy
Aravind Govindhasamy

Posted on Originally published at starter.budeglobal.in on

Cloudflare Tunnel: publish a home server or VPS without opening ports

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.

Visitors connect to Cloudflare over HTTPS; cloudflared on your server keeps an outbound tunnel to Cloudflare and forwards each hostname to a local port

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:8080 and an API on localhost: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>

Enter fullscreen mode Exit fullscreen mode

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.yml changes nothing: the routes come from the dashboard. Manage hostnames there.
  • Restarting cloudflared drops 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 incoming stays 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.txt affect 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

Enter fullscreen mode Exit fullscreen mode

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)