Skip to content

Networking

Your controllers run on ctrlplane, but what they manage often lives in your network: a Proxmox host, a database, an internal API, network gear. From ctrlplane those addresses are unreachable, and they should stay unreachable from the internet.

A tunnel solves both: your side connects out to ctrlplane, and your controllers’ traffic travels back through that connection. Nothing in your network is opened inbound, no port forwarding, no public IP, and it works behind NAT and CGNAT.

Without a tunnel, controllers reach the public internet only.

controller ──HTTPS_PROXY──▶ tunnel (SOCKS5, on ctrlplane)
▲
║ connector or Tailscale,
║ dialing out from your side
your network
Connector Tailscale or Headscale
You run One Docker container on any machine that can reach your network Nothing new, if you already use a tailnet
It reaches Everything that machine can reach What your subnet routers advertise
Auth A command with a key, shown in the site A reusable, ephemeral auth key you create
Good for Quick setup, home labs, one network Several sites, existing Tailscale setups, ACLs

Turn one on under Network in the site.

The site shows a command like this; run it on a machine in your network:

Terminal window
docker run -d --name ctrlplane-connector --restart=always jpillora/chisel:… client \
--auth <key> --fingerprint <fingerprint> https://<id>.tunnel.ctrlplane.run R:socks

It dials out to https://<id>.tunnel.ctrlplane.run over HTTPS and verifies ctrlplane’s side by its fingerprint. The command contains your tunnel’s key: treat it like a password. Whatever that machine can reach, your controllers can reach.

  1. In your tailnet, create a reusable, ephemeral auth key.
  2. In Network, choose Tailscale or Headscale and paste the key (for Headscale, also its URL).
  3. The tunnel joins your tailnet as ctrlplane-<control plane>. Whatever your subnet routers advertise is reachable through it; limit what it may reach with ACLs, like any other node.

The key is stored for this control plane and never shown again.

With a tunnel on, every controller gets:

Terminal window
HTTPS_PROXY=socks5://tunnel.tenant-<control plane>.svc:1080
HTTP_PROXY=socks5://tunnel.tenant-<control plane>.svc:1080
NO_PROXY=tenant-<control plane>.svc,tenant-<control plane>.svc.cluster.local,<your forwards>

Go programs (and most HTTP clients) honour these, and resolve names at the far end, so your network’s own DNS names work, for example https://proxmox.home.lan:8006. Your control plane itself stays direct.

Some clients ignore HTTPS_PROXY (database drivers, SSH, some SDKs). For those, add a forward as name=host:port, for example:

proxmox=192.168.0.200:8006
postgres=db.home.lan:5432

Your controllers then connect to proxmox:8006 or postgres:5432, and the tunnel carries the connection to the real host, without any proxy settings.

Change settings switches between connector and Tailscale or edits forwards; controllers restart to pick up the change. Turn off removes the tunnel, and controllers go back to public internet only. Your account’s limits decide whether you may connect a network at all.