Skip to main content
Cloudflare Tunnel creates a secure, outbound-only connection from your server to Cloudflare’s network, eliminating the need for public IP addresses or port forwarding.

Why Cloudflare Tunnel?

No port forwarding

Works without opening firewall ports or configuring NAT

DDoS protection

Cloudflare’s network protects against DDoS attacks

Free HTTPS

Automatic SSL/TLS certificates with no configuration

Custom domain

Use your own domain for professional access
Important: Do not use Cloudflare Quick Tunnels (TryCloudflare). They do not support Server-Sent Events (SSE), which HAPI requires for real-time updates. Use a Named Tunnel instead.

Prerequisites

  • Cloudflare account (free tier works)
  • Domain managed by Cloudflare DNS
  • cloudflared CLI installed

Setup

1

Install cloudflared

Download from Cloudflare downloads page:
# macOS
brew install cloudflared

# Linux (Debian/Ubuntu)
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb

# Other platforms: see Cloudflare docs
2

Authenticate with Cloudflare

cloudflared tunnel login
This opens a browser to authenticate with your Cloudflare account.
3

Create a named tunnel

cloudflared tunnel create hapi
Save the tunnel ID displayed (needed for configuration).
4

Configure DNS

Route a subdomain to your tunnel:
cloudflared tunnel route dns hapi hapi.yourdomain.com
5

Create tunnel configuration

Create ~/.cloudflared/config.yml:
tunnel: <your-tunnel-id>
credentials-file: /home/username/.cloudflared/<your-tunnel-id>.json

ingress:
  - hostname: hapi.yourdomain.com
    service: http://localhost:3006
  - service: http_status:404
Replace <your-tunnel-id> with the ID from step 3.
6

Start HAPI hub

export HAPI_PUBLIC_URL="https://hapi.yourdomain.com"
hapi hub
The hub starts on http://localhost:3006.
7

Start Cloudflare Tunnel

cloudflared tunnel --protocol http2 run hapi
Use --protocol http2 instead of QUIC (the default) to avoid timeout issues with long-lived SSE connections.
8

Access your hub

Open https://hapi.yourdomain.com in your browser.

Configuration

HAPI environment variables

export HAPI_PUBLIC_URL="https://hapi.yourdomain.com"
export HAPI_LISTEN_HOST=127.0.0.1  # Keep localhost for security
export HAPI_LISTEN_PORT=3006
hapi hub

Cloudflare Tunnel config.yml

Advanced example with multiple services:
tunnel: <your-tunnel-id>
credentials-file: /home/username/.cloudflared/<your-tunnel-id>.json

ingress:
  # HAPI hub
  - hostname: hapi.yourdomain.com
    service: http://localhost:3006
    originRequest:
      noTLSVerify: false
      connectTimeout: 30s
      keepAliveTimeout: 90s

  # Catch-all rule (required)
  - service: http_status:404
The catch-all rule (http_status:404) is required as the last entry.

Run as background service

Using systemd (Linux)

Create /etc/systemd/system/cloudflared.service:
[Unit]
Description=Cloudflare Tunnel
After=network.target

[Service]
Type=simple
User=username
ExecStart=/usr/local/bin/cloudflared tunnel --protocol http2 run hapi
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
sudo systemctl status cloudflared

Using pm2

pm2 start "cloudflared tunnel --protocol http2 run hapi" --name cloudflared
pm2 start "hapi hub" --name hapi-hub
pm2 save
pm2 startup

Using macOS launchd

Create ~/Library/LaunchAgents/com.cloudflare.tunnel.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.cloudflare.tunnel</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/cloudflared</string>
        <string>tunnel</string>
        <string>--protocol</string>
        <string>http2</string>
        <string>run</string>
        <string>hapi</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>
Load the service:
launchctl load ~/Library/LaunchAgents/com.cloudflare.tunnel.plist

Telegram integration

With Cloudflare Tunnel providing HTTPS, you can enable Telegram Mini App:
export TELEGRAM_BOT_TOKEN="your-bot-token"
export HAPI_PUBLIC_URL="https://hapi.yourdomain.com"
hapi hub
See Telegram Setup for details.

Troubleshooting

Tunnel connects but web app doesn’t load

  1. Verify HAPI_PUBLIC_URL matches your tunnel hostname exactly
  2. Check tunnel is using --protocol http2 (not QUIC)
  3. Ensure HAPI hub is running on port 3006
  4. Test locally: curl http://localhost:3006/api/health

SSE not working / Real-time updates failing

  1. Confirm you’re using a Named Tunnel, not Quick Tunnel (TryCloudflare)
  2. Add to config.yml under originRequest:
    keepAliveTimeout: 90s
    noHappyEyeballs: true
    
  3. Use --protocol http2 when starting tunnel

DNS not resolving

  1. Verify DNS record created: cloudflared tunnel route dns hapi hapi.yourdomain.com
  2. Check Cloudflare dashboard → DNS → Records
  3. DNS propagation can take a few minutes

Connection timeouts

  1. Increase timeout in config.yml:
    originRequest:
      connectTimeout: 60s
      keepAliveTimeout: 120s
    
  2. Ensure no firewall blocking localhost:3006

Comparison with relay

FeatureRelayCloudflare Tunnel
Setup complexityOne commandInitial setup required
Custom domainNoYes
EncryptionWireGuard + TLSTLS via Cloudflare
LatencyDirect peer-to-peerThrough Cloudflare network
CostFreeFree (Cloudflare Free tier)
DDoS protectionNoYes
ConfigurationZeroTunnel config + DNS

When to use Cloudflare Tunnel

Ideal when you:
  • Want a custom domain (e.g., hapi.yourcompany.com)
  • Need DDoS protection and Cloudflare features
  • Have a Cloudflare account and domain
  • Prefer managed infrastructure over relay
  • Want team access with consistent URL

Next steps

Telegram Setup

Enable Telegram Mini App (now that you have HTTPS)

Runner Setup

Configure background runner for remote sessions

Resources

Build docs developers (and LLMs) love