Ryan Schachte's Blog
TCP tunneling with Cloudflare Tunnel
January 2nd, 2024

TCP tunneling is useful because you can forward connections that aren’t just websites running HTTP(s). Use-cases where this applies is forwarding things like SSH, RDP or Postgres. Recently I wanted to use Cloudflare Tunnels to forward TCP connections for my database. This would allow me to login to my DB from anywhere in the world using a secure and protected tunnel.

TCP tunnels with CFT require that cloudflared be running on both the target node as well as all connecting client nodes. (This is only true for raw TCP and not layer 7 protocols). Let’s install it.

sudo apt-get update; sudo apt-get -y install cloudflared

Authenticate the server node with cloudflared tunnel login and open the link to authenticate in your browser. We can now create our tunnel.

cloudflared tunnel create postgres

This will create a tunnel with a UUID. Note the ID and let’s configure a DNS route that will be used to transport our packets from our local forwarder via Cloudflare’s transport network.

cloudflared tunnel route dns <TUNNEL_UUID> <DESIRED_SUBDOMAIN>

Your desired subdomain would be something like database. You should now have a custom DNS CNAME record aliasing database.<YOUR_DOMAIN>.com to the tunnel domain.

Let’s now configure our cloudflared config YAML located at ~/.cloudflared.

~/.cloudflared/config.yaml
tunnel: <TUNNEL_UUID>
credentials-file: <PATH_TO_TUNNEL_UUID>.json
 
ingress:
  - hostname: <SUBDOMAIN>.<YOURDOMAIN>.com
    service: tcp://<PRIVATE_INTERNAL_IP>:<PORT>
	# catch all
  - service: http_status:404

Start the tunnel with cloudflared tunnel run. This is good for ephemeral tests, but let’s configure it to start up automatically as a Linux service.

# install the cloudflared service
sudo cloudflared --config ~/.cloudflared/config.yml service install

You can edit the service config with sudo vim /etc/systemd/system/cloudflared.service. To ensure it’s running, check the service status with systemctl status cloudflared.

Now that the tunnel is running, the client needs to connect as well. Be sure cloudflared was installed on the client and also authenticated.

cloudflared access tcp --hostname <SUBDOMAIN>.<YOURDOMAIN>.com --url 127.0.0.1:<LOCAL_PORT_FORWARD>

The above command will take TCP connections sent to localhost:5432 in my case and push the packets over the tunnel associated with the specified hostname. In my case with Postgres, I simply connect to the host IP of 127.0.0.1 with port 5432 and it will be forwarded to the Postgres server node.

You can view Cloudflare Tunnel documentation here.

Care to comment?