Cloudflare Tunnels

#cloudflare #technology #til

Every now and then I need to expose a service running on a local machine to the internet, usually just temporarily. Rather than opening ports on a router or mucking about with firewall rules, a reverse tunnel does the job nicely.

There are a number of providers offering this kind of reverse tunnel, including:

I've been playing around with Cloudflare Tunnel recently. Its client, cloudflared, supports a couple of different modes: anonymous quick tunnels (transient, randomly-named URLs) and named tunnels, which are tied to a Cloudflare account and give you stable, predictable hostnames.

Quick tunnels

If you just want to throw something onto a public URL for an hour, TryCloudflare needs no account and nothing more than the cloudflared binary. For example, to share a local web server listening on port 8080:

cloudflared tunnel --url http://localhost:8080

cloudflared connects out to Cloudflare's edge and prints a randomly generated URL:

https://some-random-words-here.trycloudflare.com

Anyone with that URL can now reach your local server through Cloudflare's network.

A few caveats worth knowing before you rely on these:

  • The tunnel and its URL live only as long as the cloudflared process runs. Hit Ctrl-C and it's gone, and you'll get a fresh random URL next time you start one.
  • They're intended for testing and development only.

Named tunnels

For anything longer-lived, named tunnels give you a stable hostname of your choosing. These require a Cloudflare login, and a domain served by Cloudflare DNS.

First, authenticate and create the tunnel:

# Opens a browser window to authenticate
cloudflared tunnel login

# Creates the tunnel and a credentials file (~/.cloudflared/<UUID>.json)
cloudflared tunnel create my-tunnel

# Confirm it exists
cloudflared tunnel list

Next, create a configuration file at ~/.cloudflared/config.yml mapping hostnames to local services:

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

ingress:
  # Map a public hostname to a local service
  - hostname: app.my-site.com
    service: http://127.0.0.1:8080

  # Required catch-all rule; anything unmatched gets a 404
  - service: http_status:404

Ingress rules are evaluated from top to bottom, and the last rule must be a catch-all. Services can be more than plain HTTP: SSH, RDP, arbitrary TCP, Unix sockets and more are supported. Other top-level options (like protocol and edge-ip-version) can tune how cloudflared connects to the edge.

Then route a DNS record to the tunnel and start it up:

# Creates a CNAME for app.my-site.com
cloudflared tunnel route dns my-tunnel app.my-site.com

# Start serving traffic
cloudflared tunnel run my-tunnel

A couple of additional commands that may be useful for debugging:

# Sanity-check the ingress rules in your config
cloudflared tunnel ingress validate

# Check which rule a given URL would match
cloudflared tunnel ingress rule https://app.my-site.com

# See connection status for a running tunnel
cloudflared tunnel info my-tunnel

All in all, these commands provide a quick and convenient way to expose tools and services from your local machine to the internet. Especially if you already use Cloudflare as your site's DNS.