You are currently viewing Cloudflare Tunnel Zero Trust (cloudflared) for Self‑Hosted Apps + Free HTTPS

Cloudflare Tunnel Zero Trust (cloudflared) for Self‑Hosted Apps + Free HTTPS

  • Post author:
  • Post category:Tutorials
  • Post comments:0 Comments
  • Reading time:5 mins read

Table of Contents – Cloudflare Tunnel Zero Trust

Overview

Expose self-hosted apps safely without opening inbound ports. Cloudflare Tunnel creates an outbound-only connection from your host to Cloudflare Edge, gives you free HTTPS, and lets you gate access with Zero Trust (SSO/MFA).

External reference:
Cloudflare Tunnel Docs

Prerequisites

  • A domain on Cloudflare (free plan works).
  • One Linux host that runs the origin app (e.g., localhost:8080).
  • sudo access and a browser for initial login.

Placeholders: app.example.com, my-tunnel, <TUNNEL-UUID>.

Quick Architecture

Cloudflare Tunnel Zero Trust

Install/Setup

Ubuntu / Debian

# Install cloudflared (official repo)
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/ jammy main' | sudo tee /etc/apt/sources.list.d/cloudflare-client.list
sudo apt update && sudo apt install -y cloudflared
cloudflared --version

RHEL / Rocky / CentOS Stream / Fedora

# Install cloudflared (RPM)
sudo rpm -ivh https://pkg.cloudflare.com/cloudflared-latest-x86_64.rpm || sudo dnf install -y https://pkg.cloudflare.com/cloudflared-latest-x86_64.rpm
cloudflared --version

Arch / Manjaro

# Install cloudflared (community or AUR)
sudo pacman -Sy --noconfirm cloudflared || yay -S --noconfirm cloudflared
cloudflared --version

openSUSE / SLE

# Install cloudflared (RPM)
sudo zypper install -y https://pkg.cloudflare.com/cloudflared-latest-x86_64.rpm
cloudflared --version

Base Configuration

Authenticate

# Open a browser to authenticate your account
cloudflared tunnel login
# A cert is saved to ~/.cloudflared/cert.pem (back this up)

Create Tunnel

# Create a named tunnel and note its UUID
cloudflared tunnel create my-tunnel
cloudflared tunnel list

Config File

# ~/.cloudflared/config.yml
tunnel: my-tunnel
credentials-file: /home/$USER/.cloudflared/<TUNNEL-UUID>.json

ingress:
  - hostname: app.example.com
    service: http://localhost:8080
  - service: http_status:404

Route DNS

Goal: point your subdomain (e.g., app.example.com) to the tunnel my-tunnel by creating a DNS CNAME that resolves to <UUID>.cfargotunnel.com.

One-step command

cloudflared tunnel route dns my-tunnel app.example.com

What happens: the command creates:
app.example.com → <UUID>.cfargotunnel.com (proxied at Cloudflare).

Verify

# 1) DNS CNAME exists?
dig +short CNAME app.example.com

# 2) Final resolution (Cloudflare IPs)
dig +short app.example.com

# 3) Tunnel status
cloudflared tunnel list
cloudflared tunnel info my-tunnel
journalctl -u cloudflared -n 50 --no-pager

# 4) External check
curl -I https://app.example.com

Quick fixes

  • No CNAME result: run the command again on the right account/zone; ensure the domain is active on Cloudflare.
  • 502/404: verify your app is listening locally:
    ss -ltnp | grep 8080 and curl -I http://localhost:8080; also confirm hostname in config.yml matches exactly.
  • Down after reboot: enable the service (see next section).

Reload/Enable & Health Checks

# Run as a systemd service (persistent)
sudo cloudflared service install
sudo systemctl enable --now cloudflared
sudo systemctl status cloudflared --no-pager

# Health checks
cloudflared tunnel info my-tunnel
cloudflared tunnel list
sudo journalctl -u cloudflared -n 100 --no-pager
curl -I https://app.example.com

Security/Hardening

Harden the tunnel by putting your app behind identity (Zero Trust), exposing only required paths, and locking down credentials on disk.

