Cloudflare Tunnels: Expose Self-Hosted Services Without Port Forwarding

Self-hosting is great until you need to expose your services to the outside world. Port forwarding is a pain, and punching holes in your firewall feels about as secure as leaving your front door unlocked. Luckily, Cloudflare Tunnels offer a much cleaner, more secure solution. They let you expose your self-hosted applications without opening any inbound ports, and give you free SSL encryption to boot. Let’s dive into setting up a Cloudflare Tunnel from scratch and exposing a couple of services running on a home server.
Why Cloudflare Tunnels?
Traditional port forwarding exposes your server directly to the internet. Any vulnerability in your application, or even in the services running on your server, can be exploited. Cloudflare Tunnels, on the other hand, create an outbound-only connection from your server to Cloudflare’s network. All traffic is routed through Cloudflare, which acts as a reverse proxy, providing benefits like:
- Security: No open inbound ports means attackers can’t directly connect to your server. Cloudflare’s WAF (Web Application Firewall) and DDoS protection add another layer of security.
- Free SSL: Cloudflare automatically provides SSL certificates for your domain, ensuring encrypted connections.
- Easy Setup: No more messing with router settings or dynamic DNS.
- Centralised Management: Cloudflare provides a central dashboard to manage all your tunnels and services.
The Quartalis ecosystem also benefits from secure tunnels. When you’re building complex AI pipelines with tools running on different servers, tunnels provide secure communication routes between each component without the hassle of managing individual firewall rules.
Setting up Cloudflare Tunnel
Let’s walk through setting up a Cloudflare Tunnel. I’m going to assume you already have a Cloudflare account and a domain name pointed to Cloudflare’s nameservers. If not, head over to Cloudflare and get that sorted first.
Create a Tunnel in Cloudflare Dashboard
Log in to your Cloudflare dashboard and select your domain. Navigate to “Zero Trust” -> “Access” -> “Tunnels” and click “Create a tunnel”. Give your tunnel a name (e.g.,
home-server-tunnel). Cloudflare will then give you instructions on how to install and run thecloudflareddaemon.
Install and Configure
cloudflaredcloudflaredis the agent that creates the connection between your server and Cloudflare. Choose the appropriate installation method for your operating system. I’ll use the Docker method as it’s the most portable.First, create a directory to store your Cloudflare tunnel credentials:
mkdir -p /opt/cloudflare-tunnel cd /opt/cloudflare-tunnelNow, authenticate
cloudflaredwith your Cloudflare account:docker run --rm -v $PWD:/etc/cloudflared cloudflare/cloudflared:latest tunnel loginThis will open a browser window where you can select your Cloudflare account and domain. After successful authentication, a
cert.pemfile will be created in the/opt/cloudflare-tunneldirectory. This file contains the credentials for your tunnel.Create a Tunnel Configuration File
To make managing your tunnel easier, we’ll create a configuration file. This file specifies which services to expose through the tunnel. Create a file named
config.ymlin the/opt/cloudflare-tunneldirectory with the following content:tunnel: <your-tunnel-id> credentials-file: /etc/cloudflared/cert.pem ingress: - hostname: app1.yourdomain.com service: http://localhost:8080 - hostname: app2.yourdomain.com service: http://localhost:8081 - service: http_status:404Replace
<your-tunnel-id>with the actual Tunnel ID shown in the Cloudflare dashboard.app1.yourdomain.comandapp2.yourdomain.comare the hostnames you want to use to access your services, andlocalhost:8080andlocalhost:8081are the addresses where your services are running on your server. The last entry ensures that any requests that don’t match the defined hostnames will receive a 404 error.Run the Tunnel
Now, run the tunnel using Docker:
docker run -d --restart=unless-stopped --name cloudflared -v /opt/cloudflare-tunnel:/etc/cloudflared --network="host" cloudflare/cloudflared:latest tunnel run-d: Runs the container in detached mode (background).--restart=unless-stopped: Restarts the container automatically unless it’s manually stopped.--name cloudflared: Gives the container a name for easy management.-v /opt/cloudflare-tunnel:/etc/cloudflared: Mounts the directory containing the credentials file into the container.--network="host": Crucially, uses the host network so the tunnel can access your local services.
Verify the tunnel is running by checking the container logs:
docker logs cloudflaredYou should see messages indicating that the tunnel is established and routing traffic. Also, check in the Cloudflare dashboard. You should now see the tunnel status as “Healthy”.
Configure DNS Records
Finally, create DNS records in Cloudflare for
app1.yourdomain.comandapp2.yourdomain.com. These records should be CNAME records pointing to your tunnel’s ID, followed by.cfargotunnel.com.For example:
Type Name Target CNAME app1.yourdomain.com <your-tunnel-id>.cfargotunnel.comCNAME app2.yourdomain.com <your-tunnel-id>.cfargotunnel.comEnable Cloudflare Proxy (the orange cloud) for these records.
Test Your Setup
Open your browser and navigate to
app1.yourdomain.comandapp2.yourdomain.com. You should see your self-hosted applications. If you see a “502 Bad Gateway” error, double-check your tunnel configuration and DNS records. Make sure your applications are running on the specified ports on your server.
Running Multiple Services on One Tunnel
The example above showed two services, but you can easily extend this to handle many more. Just add more entries to the ingress section of your config.yml file. This is a clean way to manage multiple services without needing to run multiple tunnels or expose different ports.
For example, let’s say you want to add access to a Grafana dashboard running on port 3000:
tunnel: <your-tunnel-id>
credentials-file: /etc/cloudflared/cert.pem
ingress:
- hostname: app1.yourdomain.com
service: http://localhost:8080
- hostname: app2.yourdomain.com
service: http://localhost:8081
- hostname: grafana.yourdomain.com
service: http://localhost:3000
- service: http_status:404 After updating the configuration file, restart the cloudflared container:
docker restart cloudflared Create a new CNAME record in Cloudflare for grafana.yourdomain.com pointing to <your-tunnel-id>.cfargotunnel.com. Now you can access your Grafana dashboard at grafana.yourdomain.com.
Integrating with Docker Compose
For more complex setups, using Docker Compose can simplify managing your services and the cloudflared tunnel. Here’s an example docker-compose.yml file:
version: "3.8"
services:
app1:
image: your-app1-image
ports:
- "8080:80"
networks:
- app_net
app2:
image: your-app2-image
ports:
- "8081:80"
networks:
- app_net
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
volumes:
- /opt/cloudflare-tunnel:/etc/cloudflared
networks:
- app_net
command: tunnel run
networks:
app_net: In this example, app1 and app2 are Docker containers running your applications. The cloudflared container is configured to use the same network as the other containers, allowing it to access them by their container names (or localhost within the container).
Make sure the config.yml file is correctly configured and placed in the /opt/cloudflare-tunnel directory. Then, run docker-compose up -d to start the services.
Wrapping Up
Cloudflare Tunnels offer a secure and convenient way to expose your self-hosted services without the risks and hassles of port forwarding. It’s an excellent solution for hobbyists and professionals alike. With free SSL, centralised management, and robust security features, it’s a no-brainer for anyone serious about self-hosting.
What’s Next
Now that you have a basic Cloudflare Tunnel setup, you can explore advanced features like:
- Access Policies: Use Cloudflare Access to add authentication to your services, requiring users to log in before accessing them.
- Health Checks: Configure health checks to automatically failover to a backup server if your primary server goes down.
- argo Tunnel: Argo Tunnel provides additional performance benefits by optimising the routing of traffic through Cloudflare’s network.
- Integration with CI/CD pipelines: Automate the deployment and configuration of your tunnels as part of your development workflow.
I use Cloudflare Tunnels extensively in the Quartalis ecosystem to securely connect different services and environments. It simplifies the infrastructure and makes it easier to manage complex AI pipelines, and I hope this guide helps you do the same!
Need this built for your business?
Get In TouchRelated Posts
Docker Compose for Homelab: Running 15+ Services Like a Pro
Docker Compose is a game-changer for managing multiple containers in a homelab environment. If you're running 15+ services—like Ollama, FastAPI APIs, WordPress sites, n8n workflows, or...
Weekly Roundup: 16 March – 22 March 2026
This week in AI, self-hosting, and development — the most interesting stories and tools from 16 March – 22 March 2026.
The Complete Guide to Self-Hosting in 2026: Take Back Your Data
In 2026, the allure of self-hosting is stronger than ever. Concerns about data privacy are at an all-time high, subscription costs for cloud services are spiralling, and the desire for true control...