Skip to content

Hot's Pizza Payphone

Cloudflare Tunnel Setup Guide for Raspberry Pi

Target Setup for hotspizza.org: - admin.hotspizza.org → Web portal (localhost:5000) - ssh.hotspizza.org → Browser-based SSH (localhost:22)

Table of Contents

  1. Overview
  2. Prerequisites
  3. Installing cloudflared
  4. Authentication
  5. Creating the Tunnel
  6. Configuration
  7. DNS Routing
  8. Running as a Service
  9. Browser-Based SSH
  10. Testing
  11. Troubleshooting

Overview

What is Cloudflare Tunnel?

Cloudflare Tunnel (formerly Argo Tunnel) creates a secure outbound-only connection from your Raspberry Pi to Cloudflare's edge network. This allows you to expose local services without: - Port forwarding - Exposing your home IP address - Complex firewall rules

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                         INTERNET                                     │
│                                                                      │
│    Browser ──────► admin.hotspizza.org ──────► Cloudflare Edge      │
│                    ssh.hotspizza.org                                 │
└───────────────────────────────┬─────────────────────────────────────┘
                                │
                                │ Encrypted Tunnel (outbound from Pi)
                                │
┌───────────────────────────────▼─────────────────────────────────────┐
│                      RASPBERRY PI                                    │
│                                                                      │
│   ┌─────────────┐    ┌──────────────────┐    ┌──────────────────┐  │
│   │ cloudflared │───►│ localhost:5000   │    │ localhost:22     │  │
│   │   daemon    │    │ (uvicorn/FastAPI)│    │ (SSH server)     │  │
│   └─────────────┘    └──────────────────┘    └──────────────────┘  │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Two Tunnel Management Approaches

Approach Pros Cons
Locally-Managed (CLI) Full control, config in version control, works offline More manual setup
Remotely-Managed (Dashboard) Easy UI, no config files Requires dashboard access to modify

This guide uses locally-managed tunnels for maximum control and reproducibility.


Prerequisites

Before starting, ensure you have:

  1. Cloudflare Account (free tier is sufficient)
  2. Domain added to Cloudflare with nameservers pointed to Cloudflare
  3. In our case: hotspizza.org
  4. Raspberry Pi with:
  5. 64-bit OS (check with uname -m → should show aarch64)
  6. Internet connectivity
  7. SSH access

Verify Your Pi Architecture

uname -m
# Expected output: aarch64 (64-bit ARM)

Note: Pi Zero W (original), Model 1B, and 2B may have issues with cloudflared. Pi Zero 2 W with 64-bit OS works fine.


Installing cloudflared

This method provides automatic updates:

# Add Cloudflare's GPG key
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null

# Add the repository
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list

# Install
sudo apt update && sudo apt install cloudflared -y

# Verify
cloudflared --version

Method 2: Direct .deb Package

If the APT method fails:

# Download the ARM64 .deb package
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb -o /tmp/cloudflared.deb

# Install
sudo dpkg -i /tmp/cloudflared.deb

# Verify
cloudflared --version

Method 3: Direct Binary

Fallback if .deb has issues:

curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64 -o /tmp/cloudflared
chmod +x /tmp/cloudflared
sudo mv /tmp/cloudflared /usr/local/bin/cloudflared
cloudflared --version

Authentication

Log in to Cloudflare to authorize tunnel creation:

cloudflared tunnel login

This will: 1. Print a URL to the terminal 2. Open that URL in a browser (or copy/paste it) 3. Ask you to select which domain to authorize 4. Generate ~/.cloudflared/cert.pem (account certificate)

Select your domain (e.g., hotspizza.org) when prompted.

Verify Authentication

ls -la ~/.cloudflared/
# Should show: cert.pem

Creating the Tunnel

Create a named tunnel:

cloudflared tunnel create payphone

This outputs:

Tunnel credentials written to /home/pi/.cloudflared/<TUNNEL-UUID>.json
Created tunnel payphone with id <TUNNEL-UUID>

Save the UUID - you'll need it for the config file.

