Relayium

Server-to-server transfers with the Relayium CLI (daemon direct)

Last updated: 2026-08-05

When both machines are yours and each knows the other's address, SSH is extra friction and a rendezvous is pure overhead. Daemon direct is built for exactly this: one server listens, the other pushes straight to it over a pinned TLS 1.3 connection. No relay, no SSH, no pairing code — trust is public-key and set up once.

This guide covers starting the listener, pushing to it, approving a new pusher on first contact, automating it, and running the listener as a systemd service.

Before you start

Everything below is the relayium CLI, so install it first if you haven't. On macOS or Linux, one command drops a prebuilt binary on your PATH:

curl -fsSL https://relayium.com/install.sh | sh

Start the listener (on the receiver)

What you need

  • Two machines you control, with the receiver's address reachable from the sender. A hostname or a bare IP both work.
  • relayium on both ends. Daemon direct speaks the native protocol only, so there is no tar fallback to rescue a missing install here.
  • The listener's port open to the sender — 9031/TCP unless you change it — in the host firewall and in any cloud security group.
  • A terminal on the receiver for the first push, so you can answer the approval prompt. With no terminal, pre-authorize the sender instead (see below).

On the receiving server, serve listens for pushes and writes them into a directory. It's long-running by default; add --once to accept a single transfer and exit. You don't pre-share anything — no fingerprints to copy up front:

  1. Create the directory pushes should land in.

    mkdir -p ~/inbox
  2. Open the listener's port to the sender only. Substitute the sender's own address for 203.0.113.7 — its public IP, or its private one when the two servers share a network — and scope the cloud security group to that same source rather than to the whole internet.

    sudo ufw allow from 203.0.113.7 to any port 9031 proto tcp
  3. Start the listener in a terminal, so there is someone to answer the approval prompt on the first push. Add --once to take a single transfer and exit, or --port to move off 9031.

    relayium serve --dir ~/inbox

What a running listener looks like

serve names the address it bound, the directory it writes into, and this host's own fingerprint. With no approved peers yet, it also says it will ask about each new one.

relayium serve --dir ~/inbox
no authorized peers yet — you'll be asked to approve each new peer on its first push.
relayium serve: listening on [::]:9031, receiving into /home/you/inbox (fingerprint 5c1d9f04…)

Push to it (on the sender)

From the sending server, push to the receiver's relayium:// address. The first connection pins the receiver's fingerprint; every connection after that verifies it, and a changed fingerprint is refused rather than silently accepted — so a swapped key or a man-in-the-middle is caught, not trusted. On the very first push, the sender waits a moment while the receiver approves it (next step).

  1. Run the push from the sending server. On the very first connection it stops right here, while the receiver approves it.

    relayium push ./build.tar.zst relayium://receiver.example.com
  2. Answer the prompt on the receiver — that is the next section. The push then finishes on its own, and later pushes never stop here again.

  3. Append a port when the listener isn't on 9031.

    relayium push ./build.tar.zst relayium://receiver.example.com:9040

What a successful push looks like

On first contact the sender learns and pins the listener's fingerprint, then transfers. The receiver records the pusher's fingerprint and reports the file and byte count.

# on the SENDER, first contact
learned receiver.example.com:9031 5c1d9f04… (added to known_hosts)
  build.tar.zst (48213004 bytes)

# on the RECEIVER
authorized 74318e3b… (added to /home/you/.config/relayium/authorized_fingerprints)
received 1 file(s), 48213004 bytes from 74318e3b…

Approve the sender on first push (on the receiver)

The first time a new machine pushes to your listener, serve (in a terminal) shows you where it's from and its fingerprint and asks you to approve it — like SSH's first-connect prompt, but on the receiving side:

# on the RECEIVER, when a new sender pushes:
Incoming push from 203.0.113.7:54021
  fingerprint: 74318e3b…
Accept and remember this peer? [y/N] y

Automate it (or run without a terminal)

Because an approved fingerprint is remembered, later pushes need no prompt — so relayium push drops straight into cron, a deploy script, or CI for encrypted, integrity-checked, resumable server-to-server sync. When serve runs without a terminal (a systemd service, a pipe) it can't prompt, so it rejects unknown pushers; pre-authorize them instead. Get the fingerprint from the pusher's relayium id, or copy it from the "rejected unauthorized peer …" line in the serve log, then:

