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?
| Approach | Open Ports | IP Exposed | DDoS Protection | SSL |
|---|---|---|---|---|
| Cloudflare Tunnel | None | Hidden | Built-in | Automatic |
| Reverse proxy (Nginx) | 80, 443 | Yes | None | Manual (Let’s Encrypt) |
| Port forwarding | Any | Yes | None | Manual |
| VPN + access | VPN port | Hidden | None | Via 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
cloudflaredruns on your VPS and creates an outbound connection to Cloudflare’s edge- Traffic hits your domain → Cloudflare routes it through the tunnel → reaches your service
- Your VPS never accepts inbound connections — it only dials out
- Cloudflare handles SSL, DDoS protection, and caching automatically
No ports open. No IP exposed. No attack surface.
Prerequisites
- A domain on Cloudflare (free plan works)
- A VPS running Linux (any provider)
- Services running locally (web apps, APIs, dashboards)
Best VPS for Cloudflare Tunnel
| Provider | Plan | Price | Why |
|---|---|---|---|
| Hostinger | KVM1 | $4.99/mo | Best value, solid performance |
| Hetzner | CX22 | €3.79/mo | Cheapest for EU |
| Vultr | VC2 | $6/mo | Global locations |
| DigitalOcean | Basic | $6/mo | Simple 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.pemback 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:
YOUR_TUNNEL_UUIDwith your tunnel ID- Hostnames and ports with your actual services
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
Token-Based Setup (Recommended for Docker)
Instead of config files, use the Cloudflare Zero Trust dashboard:
- Go to Zero Trust Dashboard → Networks → Tunnels
- Create a tunnel → get the token
- Configure routes in the dashboard
- 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
- Zero Trust Dashboard → Access → Applications → Add
- Set application domain (e.g.,
grafana.yourdomain.com) - Add policy: Allow → Emails ending in
@yourdomain.com
Users see a Cloudflare login page before reaching your app.
One-Time PIN
- Add policy: Allow → Emails → specific email addresses
- Authentication: One-time PIN
- 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
| Feature | Cloudflare Tunnel | Nginx + Let’s Encrypt | Tailscale Funnel |
|---|---|---|---|
| Open ports | None | 80, 443 | None |
| DDoS protection | Yes | No | No |
| SSL | Automatic | Manual renewal | Automatic |
| Custom domain | Yes | Yes | Limited |
| Access control | Zero Trust | DIY | Tailscale ACLs |
| Price | Free | Free | Free (limited) |
| Speed | Fast (edge cache) | Direct | Good |
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
- Use Zero Trust policies — Don’t expose admin panels to the entire internet
- Rotate tunnel tokens — Delete and recreate tunnels periodically
- 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
- Monitor access logs — Zero Trust dashboard shows all requests
- 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
| Feature | Free | Pro ($20/mo) | Business ($200/mo) |
|---|---|---|---|
| Tunnels | Unlimited | Unlimited | Unlimited |
| Zero Trust users | 50 | 50 | 50 |
| DDoS protection | Basic | Enhanced | Advanced |
| WAF rules | Limited | 20 | 100+ |
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.
Recommended Setup
| Use Case | Setup | Notes |
|---|---|---|
| Single web app | Quick tunnel | cloudflared tunnel --url |
| Multiple services | Named tunnel + config | One tunnel, multiple ingress rules |
| Team access | Tunnel + Zero Trust | Email or SSO authentication |
| Production | Docker + dashboard token | Easy 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.
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
// related guides
$1 VPS Hosting 2026: Cheapest VPS Servers Starting at $1/Month
Looking for $1 VPS hosting? Compare the cheapest VPS providers starting from $1-3/month. Real specs, no hidden fees, honest reviews of budget VPS options.
tutorialCaddy Reverse Proxy Guide 2026: Automatic HTTPS Made Easy
Set up Caddy as a reverse proxy with automatic HTTPS, zero-config SSL, and simple Caddyfile syntax. Complete VPS deployment guide.
tutorialCoolify VPS Setup Guide 2026: Self-Hosted Vercel Alternative
Deploy Coolify on your VPS for a self-hosted Vercel/Netlify experience. Complete setup guide with Docker, SSL, and app deployments.
tutorialVPS with Crypto & No KYC — 7 Providers That Accept Bitcoin
Pay for VPS hosting with Bitcoin, Monero, or ETH — no ID required. We list 7 privacy-first providers with anonymous signup. Updated March 2026.
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.