Cloudflare Tunnel VPS Guide 2026: Expose Services Without Opening Ports
TUTORIAL 12 min read fordnox

Cloudflare Tunnel VPS Guide 2026: Expose Services Without Opening Ports

Set up Cloudflare Tunnel on your VPS to expose web apps securely without opening ports or revealing your server IP. Complete guide with Docker and DNS config.


Cloudflare Tunnel on VPS: Secure Access Without Open Ports

Cloudflare Tunnel lets you expose services on your VPS to the internet without opening a single port. No public IP needed, no firewall rules, no port forwarding — Cloudflare handles everything through an encrypted outbound connection.

Why Cloudflare Tunnel?

Why Cloudflare Tunnel?

Why Cloudflare Tunnel?

ApproachOpen PortsIP ExposedDDoS ProtectionSSL
Cloudflare TunnelNoneHiddenBuilt-inAutomatic
Reverse proxy (Nginx)80, 443YesNoneManual (Let’s Encrypt)
Port forwardingAnyYesNoneManual
VPN + accessVPN portHiddenNoneVia VPN

Traditional setup: open ports → configure firewall → manage SSL → hope nobody finds your IP.

Cloudflare Tunnel: install cloudflared → point it at your local service → done. Your server stays invisible.

How It Works

  1. cloudflared runs on your VPS and creates an outbound connection to Cloudflare’s edge
  2. Traffic hits your domain → Cloudflare routes it through the tunnel → reaches your service
  3. Your VPS never accepts inbound connections — it only dials out
  4. Cloudflare handles SSL, DDoS protection, and caching automatically

No ports open. No IP exposed. No attack surface.

Prerequisites

Best VPS for Cloudflare Tunnel

ProviderPlanPriceWhy
HostingerKVM1$4.99/moBest value, solid performance
HetznerCX22€3.79/moCheapest for EU
VultrVC2$6/moGlobal locations
DigitalOceanBasic$6/moSimple dashboard

Cloudflare Tunnel uses minimal resources — the VPS specs matter for your actual services, not the tunnel itself.

Quick Setup (10 Minutes)

Step 1: Install cloudflared

# Debian/Ubuntu
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
apt update
apt install cloudflared -y
# Or download binary directly
curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared

Verify:

cloudflared --version

Step 2: Authenticate

cloudflared tunnel login

This opens a browser URL. Pick the domain you want to use. A certificate is saved to ~/.cloudflared/cert.pem.

Headless server? Copy the URL it prints, open it on your local machine, authenticate, then copy cert.pem back to the server.

Step 3: Create a Tunnel

cloudflared tunnel create my-tunnel

This generates a tunnel ID and credentials file. Note the tunnel UUID — you’ll need it.

# List tunnels
cloudflared tunnel list

Step 4: Configure the Tunnel

Create the config file:

mkdir -p ~/.cloudflared
cat > ~/.cloudflared/config.yml << 'EOF'
tunnel: YOUR_TUNNEL_UUID
credentials-file: /root/.cloudflared/YOUR_TUNNEL_UUID.json

ingress:
  - hostname: app.yourdomain.com
    service: http://localhost:3000
  - hostname: api.yourdomain.com
    service: http://localhost:8080
  - service: http_status:404
EOF

Replace:

The last http_status:404 rule is required — it’s the catch-all for unmatched requests.

Step 5: Create DNS Records

cloudflared tunnel route dns my-tunnel app.yourdomain.com
cloudflared tunnel route dns my-tunnel api.yourdomain.com

This creates CNAME records pointing to your tunnel automatically.

Step 6: Run the Tunnel

cloudflared tunnel run my-tunnel

Your services are now live at app.yourdomain.com and api.yourdomain.com — with SSL, DDoS protection, and zero open ports.

Run as a System Service

Don’t run tunnels in a terminal. Set up a proper service:

cloudflared service install
systemctl enable cloudflared
systemctl start cloudflared

Check status:

systemctl status cloudflared
journalctl -u cloudflared -f

The service reads from ~/.cloudflared/config.yml (or /etc/cloudflared/config.yml on some systems).

Docker Setup

Single Service

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    command: tunnel --no-autoupdate run --token YOUR_TUNNEL_TOKEN
    restart: unless-stopped
    network_mode: host

With Docker Compose Services

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel --no-autoupdate run --token YOUR_TUNNEL_TOKEN
    restart: unless-stopped
    depends_on:
      - webapp
      - api
    networks:
      - tunnel

  webapp:
    image: nginx:alpine
    networks:
      - tunnel

  api:
    image: your-api:latest
    networks:
      - tunnel