# on the RECEIVER: pre-authorize a sender without a prompt
relayium authorize 74318e3b...

Run the listener under systemd

For an always-on inbox, run serve as a systemd service. Point --config-dir at a fixed location like /etc/relayium so the identity is stable across restarts, and let systemd keep it alive:

# /etc/systemd/system/relayium-serve.service
[Unit]
Description=Relayium daemon-direct listener
After=network-online.target

[Service]
ExecStart=/usr/local/bin/relayium serve --dir /srv/inbox --config-dir /etc/relayium
Restart=always
User=relayium

[Install]
WantedBy=multi-user.target

When a push doesn't get through

Reachability and trust are the first two things to check: ss -tinp on the sender says whether the listener was reached at all, and relayium authorize on the receiver grants the trust a rejected pusher is missing. They are not the only ways a push can fail — a receiver out of disk space, an inbox its user cannot write into, or a transferred file whose integrity check fails all report themselves — so read the error in front of you rather than assuming it is one of the four below.

Symptom, check, fix

The push sits there, then fails with a connection error.
# on the SENDER, while the push is running — run it twice, a few seconds apart
ss -tinp dst :9031
# ESTAB    the listener was reached; says nothing about progress
# SYN-SENT nothing answered on that port

SYN-SENT means the packets never reached a listening socket. Confirm serve is up on the receiver with ss -tlnp | grep 9031, then open 9031/TCP to the sender in the host firewall and the cloud security group. ESTAB proves reachability only — an established socket can sit idle or stall — so to separate a live transfer from a stalled one, run the check twice a few seconds apart and compare the bytes_acked counter that -i prints for that socket. There is no relay path here, so an unreachable listener is a hard failure rather than a slow one.

The serve log says "rejected unauthorized peer …" and the push fails.
# on the SENDER
relayium id
# 74318e3b…

# on the RECEIVER
relayium authorize 74318e3b…

serve had no terminal to ask on — a systemd unit or a pipe — so an unknown fingerprint is refused rather than trusted. Pre-authorize it: the fingerprint in the rejection line is exactly the one relayium id prints on the sender, and authorize is idempotent.

"fingerprint mismatch for receiver.example.com:9031".
grep receiver.example.com ~/.config/relayium/known_hosts

The listener presented a different key than the one pinned on first contact. If you rotated that key on purpose, delete the matching known_hosts line and push again. If you did not, leave the line alone and find out why the key changed before sending anything.

The systemd unit dies at startup with an insecure-permissions error.
systemctl status relayium-serve
# secure: /etc/relayium/id.key has insecure permissions 0644; run: chmod 600 /etc/relayium/id.key
ls -l /etc/relayium/id.key

relayium refuses to load a private key that anyone but its owner can read, the same rule ssh applies. Run chmod 600 on the path the error names, make sure it is owned by the service user, and restart the unit.

Frequently asked questions

How is daemon direct different from push over SSH?

push over SSH tunnels the transfer through your SSH connection and needs an SSH account on the remote. Daemon direct needs no SSH and no account — the two servers authenticate each other by certificate fingerprint over pinned TLS, which is lighter when both machines are yours.

Do I have to copy fingerprints around by hand?

No. In a terminal, serve prompts you to approve each new pusher on its first push — showing its address and fingerprint — and remembers it, so later pushes are silent. You only reach for relayium id or relayium authorize for non-interactive setups like a systemd service, where there's no one to answer the prompt.

Where are the identity and trust files?

In ~/.config/relayium/ by default (override with --config-dir). id.key / id.crt are this host's persistent identity, known_hosts holds fingerprints of listeners you've pushed to, and authorized_fingerprints is the listener's allow-list of pushers.

What happens if a fingerprint changes?

The push refuses and warns. The listener's key is pinned in known_hosts on first use, so a later change — a re-keyed host, or a man-in-the-middle — is rejected rather than silently accepted. Remove the known_hosts line only if you intentionally rotated the key.

Is there any relay fallback?

No. Daemon direct assumes a reachable listener address; if the connection can't be made, it fails. Nothing is ever proxied through Relayium — that's the point of this mode.

Wire up two of your own servers for direct transfers — no relay, no SSH, no pairing code.

Get the CLI

Keep reading