List Tunnels

cloudflared tunnel list

(Optional) Rename Credentials File

For clarity, rename the credentials file:

mv ~/.cloudflared/<TUNNEL-UUID>.json ~/.cloudflared/payphone.json

Configuration

Create the tunnel configuration file:

nano ~/.cloudflared/config.yml

Basic Config (Web Portal Only)

tunnel: payphone
credentials-file: /home/hots/.cloudflared/payphone.json

ingress:
  - hostname: admin.hotspizza.org
    service: http://localhost:5000
  - service: http_status:404

Full Config (Web Portal + SSH)

tunnel: payphone
credentials-file: /home/hots/.cloudflared/payphone.json

ingress:
  # Web portal
  - hostname: admin.hotspizza.org
    service: http://localhost:5000

  # SSH (browser-rendered)
  - hostname: ssh.hotspizza.org
    service: ssh://localhost:22

  # Catch-all (required)
  - service: http_status:404

Configuration Options Explained

Field Description
tunnel Tunnel name or UUID
credentials-file Path to the JSON credentials file
ingress List of hostname → service mappings
service: http_status:404 Catch-all for unmatched requests (required)

Validate Config

cloudflared tunnel ingress validate

DNS Routing

Create CNAME records that point your subdomains to the tunnel:

# Web portal
cloudflared tunnel route dns payphone admin.hotspizza.org

# SSH (if using)
cloudflared tunnel route dns payphone ssh.hotspizza.org

Each command creates a CNAME record in Cloudflare DNS pointing to <TUNNEL-UUID>.cfargotunnel.com.

Verify DNS Records

Check in Cloudflare Dashboard → DNS → Records, or:

cloudflared tunnel route ip show

Running as a Service

Install as System Service

# Copy config to system location
sudo mkdir -p /etc/cloudflared
sudo cp ~/.cloudflared/config.yml /etc/cloudflared/config.yml
sudo cp ~/.cloudflared/payphone.json /etc/cloudflared/payphone.json

# Update paths in config (change /home/hots to /etc/cloudflared)
sudo sed -i 's|/home/hots/.cloudflared|/etc/cloudflared|g' /etc/cloudflared/config.yml

# Install the service
sudo cloudflared service install

# Enable and start
sudo systemctl enable cloudflared
sudo systemctl start cloudflared

# Check status
sudo systemctl status cloudflared

View Tunnel Logs

sudo journalctl -u cloudflared -f

Browser-Based SSH

Cloudflare can render an SSH terminal directly in the browser. This is convenient because: - No SSH client needed on the connecting device - Works from any browser (phone, tablet, etc.) - Can be protected with Cloudflare Access policies

How It Works

When you visit https://ssh.hotspizza.org: 1. Cloudflare authenticates you (optional Access policy) 2. Renders a terminal in the browser 3. Proxies SSH traffic through the tunnel

Setup Steps

  1. Tunnel config (already done above): ```yaml
  2. hostname: ssh.hotspizza.org service: ssh://localhost:22 ```

  3. DNS route (already done above): bash cloudflared tunnel route dns payphone ssh.hotspizza.org

  4. (Recommended) Add Access Policy:

  5. Go to Cloudflare Zero Trust Dashboard
  6. Applications → Add Application → Self-hosted
  7. Set domain: ssh.hotspizza.org
  8. Add authentication (email OTP, Google, etc.)

Native SSH (Alternative)

If you prefer using a native SSH client, you need cloudflared on your local machine too:

On your local machine:

# Install cloudflared locally
brew install cloudflared  # macOS
# or download from GitHub for your OS

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

Then SSH normally:

ssh user@ssh.hotspizza.org

Testing

Test Tunnel Locally

Before running as a service, test manually:

cloudflared tunnel run payphone

Leave this running and test in another terminal or browser.

Test Web Portal

curl https://admin.hotspizza.org
# Should return your web portal HTML

Or open in browser: https://admin.hotspizza.org

Test SSH (Browser)

Open in browser: https://ssh.hotspizza.org

Should show a terminal login prompt.