networks:
  tunnel:

When using Docker networks, point services at container names:

ingress:
  - hostname: app.yourdomain.com
    service: http://webapp:80
  - hostname: api.yourdomain.com
    service: http://api:8080
  - service: http_status:404

Instead of config files, use the Cloudflare Zero Trust dashboard:

  1. Go to Zero Trust Dashboard → Networks → Tunnels
  2. Create a tunnel → get the token
  3. Configure routes in the dashboard
  4. Run with just: cloudflared tunnel run --token YOUR_TOKEN

No config files needed. Manage everything from the dashboard.

Multiple Services on One Tunnel

One tunnel can serve unlimited services. Add more ingress rules:

ingress:
  - hostname: grafana.yourdomain.com
    service: http://localhost:3000
  - hostname: portainer.yourdomain.com
    service: http://localhost:9000
  - hostname: code.yourdomain.com
    service: http://localhost:8443
  - hostname: files.yourdomain.com
    service: http://localhost:8080
  - hostname: yourdomain.com
    service: http://localhost:80
  - service: http_status:404

Each hostname needs a DNS route:

cloudflared tunnel route dns my-tunnel grafana.yourdomain.com
cloudflared tunnel route dns my-tunnel portainer.yourdomain.com

Access Control with Cloudflare Zero Trust

Tunnel alone makes services accessible to everyone. Add Zero Trust policies to restrict access:

Email-Based Authentication

  1. Zero Trust Dashboard → Access → Applications → Add
  2. Set application domain (e.g., grafana.yourdomain.com)
  3. Add policy: Allow → Emails ending in @yourdomain.com

Users see a Cloudflare login page before reaching your app.

One-Time PIN

  1. Add policy: Allow → Emails → specific email addresses
  2. Authentication: One-time PIN
  3. Users enter email → get a code → access granted

No passwords to manage. No accounts to create.

Service Tokens (for APIs)

# Create a service token in the dashboard
# Then access with headers:
curl -H "CF-Access-Client-Id: YOUR_CLIENT_ID" \
     -H "CF-Access-Client-Secret: YOUR_CLIENT_SECRET" \
     https://api.yourdomain.com/endpoint

Common Use Cases

Self-Hosted Dashboards

Expose Grafana, Portainer, or Uptime Kuma without opening ports:

ingress:
  - hostname: monitor.yourdomain.com
    service: http://localhost:3001
    originRequest:
      noTLSVerify: true

Development Environments

Share local dev servers temporarily:

# Quick tunnel (no config needed)
cloudflared tunnel --url http://localhost:3000

This creates a temporary *.trycloudflare.com URL. Great for demos and testing.

SSH Access

Access your VPS via SSH through Cloudflare:

Server config:

ingress:
  - hostname: ssh.yourdomain.com
    service: ssh://localhost:22
  - service: http_status:404

Client:

# Add to ~/.ssh/config
Host ssh.yourdomain.com
  ProxyCommand cloudflared access ssh --hostname %h

Now SSH without exposing port 22. Combine with Zero Trust policies for additional authentication.

TCP/UDP Services

Tunnel non-HTTP services:

ingress:
  - hostname: db.yourdomain.com
    service: tcp://localhost:5432
  - service: http_status:404

Client side:

cloudflared access tcp --hostname db.yourdomain.com --url localhost:5432

Performance Tuning

Protocol Selection

tunnel: YOUR_TUNNEL_UUID
credentials-file: /root/.cloudflared/YOUR_TUNNEL_UUID.json
protocol: quic  # Fastest, default in newer versions

ingress:
  - hostname: app.yourdomain.com
    service: http://localhost:3000
  - service: http_status:404

QUIC is faster and handles packet loss better than HTTP/2.

Connection Settings

ingress:
  - hostname: app.yourdomain.com
    service: http://localhost:3000
    originRequest:
      connectTimeout: 30s
      keepAliveTimeout: 90s
      keepAliveConnections: 100
      httpHostHeader: app.yourdomain.com
  - service: http_status:404

Caching

Since traffic goes through Cloudflare, you get their caching automatically. Configure cache rules in the Cloudflare dashboard for static assets.

Cloudflare Tunnel vs Alternatives

FeatureCloudflare TunnelNginx + Let’s EncryptTailscale Funnel
Open portsNone80, 443None
DDoS protectionYesNoNo
SSLAutomaticManual renewalAutomatic
Custom domainYesYesLimited
Access controlZero TrustDIYTailscale ACLs
PriceFreeFreeFree (limited)
SpeedFast (edge cache)DirectGood

