IAnswerable

Expose a Local Server with Cloudflare Tunnel

25 Nov 2025

You don’t need a static IP or an open port to put a server on the internet. Cloudflare Tunnel runs a daemon on your machine that opens an outbound connection to Cloudflare’s network, and Cloudflare routes public traffic to it over that connection. Your router’s firewall never has to know this is happening.

This matters most for home servers, NAS boxes, or anything behind CGNAT where you genuinely can’t get a public IP even if you wanted one.

How it actually works

Normally, exposing a service means: get a public IP, forward a port on your router, open that port in your firewall, point DNS at the IP. Every one of those is an attack surface and a thing that breaks when your ISP changes your IP.

cloudflared, the tunnel daemon, flips this. It runs on your server and dials out to Cloudflare over HTTPS. Cloudflare terminates public traffic on your domain and forwards it down that same outbound connection to your local service. No inbound port is ever opened. Your local firewall can block all inbound traffic and the tunnel still works, because from the network’s perspective, nothing is coming in — it’s a response to a connection your server started.

flowchart LR
    D["cloudflared on your server"] -->|"1 - dials out"| CF["Cloudflare edge"]
    Vi["Visitor"] -->|"2 - HTTPS request"| CF
    CF -->|"3 - routed down the tunnel"| D
    D --> S["Local service"]
  

1. Install cloudflared

curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared

2. Authenticate and create the tunnel

cloudflared tunnel login
cloudflared tunnel create home-server

This creates a tunnel and drops a credentials file under ~/.cloudflared/. That file is the only secret involved — anyone with it can route traffic through your tunnel, so treat it like an API key.

3. Configure routing

Create ~/.cloudflared/config.yml:

tunnel: home-server
credentials-file: /home/you/.cloudflared/<tunnel-id>.json

ingress:
  - hostname: app.example.com
    service: http://localhost:3000
  - hostname: files.example.com
    service: http://localhost:8080
  - service: http_status:404

That last rule is mandatory — cloudflared requires a catch-all as the final entry, and http_status:404 is the right one unless you specifically want a default service for unmatched hostnames.

4. Point DNS at the tunnel

cloudflared tunnel route dns home-server app.example.com
cloudflared tunnel route dns home-server files.example.com

This creates CNAME records pointing at your tunnel’s unique <tunnel-id>.cfargotunnel.com address — you don’t manage an A record or an IP at all.

5. Run it as a service

sudo cloudflared service install
sudo systemctl enable --now cloudflared

Without this, the tunnel dies when your SSH session ends or the machine reboots, and every service behind it goes dark with no local error to tell you why — the failure is entirely on Cloudflare’s side, showing as a 502 to visitors.

What you give up

TLS terminates at Cloudflare’s edge by default, not on your box. If your compliance requirements need end-to-end encryption to the origin, you need to also run your local service over HTTPS and set originRequest.noTLSVerify or configure proper origin certs — the plain HTTP example above is fine for a home lab, not for anything handling sensitive data.

You’re also dependent on Cloudflare’s network being up and your outbound connection staying alive. Check tunnel health with:

cloudflared tunnel info home-server

Where this beats a VPN

If the alternative you’re comparing against is a VPN back into your home network, Cloudflare Tunnel is simpler for exposing specific services because you’re not extending your entire local network to every device that connects. You expose exactly the hostnames and ports you list in config.yml — nothing else on your LAN becomes reachable. That’s a meaningfully smaller attack surface than “now this laptop is on my home subnet.”