Tutorial

Cloudflare Tunnel Setup: Expose Self-Hosted Apps Without Port Forwarding

April 06, 2026

Back to Blog

What if you could expose a web app running on your home server, NAS, or Raspberry Pi to the internet — without opening any ports, without a static IP, and without touching your router? That's exactly what Cloudflare Tunnel does, and it's completely free.

Whether you're running a self-hosted wiki, a development environment, a home automation dashboard, or a personal API server, Cloudflare Tunnel creates a secure, encrypted connection from your machine to Cloudflare's global network — no inbound firewall rules required.

Your App
localhost:3000
cloudflared
outbound only
Cloudflare Edge
SSL + DDoS + WAF
Visitors
app.yourdomain.com
0
Open Ports Required
Free
For All Cloudflare Plans
<5min
Setup Time

What Is Cloudflare Tunnel?

Cloudflare Tunnel (formerly known as "Argo Tunnel") establishes an outbound-only encrypted connection from your server to Cloudflare's nearest edge server. Once the tunnel is running, Cloudflare proxies incoming requests through the tunnel to your local service — all without opening any inbound ports on your firewall.

This approach inverts the traditional model. Instead of your server listening for incoming connections (which requires open ports, a public IP, and firewall rules), your server initiates the connection outward to Cloudflare. The result is a dramatically smaller attack surface.

Key benefits at a glance:
  • No port forwarding or firewall changes needed
  • No static or public IP address required — works behind NAT and CGNAT
  • Automatic SSL certificates (Cloudflare handles them)
  • Built-in DDoS protection and WAF
  • Works from any network — home, office, cloud, or even mobile hotspot
  • Multiple services through a single tunnel

Common Use Cases

Home Lab / Self-Hosted Apps

Expose Nextcloud, Gitea, Jellyfin, Vaultwarden, or any self-hosted app without opening your home router to the internet.

Development Environments

Share a local dev server with teammates or clients. Better than ngrok for persistent tunnels — free, faster, and your own domain.

IoT & Home Automation

Access Home Assistant, Grafana dashboards, or Pi-hole remotely without a VPN or exposing ports.

Internal Tools

Make internal dashboards, admin panels, or monitoring tools accessible from anywhere, with Cloudflare Access for authentication.

Prerequisites

Before getting started, make sure you have:

  • A free Cloudflare account
  • A domain added to your Cloudflare account (DNS must be managed by Cloudflare)
  • A machine running the service you want to expose (any OS — Linux, macOS, Windows, Docker)
  • The cloudflared CLI tool (we'll install it in the steps below)

Method 1: Dashboard Setup (Easiest)

The Cloudflare dashboard provides a guided wizard that generates the install command for you. This is the recommended method for most users.

1
Open Zero Trust Dashboard
Go to one.dash.cloudflare.com → Networks → Tunnels → Create a tunnel
2
Select tunnel type
Choose Cloudflared (the default connector). Give your tunnel a descriptive name like home-server or dev-machine.
3
Install the connector
Cloudflare generates a one-line install command tailored to your OS. For Linux (Debian/Ubuntu):
# Debian/Ubuntu
$ curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
$ sudo dpkg -i cloudflared.deb

# Then run the connector with the token from the dashboard:
$ sudo cloudflared service install YOUR_TUNNEL_TOKEN
4
Add a public hostname
Back in the dashboard, click Add a public hostname:
  • Subdomain: app (or whatever you want)
  • Domain: Select your Cloudflare domain
  • Service type: HTTP
  • URL: localhost:3000 (or whatever port your app runs on)
5
Save and test
Visit https://app.yourdomain.com in your browser. Your local app is now live on the internet, with SSL, behind Cloudflare's WAF and DDoS protection.
That's it! The dashboard method handles tunnel creation, DNS record creation (CNAME), and connector installation in one guided flow. Cloudflare even generates the right install command for your operating system.

Method 2: CLI Setup

For more control — or if you prefer terminal workflows — you can create and manage tunnels entirely from the command line.

# Step 1: Authenticate with your Cloudflare account
$ cloudflared tunnel login
# Opens browser for OAuth — select your zone

# Step 2: Create a named tunnel
$ cloudflared tunnel create my-tunnel
Tunnel credentials written to /root/.cloudflared/a]b1c2d3-uuid.json
Created tunnel my-tunnel with id ab1c2d3e-4f56-7890-abcd-ef1234567890