For exposing web services, Cloudflare Tunnel is hard to beat. For private networking between devices, check out WireGuard or Tailscale instead.

Troubleshooting

Tunnel Not Connecting

# Check tunnel status
cloudflared tunnel info my-tunnel

# Check logs
journalctl -u cloudflared --no-pager -n 50

# Test connectivity
cloudflared tunnel run --loglevel debug my-tunnel

502 Bad Gateway

Your local service isn’t running or is on a different port:

# Verify service is listening
ss -tlnp | grep :3000

# Check if service responds
curl -v http://localhost:3000

DNS Not Resolving

# Verify CNAME exists
dig app.yourdomain.com CNAME

# Should point to UUID.cfargotunnel.com
# If not, re-run:
cloudflared tunnel route dns my-tunnel app.yourdomain.com

WebSocket Issues

Add WebSocket support:

ingress:
  - hostname: ws.yourdomain.com
    service: http://localhost:3000
    originRequest:
      noTLSVerify: false
  - service: http_status:404

Cloudflare Tunnel supports WebSockets natively — just make sure your Cloudflare plan allows it (free plan does for most use cases).

Certificate Errors

# Re-authenticate
cloudflared tunnel login

# Check cert
ls -la ~/.cloudflared/cert.pem

Security Best Practices

  1. Use Zero Trust policies — Don’t expose admin panels to the entire internet
  2. Rotate tunnel tokens — Delete and recreate tunnels periodically
  3. Close all VPS ports — If everything goes through tunnels, close 80/443 entirely
# Lock down firewall — only SSH needed
ufw default deny incoming
ufw allow ssh
ufw enable
# No need for 80/443 at all
  1. Monitor access logs — Zero Trust dashboard shows all requests
  2. Enable notifications — Get alerts for tunnel health changes

Multiple Tunnels

Run separate tunnels for isolation:

cloudflared tunnel create production
cloudflared tunnel create staging

Separate config files:

cloudflared tunnel --config ~/.cloudflared/production.yml run production
cloudflared tunnel --config ~/.cloudflared/staging.yml run staging

Pricing

FeatureFreePro ($20/mo)Business ($200/mo)
TunnelsUnlimitedUnlimitedUnlimited
Zero Trust users505050
DDoS protectionBasicEnhancedAdvanced
WAF rulesLimited20100+

The free plan covers most self-hosting needs. You only need Pro for advanced WAF rules or priority support.

FAQ

Is Cloudflare Tunnel free?

Yes. Tunnels are free on all plans, including the free tier. Zero Trust is free for up to 50 users.

Does it slow down my services?

Usually the opposite — Cloudflare’s edge network and caching can make services faster for global users.

Can I use it without a domain?

For quick testing, cloudflared tunnel --url gives you a temporary *.trycloudflare.com domain. For production, you need a domain on Cloudflare.

What happens if Cloudflare goes down?

Your services become unreachable. This is the tradeoff — you depend on Cloudflare’s uptime. For critical services, maintain a backup access method (VPN or direct SSH).

Can I run multiple tunnels on one VPS?

Yes. Each tunnel is a separate process with its own config. No limits on tunnels per server.

Does it work with Docker?

Perfectly. Use the official cloudflare/cloudflared image. Token-based setup is easiest for containers.

Use CaseSetupNotes
Single web appQuick tunnelcloudflared tunnel --url
Multiple servicesNamed tunnel + configOne tunnel, multiple ingress rules
Team accessTunnel + Zero TrustEmail or SSO authentication
ProductionDocker + dashboard tokenEasy management, auto-restart

The combination of a Hostinger VPS + Cloudflare Tunnel gives you enterprise-grade infrastructure for under $5/month. No ports to manage, no SSL to renew, no DDoS to worry about — just deploy your apps and let Cloudflare handle the rest.

~/cloudflare-tunnel-vps-guide/get-started

Ready to get started?

Get the best VPS hosting deal today. Hostinger offers 4GB RAM VPS starting at just $4.99/mo.

Get Hostinger VPS — $4.99/mo

// up to 75% off + free domain included

// related topics

cloudflare tunnel vps cloudflare tunnel setup cloudflared vps expose vps without ports cloudflare zero trust tunnel

// related guides

Andrius Putna

Andrius Putna

I am Andrius Putna. Geek. Since early 2000 in love tinkering with web technologies. Now AI. Bridging business and technology to drive meaningful impact. Combining expertise in customer experience, technology, and business strategy to deliver valuable insights. Father, open-source contributor, investor, 2xIronman, MBA graduate.

// last updated: March 10, 2026. Disclosure: This article may contain affiliate links.