This page is in English — onze technische documentatie voor ontwikkelaars en beheerders is in het Engels.
How the Relay Works
How a device connects to a Viaalink Relay, how an address reaches that device, which ports and protocols are involved, what is encrypted where, and how connections recover.
The short version
A machine that wants to publish something opens one outbound SSH connection to your Relay. Over that connection it asks the Relay to accept traffic on its behalf. When a visitor opens the address, the Relay sends the request back down the same SSH connection to the machine, and the machine hands it to the local service.
Nothing on the publishing machine listens on the internet. It needs no open port and no router rule — only the ability to reach the Relay’s SSH port (2222).
The parts
| Part | Runs on | What it does |
|---|---|---|
Relay server (viaalink-relay) | Your server, NAS, or PC | Accepts SSH connections from devices, hands out addresses, receives visitor traffic, and carries it to the right device. Also serves the setup dashboard (/_relay/dashboard) and, when enabled, calls through firewalls. |
| Caddy (optional, in front) | Same machine | Takes public ports 80 and 443, holds the Let’s Encrypt certificates, and passes requests to the Relay’s HTTP listener on 8080. See Run Relay with Docker. |
| Client | The machine you publish from | Either standard OpenSSH (ssh -R) or the client built into the Relay binary (viaalink-relay connect). via publish starts the built-in client for you. |
| Visitor | Anywhere | Opens the address in a browser or app. Needs nothing installed and no key. |
The path of one request
Visitor's browser
| https://myapp.relay.example.com (TCP 443)
v
+-------------------------------------------------+
| Relay server |
| Caddy :443 certificate; HTTPS ends here |
| | plain HTTP inside the machine |
| v |
| HTTP listener :8080 finds who owns "myapp" |
| | |
| v |
| SSH listener :2222 that device's session |
+-------------------------------------------------+
^
| ONE SSH connection, opened by the device
| (TCP 2222, encrypted). Each request travels
| back down it as a new SSH channel.
|
+-------------------------------------------------+
| Your machine |
| client: ssh -R ... or viaalink-relay connect |
| | |
| v |
| local app localhost:3000 |
+-------------------------------------------------+Step by step: a device connects
- TCP connection. The device connects to the Relay’s SSH listener (
--ssh-address, port2222in every package). It must finish logging in within five seconds (--cleanup-unauthed-timeout, default5s), or the Relay closes the connection. - SSH handshake. The Relay proves who it is with its SSH host key, kept in the folder set by
--private-keys-directoryand created on first start when that folder is empty. Everything after this point is encrypted. - Login. The device proves who it is with an SSH key. The Relay compares the key with the approved keys in
--authentication-keys-directory: every file there may hold one or more lines inauthorized_keysformat, and the folder is re-read every 200 ms, so adding or deleting a file takes effect without a restart. A shared password (--authentication-password) is possible but off by default. A device gets three tries per connection. In license mode the Relay also checks that a new device fits your plan’s number of connected devices. - Asking for addresses. The device sends standard SSH remote-forward requests — the same ones
ssh -Rsends. The Relay decides what each request becomes from the name and the port in it (see the next section) and replies with what it assigned. - The welcome text. If the device opens an SSH session (plain
sshdoes;viaalink-relay connectdoes too), the Relay writes--welcome-messageand one line per address, such asHTTPS: https://k3x.relay.example.comorTCP Alias: mybox:22. This text is the authoritative statement of the address the Relay assigned. - Traffic. For every visitor connection, the Relay opens a new channel to the device over the existing SSH connection. The client connects that channel to the local service (for example
localhost:3000) and copies bytes both ways.
The Relay keeps no state for a device beyond its open connection: when the SSH connection closes, every address it held is removed.
How devices get approved
A device’s public key reaches --authentication-keys-directory in one of three ways:
- the operator copies the
.pubfile into the folder (Access Control and Security); - the operator pastes it, or generates a new key pair, on the Users tab of
/_relay/dashboard; - the device registers itself by sending its public key to
/_relay/registertogether with an invite code the operator created on the dashboard (or with no code when the operator allows open registration). This endpoint only answers when the Relay runs with a license file.
What a request turns into
The name and port a device asks for decide the kind of address:
| The device asks for | Example | Becomes | Who can reach it |
|---|---|---|---|
| a name with port 80 or 443 | ssh -R myapp:80:localhost:3000 | a web address, https://myapp.relay.example.com | Anyone, unless you add a password per link or IP/country rules |
localhost (or an IP) with another port | ssh -R 5432:localhost:5432 | a public TCP port on the Relay | Anyone who can reach that port |
| a name with another port | ssh -R mybox:22:localhost:22 | a private alias, mybox:22 | Only other devices logged in to the same Relay — see SSH through the Relay |
Names and ports are chosen by the Relay unless you change these defaults:
--bind-random-subdomains(on by default) gives every web address a random three-character name. Turn it off to let devices keep the name they ask for.--bind-random-aliases(on by default) does the same for private aliases.--bind-random-ports(on by default) gives every public TCP forward a random port from--port-bind-range.
With random names on, a device gets a new address each time it reconnects. The welcome text and the client’s output always show the current one.
An address can also carry a path (--bind-http-path) or a password per link (user:password@myapp:80:localhost:3000, allowed while --bind-http-auth is on).
Ports and protocols
| Port | Protocol | Opened by | Purpose |
|---|---|---|---|
2222 | TCP (SSH) | Publishing devices and SSH users, inbound to the Relay | Device connections, private aliases, and SSH through the Relay |
80 | TCP (HTTP) | Visitors and Let’s Encrypt | Plain HTTP, redirect to HTTPS, HTTP-01 certificate checks |
443 | TCP (HTTPS) | Visitors | Every published web address |
8080 | TCP (HTTP) | Caddy on the same machine; your browser on the LAN | The Relay’s own HTTP listener behind Caddy, and the setup dashboard before HTTPS is set up |
8443 | TCP (HTTPS) | — | The Relay’s own HTTPS listener, used only with --https |
| random | TCP | Anyone | Public TCP forwards, when you use them |
3478 | UDP | The Viaalink app | Calls through firewalls (STUN and TURN) |
49152–49167 | UDP | The Viaalink app | Media for calls through firewalls, one port per stream |
4222 | TCP | The Viaalink app, optional | Messaging (--nats-enabled) |
4223 | TCP (WebSocket) | Phones, optional | Messaging for phone clients |
The binary’s compiled defaults are localhost:2222, localhost:80 and localhost:443; the Linux, Docker and ASUSTOR setups change them to :2222, :8080 and :8443 so Caddy can own 80 and 443. The full router guidance is on Requirements and Network Architecture.
Only the Relay needs open ports. Publishing machines and SSH users connect out to 2222 and need nothing open themselves.
Security model
What is encrypted where
| Hop | Protection |
|---|---|
| Visitor → Relay | HTTPS with the Relay’s Let’s Encrypt certificate. Port 80 stays plain HTTP unless you redirect it (--force-all-https). |
| Caddy → Relay HTTP listener | Plain HTTP, but only inside the Relay machine or its private container network. |
| Relay → device | Inside the device’s SSH connection (encrypted and integrity-checked). |
| Device → local service | Whatever the local hop is — usually plain traffic to localhost on the same machine. |
HTTPS ends at the Relay: the Relay has to read the request to know which device it belongs to, so whoever runs the Relay could, in principle, see web traffic in the clear. When that is not acceptable:
- use TLS passthrough (
--sni-proxy): the Relay routes on the name in the TLS handshake and your own service holds the certificate; - or publish a protocol that encrypts by itself — SSH over a private alias, for example. The Relay copies those bytes without being able to read them.
Who proves what to whom
| Who | Proves itself to | How |
|---|---|---|
| Device | Relay | SSH key listed in the approved keys folder (or the shared password, if you turned it on) |
| Relay | Device | SSH host key. OpenSSH checks it against ~/.ssh/known_hosts and warns when it changes. The built-in client (viaalink-relay connect) currently accepts the host key without checking it — use OpenSSH when you need the Relay’s identity pinned. |
| Relay | Visitor | Its Let’s Encrypt certificate |
| Visitor | Relay | Nothing by default. Optional: a password per link, IP and country rules (Access Control and Security) |
| Operator | Setup dashboard | The setup code (--setup-token, or the generated setup-token.txt) |
Keeping the connection alive
From the Relay’s side
The Relay sends a keepalive request to every device every 5 seconds (--ping-client, --ping-client-interval). A connection that stays silent for that interval plus --ping-client-timeout (5 seconds) is closed, and its addresses are removed. A device that disappears without saying goodbye is gone from the Relay within about ten seconds.
From the built-in client’s side
viaalink-relay connect (and so via publish) keeps the connection up by itself:
- Keepalive: it sends a keepalive every
--keepaliveseconds (default10), waits--keepalive-timeoutseconds (default5) for each answer, and treats three missed answers in a row as a dead connection. - Reconnect: with
--reconnect(on by default) it reconnects after a drop, waiting 1 second before the first attempt and doubling the wait up to 60 seconds, with up to a quarter extra at random so many devices do not return at the same moment. - Fresh start: after reconnecting it asks for every address again. With random names on, the addresses change — the new ones are printed as soon as the Relay assigns them.
With plain OpenSSH
OpenSSH does not reconnect on its own. Add ServerAliveInterval and ExitOnForwardFailure to the connection and run it under a service manager that restarts it, or use viaalink-relay connect instead. SSH through the Relay has a ready-made ~/.ssh/config.
Asking a Relay what it serves
To see which domains a Relay serves without publishing anything, connect with your key and read its policy:
viaalink-relay info --server relay.example.com:2222 --key ~/.ssh/id_ed25519Nothing is bound and nothing appears in the Relay’s list of connections. Add --json for machine-readable output.