Skip to content

Run the relay yourself

The relay lets a town hall with no public address serve agents anywhere: the town hall and the agents both connect out to the relay, and the relay passes their messages along. Myrtille Cloud runs one for you at relay.myrtille.eu; this page is the same relay on your own server. It is part of the repository (apps/cloud, command myrtille-relay), under the Apache 2.0 licence like the rest.

You do not need a relay if your town hall already has an https address your agents can reach (Cloud features, self-hosted).

  • Stateless. A relay is one or more gateways. Each gateway checks every token by itself (its signature, its expiry, its scope), then passes each message to the gateway that holds the other end through Redis. Run one gateway with no Redis at all, or several behind one address with one Redis between them; any of them can stop, and its connections open again on another.
  • Signed tokens. When your town hall goes through a relay, the tokens it gives its agents name their organisation, town, agent, what they allow (local, docker, enrol or townhall) and when they expire, signed with the key of the town. The relay never stores a token, and hands the town hall only a hash of it; the town hall still checks each token as before, so revoking an agent in the town works at once.
  • One town per key. The key of each town is derived from the key of the relay. A town hall holds its own key only: it can sign tokens for its own town and no other, and the relay never carries a token of one organisation to the town hall of another. A town whose key may have leaked is re-keyed alone: its old key stops linking at once, and the other towns are untouched.
  • Sealed between gateways. What the gateways say to each other through Redis is encrypted and authenticated with a key derived from the relay key, which Redis never holds: a writer on Redis can neither read a message nor slip one in.
  • Logs only pass through. Live container logs, like every other message, stay in memory and cross Redis by publish and subscribe, which Redis never writes to disk. Redis holds only short session keys and per-minute counters. The relay logs ids, counts and error codes, never the content of a message.
  • Checked messages. Every message is checked against the protocol, in both directions, and written again from what was checked: a field the protocol does not name is refused.
  • Limits. Each agent may send 3,000 messages and 64 MiB a minute, wherever it connects; an address may open 60 connections and try 10 enrolments a minute on a gateway.

You need Node 22.19 or later (or 24.6 or later) and the repository, until the relay gets its own image.

  1. Build it once:

    Terminal window
    git clone https://github.com/myrtillehq/myrtille.git
    cd myrtille
    corepack enable
    pnpm install --frozen-lockfile
    pnpm --filter "@myrtillehq/cloud..." build
  2. Make the key of the relay and keep it secret, like a password:

    Terminal window
    node apps/cloud/dist/relay-gateway/bin.js new-key

    Put it in the environment of the relay as MYRTILLE_RELAY_SIGNING_KEY. Never put it in a file of a repository.

  3. Start the relay behind your reverse proxy with TLS (the relay listens on port 7430 by default):

    Terminal window
    MYRTILLE_RELAY_SIGNING_KEY=... MYRTILLE_RELAY_TRUSTED_PROXIES=1 node apps/cloud/dist/relay-gateway/bin.js serve

    With several gateways, give them the same key and the same Redis: MYRTILLE_RELAY_REDIS_URL=rediss://... (TLS; redis:// is refused beyond the machine itself). Give the relay a Redis user of its own, with a password, limited to the relay:* keys and channels, and turn persistence on: Redis holds session keys that expire within a minute, per-minute counters, and the generation of each town you re-keyed, which must survive a restart. No message content is ever stored there.

  4. Give your town hall the key of its town. A self-hosted town hall is the organisation self; its town is the id of your town plan:

    Terminal window
    MYRTILLE_RELAY_SIGNING_KEY=... node apps/cloud/dist/relay-gateway/bin.js town-key self harbour --secrets-file harbour.env

    The key is written into that new file (readable by you only), never on the screen. Copy its MYRTILLE_RELAY_KEY (it starts with g1.) into the secret store of the town hall, with MYRTILLE_RELAY_URL=https://relay.example.com, delete the file, and restart the town hall. Its log says linked to the relay.

    A relay that serves the town halls of several operators sets MYRTILLE_RELAY_SHARED=true: it then refuses the organisation self, and each operator’s town hall gets an organisation id of its own (MYRTILLE_ORG_ID), which you pass to town-key instead of self.

  5. Enrol your agents through the relay. Create an enrolment token in the town as usual, then on the workstation:

    Terminal window
    myrtille connect https://relay.example.com

    and paste the token when it asks (Claude Code). The agent is the same program: only the address differs. Agents enrolled before the relay keep connecting to the town hall directly until you enrol them again.

Variable What it does
MYRTILLE_RELAY_SIGNING_KEY The key of the relay (required).
MYRTILLE_RELAY_SIGNING_KEY_PREVIOUS The previous key while you change it: agent tokens it signed stay valid until they expire.
MYRTILLE_RELAY_REDIS_URL The Redis of several gateways (rediss://). Without it, one gateway keeps everything in memory.
MYRTILLE_RELAY_SHARED true when the relay serves several operators: the organisation self is refused.
MYRTILLE_RELAY_FLOORS_FILE A file where rekey --floors-file writes the generation of each re-keyed town, signed: the gateways read it at start.
MYRTILLE_RELAY_PORT The port (7430 by default).
MYRTILLE_RELAY_HOST The address it listens on (0.0.0.0 by default).
MYRTILLE_RELAY_TRUSTED_PROXIES How many reverse proxies stand in front of it (0 by default).
MYRTILLE_RELAY_OUTBOUND_IPS The outbound addresses to publish (see below).
  • The key of one town (the MYRTILLE_RELAY_KEY of one town hall): re-key that town alone with node apps/cloud/dist/relay-gateway/bin.js rekey <org> <town> --secrets-file <new file>. This needs the Redis of the relay. Every gateway closes the links of that town signed with the old key at once and refuses them from then on; give the town hall the new key and restart it. Its agents reconnect by themselves: their own tokens stay valid, since the town hall checks each one. Redis keeps the generation of each re-keyed town, signed; pass --floors-file <file> to rekey and give the same file to the gateways as MYRTILLE_RELAY_FLOORS_FILE, so that a gateway started after Redis lost its data still refuses the old key. Without that file, re-key those towns again after a loss of Redis, before you restart the gateways.
  • The key of the relay: make a new one, write the new key of every town with it, start the gateways with the new key and the old one as MYRTILLE_RELAY_SIGNING_KEY_PREVIOUS, then give every town hall its new key. The old key is then accepted for agent tokens only, never for a town hall link or an enrolment. Remove it after 31 days, once the town halls have renewed their agents’ tokens; removing it at once refuses every agent it signed, which must then be enrolled again.

A relay closes a town hall link when its token expires (the town hall sends a new one every few minutes), and an agent connection when the agent’s token expires.

Myrtille Cloud calls the API of your Coolify from a few fixed addresses, so that you can allow only them. The relay publishes them at /relay/v1/outbound-ips, as JSON (ipv4 and ipv6 lists): for the cloud, https://relay.myrtille.eu/relay/v1/outbound-ips. Add each to the allowed IPs of your Coolify API. On your own relay, list your town hall’s outbound addresses in MYRTILLE_RELAY_OUTBOUND_IPS (addresses or CIDR ranges, separated by commas) if you want them published the same way.