All posts

Sep 8, 2026

Exposing Coolify apps through a Cloudflare Tunnel

How to create a Cloudflare Tunnel, install the connector on your server and route your domains through it to Coolify, so your apps go online without a single open port.

Cloudflare and Coolify thumbnail

Introduction

A Cloudflare Tunnel is an outbound connection from your server to Cloudflare's network. A small daemon called cloudflared runs on the machine, connects to Cloudflare, and from then on Cloudflare forwards requests for your domains through that connection. The server never has to accept incoming traffic: no open ports, no public IP required, and nobody can find your origin address just by resolving your domain.

In this article I will guide you through creating a tunnel, installing the connector on the server and routing your domains through it to Coolify's proxy, so every app you deploy is reachable through Cloudflare instead of through open ports. It picks up right where my post on setting up Coolify left off.

For this guide, I'll be assuming you already have a Coolify instance running on a Linux server and a domain managed by Cloudflare.

What you need

  • A domain whose nameservers point to Cloudflare. Tunnels only work for zones Cloudflare manages.
  • A Cloudflare account with Zero Trust enabled. The free plan is enough, although Cloudflare asks for a payment method during onboarding; you won't be charged.
  • A server with Coolify installed. It doesn't need a public IP anymore, but it does need outbound internet access.

Creating the tunnel

Open the Zero Trust dashboard and go to Networks -> Tunnels -> Create a tunnel. Choose Cloudflared as the connector type, give the tunnel a name (I'm calling mine coolify) and save it.

The next screen shows the installation commands for every operating system, each one carrying a long token. That token is the tunnel's credential: whoever has it can connect a machine to your tunnel, so treat it like a password.

Installing the connector on the server

SSH into the server and run the commands from the dashboard. On Debian they install the cloudflared package and register it as a systemd service with your token:

bash
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
dpkg -i cloudflared.deb
cloudflared service install <YOUR_TUNNEL_TOKEN>

Back in the dashboard, the connector should appear a few seconds later with a Healthy status. If it doesn't, check the service:

bash
systemctl status cloudflared
journalctl -u cloudflared -f
bash
docker run -d --name cloudflared --restart unless-stopped \
--network coolify \
cloudflare/cloudflared:latest tunnel --no-autoupdate run --token <YOUR_TUNNEL_TOKEN>

Routing a domain to Coolify

With the tunnel connected, open its Public Hostname tab and click Add a public hostname. This is where you tell Cloudflare which domain goes where:

  • Subdomain: the name of your app, for example blog.
  • Domain: your Cloudflare zone, example.com.
  • Service: type HTTP, URL localhost:80. That's Coolify's proxy (Traefik), which will look at the Host header and route the request to the right container.

Save, and Cloudflare creates the DNS record for you: a proxied CNAME from blog.example.com to the tunnel's address. No A record, no IP anywhere.

*.example.com    CNAME    <TUNNEL_ID>.cfargotunnel.com    (proxied)

Configuring the app in Coolify

Open the resource in Coolify and set its domain to http://blog.example.com. Yes, http, and this is the one detail that trips most people up.

TLS ends at Cloudflare's edge: visitors talk to Cloudflare over HTTPS, the tunnel itself is encrypted, and Traefik only ever sees plain HTTP on port 80. If you set the domain to https://, Traefik tries to get a Let's Encrypt certificate it can't obtain (port 80 isn't reachable from the internet anymore) and redirects every HTTP request to HTTPS, which the tunnel sends back as HTTP. The result is a redirect loop.

Save and redeploy the resource. Then, in the Cloudflare dashboard of your zone, go to SSL/TLS -> Edge Certificates and turn on Always Use HTTPS so that visitors typing the plain address get redirected by Cloudflare, not by Traefik.

Closing the ports

The whole point of this is not having anything exposed, so once your apps respond through the tunnel, close HTTP and HTTPS in the firewall:

bash
ufw delete allow 80/tcp
ufw delete allow 443/tcp
ufw status

Keep SSH open for now, or move it through the tunnel as well (more on that below). If you set a domain for the Coolify dashboard itself in the previous post, give it a public hostname too so it stays reachable, pointing at localhost:8000 this time: that's Coolify's own port, no need to go through Traefik for it.

Bonus: servers without a public IP

Since the connection is outbound, the same trick works for a machine that isn't reachable at all: a box at home, a Raspberry Pi, a VM behind a corporate NAT. Install the connector on it with its own tunnel, and Coolify can manage it as an additional server.

Add a public hostname of type SSH pointing at localhost:22, for example ssh-home.example.com. Then, in Coolify, go to Servers -> + Add, use that hostname as the address and enable the Cloudflare Tunnel option in the server's settings. Coolify then opens its SSH connection through Cloudflare instead of connecting to an IP, and the apps you deploy there get exposed with public hostnames exactly like above.

Testing it out

From your computer, check that the domain responds and that the answer comes from Cloudflare:

bash
curl -I https://blog.example.com
HTTP/2 200
server: cloudflare
cf-ray: 8c1f2a3b4d5e6f70-EZE

The server: cloudflare and cf-ray headers tell you the request went through Cloudflare's network. Then try the same request against the server's IP on port 443: it should time out, because nothing is listening for the outside world anymore.

That's it! Your apps are served through Cloudflare, your origin IP is out of the picture and the server has no inbound ports open. Any new resource you deploy in Coolify is one public hostname away from being online.