tollan docs

Device groups (private networking)#

A device group lets your devices talk to each other by name — mymac-anna-myteam.internal — without anything being exposed to the internet. There's no public DNS record, no certificate, no public listener: an internal name is unreachable from outside the group by construction, not by obscurity.

Use it for the machine-to-machine half of a fleet: a sensor pushing to a collector, SSH between your own boxes, a dashboard reading a database on another site — all over the devices' existing tunnels.

How it works#

Every device already holds an authenticated tunnel to the relay. A group connection uses those tunnels end to end: the initiating agent asks the relay to connect it to a service on an internal name, the relay checks that both devices are members of a group the service is published to (and the same organization, and the route is enabled — anything else is refused), then splices the two tunnel legs together. The receiving device just sees an ordinary incoming channel to its local target.

  • Internal names never exist publicly. They're never written to DNS, never issued a certificate, and the public ingress won't route them — .internal is an ICANN-reserved TLD that cannot publicly resolve.
  • Plain TCP works. Unlike public hostname routes (which route on TLS SNI), the internal plane doesn't need the connection to be TLS — SSH, Postgres, HTTP, MQTT all work as-is.
  • The privacy note. Group traffic is mTLS-protected between each device and the relay, but the relay splices the stream, so for the strongest posture run TLS (or mTLS — your devices already have CA-issued certs) inside the group connection. Self-encrypting protocols like SSH give you this for free.

Set it up#

1. Create the group. Console → Fleet → GroupsNew group. Add the member devices. Membership is the authorization boundary — only members can reach (or be reached by) the group's routes.

2. Publish a service to the group. On the serving device's page, add a route with Exposure: internal (group) (the option appears once the device is in a group), pick the group, and set the internal target as usual — e.g. 127.0.0.1:22. The console shows where members reach it:

mymac-anna-myteam.internal:22

The name is {device}-{username}-{group}.internal — the same device-username shape as the device's public address, plus the group it is published to. Services are reached there on their own ports, exactly like a machine on your LAN: publish several services (one route per port) and they all answer at the same name. A device published to two groups answers to one name per group; the group also decides who may connect.

**3. Enable peer sync on the devices that will initiate connections** (Go agent). Add to the device's agent.env:

PEERS_URL=https://app.<your-apex>/device/peers

(the same origin as TARGETS_URL). Optional: PEERS_REFRESH_INTERVAL (default 15m) and HOSTS_FILE (default /etc/hosts). The agent then maintains a managed hosts-file block mapping each peer's name to a local loopback address and listens there on each published port, so the names resolve only on group members.

Two practical notes:

  • Writing /etc/hosts and binding a low port (like :22 for SSH parity) needs privileges — for a systemd unit, AmbientCapabilities=CAP_NET_BIND_SERVICE covers the port part.
  • Devices that only receive group connections need no changes at all — not even an agent update. Older agents and ESP32 boards can host group services as they are; initiating from an ESP32 uses the library's dialPeer(hostname, port) call.

4. Connect from any member device:

bash
ssh pi@mymac-anna-myteam.internal                 # by name, as typed
curl http://collector-anna-myteam.internal/       # port 80
psql "host=dbbox-anna-myteam.internal port=5432 …"

Group SSH vs public SSH#

Both work — they solve different problems:

Public port routeInternal group route
Reachable fromanywhere on the internetmember devices only
Addressrelay-host:PORTdevice-username-group.internal (+ the service port)
Exposurea real public listener (use keys!)none — nothing to scan
Client needsany SSH clientthe device's agent with peer sync

If only your own devices ever need to reach a box, an internal route is strictly the safer choice: there is no public artifact to protect.

Limits & notes#

  • Group traffic transits the relay (hub-and-spoke, not peer-to-peer) and shares the device's tunnel with its public traffic.
  • The receiving agent's target allowlist still applies — the relay can't steer a device to a target its operator didn't authorize, group or no group.
  • Initiating devices need an agent speaking tollan.v2; the console flags devices whose agent needs an update. Receivers work on any version.