Check Tunnel Info

cloudflared tunnel info payphone

Troubleshooting

Common Issues

1. "failed to connect to origin"

Cause: Local service not running

Fix: Ensure uvicorn/FastAPI is running on port 5000:

systemctl --user status payphone-web

2. "Unable to reach the origin service"

Cause: Wrong port or service not listening

Fix: Verify the service is listening:

ss -tlnp | grep 5000

3. DNS not resolving

Cause: CNAME not created or propagating

Fix: Check Cloudflare DNS dashboard, wait a few minutes, or:

dig admin.hotspizza.org CNAME

4. "Tunnel credentials file not found"

Cause: Wrong path in config.yml

Fix: Verify the credentials file path matches your config:

ls -la ~/.cloudflared/
cat ~/.cloudflared/config.yml

5. Certificate errors

Cause: cert.pem missing or expired

Fix: Re-authenticate:

cloudflared tunnel login

Useful Debug Commands

# Check tunnel status
cloudflared tunnel info payphone

# List all tunnels
cloudflared tunnel list

# Validate config
cloudflared tunnel ingress validate

# Test connectivity
cloudflared tunnel run --loglevel debug payphone

# Check systemd service
sudo systemctl status cloudflared
sudo journalctl -u cloudflared --since "10 minutes ago"

Port 7844 Required

Cloudflared needs outbound access to port 7844 (QUIC). If behind a restrictive firewall:

# Test connectivity
cloudflared tunnel connectivity

Quick Reference Commands

# Install
sudo apt install cloudflared

# Authenticate
cloudflared tunnel login

# Create tunnel
cloudflared tunnel create <NAME>

# Add DNS route
cloudflared tunnel route dns <NAME> <HOSTNAME>

# Run manually
cloudflared tunnel run <NAME>

# Install as service
sudo cloudflared service install

# Service management
sudo systemctl start cloudflared
sudo systemctl stop cloudflared
sudo systemctl restart cloudflared
sudo systemctl status cloudflared

# View logs
sudo journalctl -u cloudflared -f

# Tunnel info
cloudflared tunnel info <NAME>
cloudflared tunnel list

References


Appendix: Full Setup Script

Here's a complete script for reference (adjust paths as needed):

#!/bin/bash
set -e

TUNNEL_NAME="payphone"
DOMAIN="hotspizza.org"
WEB_HOSTNAME="admin.$DOMAIN"
SSH_HOSTNAME="ssh.$DOMAIN"
CREDS_FILE="/etc/cloudflared/${TUNNEL_NAME}.json"
CONFIG_FILE="/etc/cloudflared/config.yml"

echo "=== Installing cloudflared ==="
sudo mkdir -p --mode=0755 /usr/share/keyrings
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 any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared -y

echo "=== Authenticating ==="
cloudflared tunnel login

echo "=== Creating tunnel ==="
cloudflared tunnel create $TUNNEL_NAME

# Get tunnel UUID
TUNNEL_UUID=$(cloudflared tunnel list | grep $TUNNEL_NAME | awk '{print $1}')

echo "=== Setting up config ==="
sudo mkdir -p /etc/cloudflared
sudo cp ~/.cloudflared/${TUNNEL_UUID}.json $CREDS_FILE

sudo tee $CONFIG_FILE > /dev/null << EOF
tunnel: $TUNNEL_NAME
credentials-file: $CREDS_FILE

ingress:
  - hostname: $WEB_HOSTNAME
    service: http://localhost:5000
  - hostname: $SSH_HOSTNAME
    service: ssh://localhost:22
  - service: http_status:404
EOF

echo "=== Creating DNS routes ==="
cloudflared tunnel route dns $TUNNEL_NAME $WEB_HOSTNAME
cloudflared tunnel route dns $TUNNEL_NAME $SSH_HOSTNAME

echo "=== Installing service ==="
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared

echo "=== Done! ==="
echo "Web portal: https://$WEB_HOSTNAME"
echo "SSH:        https://$SSH_HOSTNAME"
sudo systemctl status cloudflared