A) Gate access with Zero Trust (SSO/MFA)

  1. Cloudflare dashboard → Zero TrustAccessApplicationsAddSelf-hosted.
  2. Application name: any (e.g., My App).
  3. Application domain: your public host (e.g., app.example.com).
  4. Identity providers: pick Google/GitHub/Email OTP.
  5. Policies → Add: Type=Allow; Include emails ending @yourdomain.com; Require MFA.
  6. Save → open https://app.example.com in Incognito → expect Access login first.

Docs: Cloudflare Zero Trust — Self-hosted apps

B) Expose only what you need (limit paths/methods)

Put admin on a separate hostname and return 404 for everything else by default.

# ~/.cloudflared/config.yml
tunnel: my-tunnel
credentials-file: /home/$USER/.cloudflared/<TUNNEL-UUID>.json

ingress:
  - hostname: app.example.com
    service: http://localhost:8080

  - hostname: admin.example.com
    service: http://localhost:8080/admin

  - service: http_status:404

Apply & verify:

sudo systemctl restart cloudflared
sudo systemctl status cloudflared --no-pager

curl -I https://app.example.com/           # 200/301/302
curl -I https://app.example.com/secret     # 404 by default rule
curl -I https://admin.example.com/         # Access login (admins only)

C) Protect & rotate local credentials

Protect these files: ~/.cloudflared/cert.pem and ~/.cloudflared/<TUNNEL-UUID>.json (or /etc/cloudflared/ if root-managed).

# If running as your user
chmod 700 ~/.cloudflared
chmod 600 ~/.cloudflared/*.json ~/.cloudflared/cert.pem

# If running as a system service (root)
sudo chown -R root:root /etc/cloudflared
sudo chmod 700 /etc/cloudflared
sudo chmod 600 /etc/cloudflared/*.json /etc/cloudflared/cert.pem

Performance & Optimization

Test flags in foreground, then persist via systemd.

A) Foreground test (temporary)

cloudflared --version
cloudflared tunnel run my-tunnel   --metrics 127.0.0.1:4321   --protocol http2   --retries 5   --transport-loglevel warn

B) Persist via systemd drop-in

command -v cloudflared
sudo mkdir -p /etc/systemd/system/cloudflared.service.d
sudo tee /etc/systemd/system/cloudflared.service.d/10-flags.conf >/dev/null <<'EOF'
[Service]
Environment="CF_ARGS=--metrics 127.0.0.1:4321 --protocol http2 --retries 5 --transport-loglevel warn"
ExecStart=
ExecStart=/usr/bin/cloudflared --config /etc/cloudflared/config.yml --no-autoupdate tunnel run $CF_ARGS
EOF
sudo systemctl daemon-reload
sudo systemctl restart cloudflared
sudo systemctl status cloudflared --no-pager

Backup & Restore

Backup

tar czf ~/cloudflared-backup.tgz ~/.cloudflared
sha256sum ~/cloudflared-backup.tgz > ~/cloudflared-backup.tgz.sha256

Restore

sha256sum -c ~/cloudflared-backup.tgz.sha256
tar xzf ~/cloudflared-backup.tgz -C ~/
sudo cloudflared service install
sudo systemctl enable --now cloudflared

Troubleshooting (Top issues)

1) Tunnel connects but 502/404 at origin → local app/port mismatch.

ss -ltnp | grep 8080 || echo 'App not listening on 8080'
curl -I http://localhost:8080
tail -n 100 ~/.cloudflared/*.log 2>/dev/null || true

2) Tunnel down after reboot → service not installed/enabled.

sudo systemctl enable --now cloudflared
sudo systemctl status cloudflared --no-pager
journalctl -u cloudflared -n 100 --no-pager

3) DNS not resolving → CNAME missing or wrong zone.

dig +short CNAME app.example.com
cloudflared tunnel route dns my-tunnel app.example.com

Key Takeaways & Next Steps

  • Outbound-only tunnels remove router/NAT changes.
  • Zero Trust (SSO/MFA) protects access behind identity.
  • Back up credentials and config; run as a service for reliability.

 

Leave a Reply