tollan docs

Routes & exposure#

A route connects a public address to an internal target. Until a device has a route, its tunnel is up but nothing is exposed. Routes are created per device and take effect on the relay immediately — no restart.

Anatomy of a route#

  • Exposure mode — how it's reached: hostname (SNI) or port publicly, or internal (group-only, never public).
  • Public address — the hostname or port assigned (or chosen).
  • Internal target — the host:port the agent bridges the connection to.
  • Enabled — a toggle to take the route on- or offline without deleting it.

Exposure modes#

Hostname (SNI)#

The route is published at a subdomain — by default your-slug.tollan.app — served on the relay's shared :443. The relay reads the SNI from the incoming TLS handshake and demuxes to the owning device. Because routing happens on the unencrypted SNI field, the relay never has to decrypt your traffic — this is passthrough, and it's what makes the tunnel zero-knowledge.

Use hostname mode for HTTPS and other TLS services. It scales to many devices on one IP and one port.

Port#

The route is published at a dedicated public TCP port on the relay, e.g. relay.tollan.ie:19000. The relay opens a listener per port-route and forwards everything on it to the device.

Use port mode for non-HTTP TCP protocols — SSH, databases, MQTT — or when the client can't send SNI. You can preview the next free port in the console before committing. See Accessing a host over SSH for a complete walkthrough.

Internal (group)#

The route is published only inside a device group, at a name like mymac22.tollan.myteam.internal. No public DNS, certificate, or listener exists for it — only member devices can resolve and reach it, over their authenticated tunnels. The option appears in the console once the device belongs to a group.

Use internal mode for machine-to-machine traffic between your own devices: SSH between boxes, a sensor pushing to a collector, a dashboard reading a remote database.

Hostname (SNI)Port
Reached atname.tollan.apprelay-host:PORT
Best forHTTPS / TLS servicesarbitrary TCP
Relay sees plaintext?No (passthrough)Only if you use terminate mode
Uses shared :443YesNo (own port)

Internal targets#

The internal target is where the agent connects on your side:

  • 127.0.0.1:8080 — a service on the same host as the agent (handler/local).
  • 192.168.1.50:80 — a different host on the LAN (forwarding mode).
  • [::1]:8080 — an IPv6 target; wrap the address in square brackets (see below).

The agent will only ever bridge to targets it's allowed to. That allowlist is the security boundary — see below.

IPv6 targets#

IPv6 targets work exactly like IPv4 ones — the agent dials them natively — with one rule: always wrap the address in square brackets so the :port stays unambiguous.

  • [::1]:8080, [fd12:3456::50]:443, and link-local with a zone [fe80::1%eth0]:22.
  • fd12:3456::50:443 — a bare (unbracketed) address is ambiguous (which colon begins the port?). It may save, but the agent can't parse it and the connection silently never forms. Bracket it.

The device itself must have IPv6 configured with a working route to that address (a service listening on ::1, a ULA, or a link-local address). The bracketed target is what goes on the device's allowlist; the console keeps the allowlist and the route in sync for you.

Forwarding mode#

One agent can front many hosts on its network. In forwarding mode the agent dials the relay-named host:port and pipes bytes, so a single cheap box (or ESP32) becomes a proxy for a printer, NAS, camera, or PLC.

The device only dials targets on its allowlist. The relay can name a target when it opens a channel, but if it isn't on the device's allowlist the channel is refused. This is defense in depth: even a compromised relay can't pivot your device to an arbitrary internal host.

Keep the device's allowlist in lockstep with its route internal-targets:

  • Go agent — set LOCAL_TARGETS (and optionally sync from the control plane via TARGETS_URL).
  • ESP32 library — set cfg.forwardTargets / cfg.defaultTarget.

Passthrough vs terminate#

Every hostname route has a TLS mode that decides where the visitor's TLS connection ends — and that one choice drives everything else: who holds the certificate, what the relay can see, and which access rules can be enforced at the edge.

Passthrough#

The relay reads only the (unencrypted) SNI field of the TLS handshake to pick the owning device, then splices the still-encrypted stream through the tunnel. Your service terminates TLS itself, with its own certificate.

  • Zero-knowledge — the relay never holds a key for your traffic and never sees plaintext. This is the strongest privacy stance Tollan offers.
  • Your certificate, your problem — the device must serve a valid TLS certificate itself.
  • Edge enforcement is limited by design — the relay can't read (or inject) HTTP into a stream it can't decrypt, so HTTP-layer rules like basic auth cannot be applied at the edge. Only the IP allowlist (pure connection metadata) is enforced; anything app-layer is your service's job.

Terminate#

The relay's edge terminates TLS using a managed certificate (the platform wildcard for *.tollan.app subdomains, or an auto-issued ACME certificate for a custom domain), then forwards the decrypted request to your device over the mTLS tunnel.

  • Managed HTTPS — no certificate to install on the device; the platform issues and renews it.
  • Edge gatekeeping works — because the edge sees the request, it can enforce access rules before anything reaches your device: basic auth challenges, IP allowlists.
  • The trade — the relay sees plaintext for this route. That's an explicit, per-route choice, never a default you stumble into.

Choosing#

PassthroughTerminate
Visitor's TLS ends atyour device/servicethe relay edge
Certificateyours, on the devicemanaged by the platform
Relay sees plaintextneveryes, for this route
Basic auth at the edgeno — enforce in your appyes
IP allowlist at the edgeyes (connection metadata)yes
Best forservices that already speak HTTPS; maximum privacyplain-HTTP services, edge auth, custom domains

Rules of thumb: if the service on your device already serves HTTPS and you want the relay blind, use passthrough. If the service speaks plain HTTP, or you want Tollan to hold the door (basic auth in front of an internal dashboard), use terminate. Platform subdomains created from the console use terminate automatically — that's what makes https://your-slug.tollan.app work with zero TLS setup on the device.

Non-TLS TCP (SSH, databases, MQTT): TLS mode doesn't apply — use a port route, which is always a raw passthrough splice. See the SSH guide for a worked example.

Custom domains#

On paid plans you can attach your own domain to a route instead of a *.tollan.app subdomain. Add the domain in the console, point DNS at the relay, and Tollan obtains and renews a TLS certificate for it automatically (ACME). The relay then routes that hostname's SNI to your device.

How a route change reaches the relay#

You never touch relay config. When you create, edit, enable, or delete a route:

  1. The control plane persists the change and notifies the relay.
  2. The relay picks up the updated routing over its authenticated, private channel.
  3. The relay atomically swaps the table — existing tunnels stay up, and new routing takes effect at once.

That's why route edits feel instant and never drop unrelated traffic.


Next: put a gate in front of a route with Access control.