How to Use Cloudflare Tunnel to Expose Your VPS Without Opening Firewall Ports
Cloudflare Tunnel creates an outbound-only encrypted connection from your VPS to Cloudflare’s network — your services become accessible via Cloudflare’s edge without opening any inbound ports on your VPS firewall. No exposed port 80, no exposed port 443, no public IP directly reachable by attackers. Cloudflare terminates all public HTTPS connections and forwards traffic through the encrypted tunnel. This is the most security-hardened way to expose VPS services publicly.
Why Cloudflare Tunnel Over Standard Nginx + SSL
- Zero inbound ports: UFW blocks all inbound traffic; the tunnel is outbound-only. Port scans, direct IP attacks, and DDoS against your VPS IP are all useless.
- Automatic SSL: Cloudflare handles SSL certificates — no Certbot, no renewal management.
- DDoS protection: Cloudflare absorbs attacks at its edge before traffic reaches your VPS.
- No IP exposure: Your VPS IP is never publicly associated with your domain.
- Free tier: Cloudflare Tunnel is free for personal and small business use.
Prerequisites
- A Cloudflare account (free at cloudflare.com)
- Your domain’s nameservers pointed to Cloudflare
- A VPS running your services (locally on any port)
Step 1: Install cloudflared on VPS
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb
cloudflared version # Verify installation
Step 2: Authenticate with Cloudflare
cloudflared tunnel login
This outputs a URL. Open it in your browser, log into Cloudflare, select your domain, and click Authorize. A certificate file is saved to ~/.cloudflared/cert.pem.
Step 3: Create a Named Tunnel
cloudflared tunnel create my-vps-tunnel
# Output includes your tunnel UUID:
# Created tunnel my-vps-tunnel with id abc12345-def6-7890-abcd-ef1234567890
# List all tunnels
cloudflared tunnel list
Step 4: Configure the Tunnel (Multi-Service)
nano ~/.cloudflared/config.yml
tunnel: abc12345-def6-7890-abcd-ef1234567890 # Your tunnel UUID
credentials-file: /root/.cloudflared/abc12345-def6-7890-abcd-ef1234567890.json
ingress:
# Main website — Nginx backend on port 8080
- hostname: yourdomain.com
service: http://localhost:8080
# API — FastAPI on port 8000
- hostname: api.yourdomain.com
service: http://localhost:8000
# Grafana monitoring — port 3001
- hostname: grafana.yourdomain.com
service: http://localhost:3001
# Gitea — port 3000
- hostname: git.yourdomain.com
service: http://localhost:3000
# JupyterLab — port 8888 (with WebSocket support)
- hostname: jupyter.yourdomain.com
service: http://localhost:8888
originRequest:
noTLSVerify: false
connectTimeout: 30s
# Required: catch-all for unmatched hostnames
- service: http_status:404
Step 5: Create DNS Records
# Creates CNAME records in Cloudflare DNS automatically
cloudflared tunnel route dns my-vps-tunnel yourdomain.com
cloudflared tunnel route dns my-vps-tunnel api.yourdomain.com
cloudflared tunnel route dns my-vps-tunnel grafana.yourdomain.com
cloudflared tunnel route dns my-vps-tunnel git.yourdomain.com
cloudflared tunnel route dns my-vps-tunnel jupyter.yourdomain.com
Step 6: Run as systemd Service
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
sudo systemctl status cloudflared
Step 7: Lock Down the Firewall Completely
With the tunnel running, close all inbound ports except SSH:
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH from your IP only (most secure)
sudo ufw allow from YOUR_HOME_IP to any port 22
# Or allow SSH from anywhere (less secure but practical)
# sudo ufw limit 22/tcp
# Do NOT open ports 80 or 443 — tunnel handles all web traffic
sudo ufw enable
sudo ufw status verbose
WebSocket Configuration
For services using WebSockets (Jupyter, real-time apps, n8n), add to the ingress entry:
- hostname: realtime.yourdomain.com
service: http://localhost:4000
originRequest:
connectTimeout: 30s
http2Origin: true # Improves WebSocket performance
Also ensure WebSockets are enabled in Cloudflare Dashboard → your domain → Network → WebSockets (enabled by default).
Cloudflare Access: Add Authentication to Internal Tools
Combine Cloudflare Tunnel with Cloudflare Access to add SSO authentication to internal services without configuring auth in each app:
- Cloudflare Dashboard → Zero Trust → Access → Applications → Add application
- Enter the hostname (
grafana.yourdomain.com) - Set policy: allow specific email addresses, GitHub org members, or Google Workspace domain
Users are redirected to Cloudflare’s login page before reaching your service — no Grafana auth configuration required.
Monitor Tunnel Status
# View tunnel status and connection metrics
cloudflared tunnel info my-vps-tunnel
# View logs
sudo journalctl -u cloudflared -f
# Test tunnel connectivity
curl -I https://yourdomain.com
Getting Started
Cloudflare Tunnel works on any Ubuntu VPS at VPS.DO — no special networking required. The tunnel creates outbound connections to Cloudflare’s edge, which any standard VPS supports. Combine with KVM VPS running Coolify or Dokku for a zero-inbound-port deployment that is developer-friendly and security-hardened.
Conclusion
Cloudflare Tunnel eliminates the VPS’s public-facing attack surface: no open ports 80 or 443, no direct IP exposure, no SSL certificate management, and DDoS protection at Cloudflare’s edge. The outbound-only tunnel model means your VPS IP can be fully firewalled while all web services remain publicly accessible. For security-conscious deployments, this is a meaningful improvement over the traditional open-port-plus-SSL approach.