# Step 3: Create DNS route
$ cloudflared tunnel route dns my-tunnel app.example.com
Added CNAME app.example.com which will route to this tunnel

Configuration File

Create ~/.cloudflared/config.yml to define your tunnel's routing rules:

# ~/.cloudflared/config.yml
tunnel: ab1c2d3e-4f56-7890-abcd-ef1234567890
credentials-file: /root/.cloudflared/ab1c2d3e-4f56-7890-abcd-ef1234567890.json

ingress:
  - hostname: app.example.com
    service: http://localhost:3000
  - hostname: api.example.com
    service: http://localhost:8080
  - hostname: grafana.example.com
    service: http://localhost:3000
  - service: http_status:404
The catch-all rule is mandatory. The last ingress entry must be a catch-all without a hostname field. This handles any request that doesn't match your defined hostnames. Using http_status:404 returns a clean 404 for unmatched requests.

Now start the tunnel:

$ cloudflared tunnel run my-tunnel
INF Starting tunnel tunnelID=ab1c2d3e-4f56-7890-abcd-ef1234567890
INF Connection registered connIndex=0 location=AMS
INF Connection registered connIndex=1 location=FRA
INF Connection registered connIndex=2 location=CDG
INF Connection registered connIndex=3 location=LHR
Four connections by default. Cloudflare Tunnel automatically establishes connections to 4 different edge locations for redundancy. If one edge goes down, traffic seamlessly routes through the others.

Method 3: Docker

If your services already run in Docker, running the tunnel connector as a container makes perfect sense. This is especially clean for Docker Compose setups.

# Standalone Docker run
$ docker run -d --name cloudflared \
  --restart unless-stopped \
  cloudflare/cloudflared:latest \
  tunnel --no-autoupdate run --token YOUR_TUNNEL_TOKEN

Docker Compose Example

# docker-compose.yml
version: "3.8"
services:
  webapp:
    image: nginx:alpine
    volumes:
      - ./html:/usr/share/nginx/html

  tunnel:
    image: cloudflare/cloudflared:latest
    command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
    restart: unless-stopped
    depends_on:
      - webapp
Docker networking tip: When using Docker Compose, the tunnel container can reach other containers by their service name. So the public hostname route should point to http://webapp:80 instead of http://localhost:80, since they're on the same Docker network.

Running as a System Service

For production use, you'll want the tunnel to start automatically on boot and restart on failure. The cloudflared service install command (used in Method 1) does this automatically. For manual CLI setups, create a systemd unit file:

# /etc/systemd/system/cloudflared.service
[Unit]
Description=Cloudflare Tunnel
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/cloudflared tunnel run
Restart=on-failure
RestartSec=5s
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable cloudflared
$ sudo systemctl start cloudflared
$ sudo systemctl status cloudflared
● cloudflared.service - Cloudflare Tunnel
   Loaded: loaded
   Active: active (running)

Multiple Services Through One Tunnel

One of the most powerful features of Cloudflare Tunnel is routing multiple services through a single tunnel. Each service gets its own subdomain, all managed through the same config.yml or dashboard configuration.

Public Hostname Local Service Port Use Case
app.example.com React / Next.js app 3000 Main web application
api.example.com Go / Node.js API 8080 Backend REST API
grafana.example.com Grafana 3000 Monitoring dashboards
home.example.com Home Assistant 8123 IoT / home automation
git.example.com Gitea 3000 Self-hosted Git
vault.example.com Vaultwarden 8080 Password manager
Pro tip: You can mix protocols too. Cloudflare Tunnel supports HTTP, HTTPS, TCP, SSH, and RDP. This means you can tunnel SSH access (ssh.example.com) or even RDP (rdp.example.com) through the same tunnel — all without opening any ports.

