# Cloudflare Tunnel Setup for Incus Host ## Overview Single routing point for all services across Incus instances. Services self-declare by dropping a file into a shared directory. No port forwarding, no OPNsense reverse proxy config, no DDNS. ## Prerequisites - Domain: ampbox.io (registrar: Namecheap) - Cloudflare account (free tier) with ampbox.io DNS delegated - At Namecheap: set nameservers to the ones Cloudflare assigns - Verify domain is active in Cloudflare dashboard ## 1. Install cloudflared on the Incus host ```bash 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 sudo apt update && sudo apt install -y cloudflared ``` ## 2. Authenticate and create the tunnel ```bash cloudflared tunnel login # opens browser, authorize ampbox.io cloudflared tunnel create ampbox # creates tunnel, saves credentials JSON cloudflared tunnel route dns ampbox "*.ampbox.io" # wildcard DNS route ``` Note the credentials file path printed (typically `~/.cloudflared/.json`). You need it for step 4. ## 3. Create the service declaration directory ```bash sudo mkdir -p /etc/cloudflared/services.d ``` ## 4. Write the main tunnel config ```bash # Replace with your actual tunnel ID from step 2 sudo tee /etc/cloudflared/config.yml << 'EOF' tunnel: ampbox credentials-file: /root/.cloudflared/.json ingress: - service: http_status:404 EOF ``` ## 5. Share the declaration directory into all instances ```bash incus profile device add default svc-declare disk \ source=/etc/cloudflared/services.d \ path=/mnt/services ``` ## 6. Helper scripts (on the host) ### /usr/local/bin/svc-rebuild Reads all service declarations and rebuilds the cloudflared ingress config. ```bash #!/usr/bin/env bash set -euo pipefail CONFIG="/etc/cloudflared/config.yml" SERVICES_DIR="/etc/cloudflared/services.d" TUNNEL_CREDS="/root/.cloudflared/.json" # fix this cat > "$CONFIG" <
> "$CONFIG" done < "$f" done echo " - service: http_status:404" >> "$CONFIG" sudo systemctl restart cloudflared echo "Rebuilt with $(ls "$SERVICES_DIR"/*.yml 2>/dev/null | wc -l) service(s)" ``` ```bash sudo install -m 755 /dev/stdin /usr/local/bin/svc-rebuild < svc-rebuild.sh ``` ### /usr/local/bin/svc-watch Auto-rebuilds on file changes (optional but recommended). ```bash #!/usr/bin/env bash set -euo pipefail SERVICES_DIR="/etc/cloudflared/services.d" echo "Watching $SERVICES_DIR for changes..." while inotifywait -q -e create,delete,modify "$SERVICES_DIR"; do sleep 1 # debounce /usr/local/bin/svc-rebuild done ``` ```bash sudo apt install -y inotify-tools sudo install -m 755 /dev/stdin /usr/local/bin/svc-watch < svc-watch.sh ``` ## 7. Systemd services (on the host) ### /etc/systemd/system/cloudflared.service ```ini [Unit] Description=Cloudflare Tunnel After=network-online.target Wants=network-online.target [Service] ExecStart=/usr/local/bin/cloudflared tunnel run ampbox Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ``` ### /etc/systemd/system/svc-watch.service ```ini [Unit] Description=Service declaration watcher After=cloudflared.service [Service] ExecStart=/usr/local/bin/svc-watch Restart=always [Install] WantedBy=multi-user.target ``` ```bash sudo systemctl daemon-reload sudo systemctl enable --now cloudflared svc-watch ``` ## 8. Declaring services FROM INSIDE an instance ### Expose an HTTP service ```bash cat > /mnt/services/myapp.yml << EOF - hostname: myapp.ampbox.io service: http://$(hostname -I | awk '{print $1}'):8080 EOF ``` ### Expose SSH ```bash cat > /mnt/services/ssh-$(hostname).yml << EOF - hostname: ssh-$(hostname).ampbox.io service: ssh://$(hostname -I | awk '{print $1}'):22 EOF ``` ### Remove a service ```bash rm /mnt/services/myapp.yml # svc-watch picks it up and rebuilds automatically ``` ## 9. Client-side SSH config (on laptops / remote machines) ``` # ~/.ssh/config Host *.ampbox.io ProxyCommand cloudflared access ssh --hostname %h ``` Then: `ssh user@ssh-myinstance.ampbox.io` just works. ## 10. OPNsense cleanup Once the tunnel is verified working, remove from OPNsense: - All per-service reverse proxy rules in os-caddy - Port forward / NAT rules for 80 and 443 - DDNS configuration (no longer needed) OPNsense goes back to just being your firewall. ## Quick Reference | Action | From inside instance | |-------------------------|----------------------------------------------------------| | Expose HTTP | `echo "- hostname: X.ampbox.io\n service: http://IP:PORT" > /mnt/services/X.yml` | | Expose SSH | Same pattern with `service: ssh://IP:22` | | Expose raw TCP | Same pattern with `service: tcp://IP:PORT` | | Remove service | `rm /mnt/services/X.yml` | | List registered | `ls /mnt/services/` | | Check tunnel status | `sudo systemctl status cloudflared` (from host) |