Join a home or office Node (no public IP)

Cloudflare Tunnel is the default inbound path for residential, CGNAT, and office boxes — the agent itself is pull-only and still joins the fleet without it.

Join a home or office Node (no public IP)

A laptop on Wi-Fi, an office PC, or a GPU box behind a consumer ISP usually has no public IPv4 — RFC1918, CGNAT (100.64.0.0/10), or a firewall you do not control. HostSSH still onboards that machine. The agent is pull-only: it dials the control plane, heartbeats, and claims jobs. Nothing inbound is required for the fleet loop.

Inbound is a separate problem: publishing an app, exposing a local port, or reaching a dashboard on that box from the internet. For those, Cloudflare Tunnel is the default path. Native reverse-tunnel (no Cloudflare) is the later vendor-exit; until that ships, named cloudflared is what we document and what hostssh doctor checks.

What you get without a tunnel

After install + license activate:

  • The Node appears in the fleet and heartbeats.
  • Captures, restores, deploys, and prune jobs run (the agent pulls them).
  • You can SSH to the box on your LAN the usual way.

You cannot yet: put a public hostname on an app that lives only on that Node, or use Expose to publish a Node-local port. Those need the tunnel below.

1. Install cloudflared on the Node

On Debian/Ubuntu:

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-get update && sudo apt-get install -y cloudflared

Or grab a release binary from Cloudflare and put it on PATH. Confirm:

cloudflared --version

2. Create a named tunnel and install it as a service

From the Node (one-time, uses your Cloudflare account):

cloudflared tunnel login
cloudflared tunnel create hostssh-home
cloudflared tunnel route dns hostssh-home apps.example.com   # optional hostname
sudo cloudflared service install
sudo systemctl enable --now cloudflared

The service reads /etc/cloudflared/config.yml. HostSSH's expose job can merge hostname → localhost:<port> ingress into that file and reload the service; you can also hand-edit ingress. GPU / long-lived inference origins need the long proxy timeouts documented in GPU workloads.

3. Point the agent at the tunnel

Set the tunnel name (not the token) on the agent:

# /etc/hostssh/agent.env  (or the systemd drop-in for hostssh-agent)
HOSTSSH_CF_TUNNEL=hostssh-home

Then restart the agent:

sudo systemctl restart hostssh-agent

Residential / office installs can also set HOSTSSH_TUNNEL_REQUIRED=1. That does not fail doctor if cloudflared is missing (a box that only heartbeats is still valid) — it changes the warning so you cannot miss it.

4. Prove it with doctor

hostssh doctor

Rows that matter here:

CheckHealthyWhat a warn means
reachabilityok with a public addressprivate/CGNAT only — expected at home; inbound needs the tunnel
cloudflaredbinary + HOSTSSH_CF_TUNNEL + cloudflared.service activeMissing binary, unset name, or service not running

A warn never fails doctor by itself. A fail (Docker missing, engine scripts gone) still exits 1.

hostssh doctor --json

is the machine-readable form (same checks).

5. Expose a port from the dashboard

Once doctor is green on cloudflared, Access → Expose (or an expose job) publishes a Node-local port at the public hostname the tunnel already serves. Teardown is Unexpose. Live proof of a named tunnel on a specific box is still an operator window — the code path is what this page describes.

Honest limits

  • The control plane does not provision your Cloudflare account. You create the named tunnel; we consume HOSTSSH_CF_TUNNEL.
  • cloudflared is not in install.sh. It is optional for VPS nodes with a public IP and Traefik. Home/office nodes that want inbound should install it themselves (Apache-2.0, not in the HostSSH binary).
  • Firewall: allow the agent's egress first (see Hardening). A default-deny inbound policy is correct on a tunnel-first Node — do not punch :80 on a residential NAT.