Security Best Practices

While Cloudflare Tunnel dramatically reduces your attack surface by eliminating open ports, there are additional security measures you should implement:

  • Enable Cloudflare Access — Add authentication (email OTP, Google/GitHub SSO, or SAML) before visitors can reach your tunnel. Free for up to 50 users.
  • Set up WAF rules — Use Cloudflare's Web Application Firewall to block common attack patterns on your tunnel endpoints.
  • Use service tokens — For machine-to-machine access (CI/CD, APIs), create service tokens instead of user-based authentication.
  • Never tunnel database ports — MySQL (3306), PostgreSQL (5432), Redis (6379) should NEVER be publicly exposed through a tunnel. Use Cloudflare Access + Warp for admin access if needed.
  • Monitor tunnel health — Check the Zero Trust dashboard regularly for connection status, traffic anomalies, and authentication logs.
  • Rotate tunnel tokens periodically — If a token is compromised, delete the tunnel and create a new one. The old token is instantly invalidated.
Never expose sensitive services without authentication. A Cloudflare Tunnel makes your local service accessible to the entire internet. Without Cloudflare Access or another authentication layer, anyone who knows the URL can access your app. Always add at least one authentication method for admin panels, databases, and internal tools.

Tunnel vs Traditional Port Forwarding

Here's a comprehensive comparison to understand why tunnels are the modern approach:

Feature Traditional (Port Forward) Cloudflare Tunnel
Open ports required Yes (80, 443, custom) None
Static/public IP needed Yes No
Works behind NAT/CGNAT No Yes
DDoS protection Manual setup Automatic
SSL certificate Manual (Let's Encrypt) Automatic
Firewall configuration Required None
Router configuration Port forwarding rules None
Built-in authentication None Cloudflare Access
Attack surface Exposed ports Zero inbound
Setup complexity Moderate Low (5 minutes)
Cost Free Free

Troubleshooting Common Issues

Problem Cause Fix
502 Bad Gateway Local service isn't running or wrong port Verify the service is running: curl http://localhost:PORT
Tunnel shows "inactive" cloudflared process not running Check systemd: systemctl status cloudflared
DNS resolution fails CNAME record not created Run: cloudflared tunnel route dns TUNNEL_NAME hostname
WebSocket connections fail Missing config Cloudflare Tunnel supports WebSockets by default — check your app's WebSocket path
Slow performance Tunnel routing through distant edge Check cloudflared tunnel info — connections should be to nearby locations

Panelica + Cloudflare Tunnel

While Panelica is designed for dedicated servers and VPS instances with public IP addresses, Cloudflare Tunnel complements it perfectly for specific use cases:

Docker Apps via Tunnel

Running Docker containers on Panelica? Use Cloudflare Tunnel to expose them through custom domains without allocating additional IPs or opening extra ports. Panelica's Docker management + Cloudflare integration makes this seamless.

Dev-to-Production Bridge

Connect development servers behind NAT to production Cloudflare zones. Test on your local machine, expose via tunnel, and share with your team — all through your real domain.

Panelica's Cloudflare integration already handles DNS record management, cache purge, Development Mode, and Under Attack Mode from within the server panel. Combined with Cloudflare Tunnel for edge cases, you get complete control over your Cloudflare setup without ever leaving your dashboard.

Quick Start Checklist

Here's the fastest path to getting your first tunnel running:

  • Sign up for Cloudflare and add your domain (free)
  • Go to Zero Trust → Networks → Tunnels → Create
  • Install cloudflared on your machine with the generated command
  • Add a public hostname pointing to your local service
  • Visit your subdomain — your app is live
  • Enable Cloudflare Access for authentication (strongly recommended)
  • Add additional hostnames for more services as needed

Cloudflare Tunnel has fundamentally changed how self-hosted services can be exposed to the internet. No more port forwarding nightmares, no more dynamic DNS hacks, no more worrying about DDoS attacks hitting your home IP. Set it up once, and your services are globally available, secured by Cloudflare's infrastructure — all for free.

Share: