Run Relayium as an always-on receive service
Last updated: 2026-08-06
relayium serve --once handles a single incoming transfer and exits — fine for a one-off pull. But if you want a machine to be a standing drop point — a home server that backups land on overnight, a build box that CI pushes artifacts to, a NAS your phone can send photos to whenever — you want serve running all the time, not started by hand for each transfer.
This guide covers starting a long-running listener, approving who's allowed to push to it, pre-authorizing peers for the cases where no one's at the terminal, running it under systemd, and letting a sync --delete sender mirror deletions.
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
- Prefer to pick the file yourself, or on Windows? Grab a binary from the releases page — relayium.com/cli lists every install option (or go build -o relayium ./cmd/relayium if you have Go).
- relayium --version confirms it's installed. Skip this and the commands below just print 'command not found'.
Start the listener
What you need before step 1
- The CLI on both machines. relayium version prints a version string on each; a shell that answers command not found means it is not installed there yet.
- A directory for incoming files on this machine, and the disk to hold what will land in it.
- An address the sender can reach and an open inbound port. serve listens on 9031 unless you pass --port.
- If this machine will run without a terminal — which is the whole point of a service — the pusher's fingerprint, ahead of time. There is a section on that below, and it is the single most common reason an always-on receiver rejects everything.
serve listens for daemon-direct pushes (relayium://host:port) over a pinned TLS 1.3 connection and writes what it receives into a directory. Nothing needs to be pre-shared to start it — no fingerprints to copy, no server to register:
relayium serve --dir ~/inbox
relayium serve --dir /srv/drop --port 9040 # non-default port
relayium serve --dir ~/inbox --allow-delete # let a sync --delete sender mirror deletions
Pick where files should land and start the listener. Nothing has to be pre-shared to get this far.
relayium serve --dir ~/inboxOn the sending machine, push something at this host by its relayium:// address.
relayium push ./report.pdf relayium://drop.example.com:9031Back on the receiver, answer the approval prompt. y writes that fingerprint to authorized_fingerprints, and later pushes from the same machine never prompt again.
Confirm the file actually landed in --dir rather than the directory you started serve from.
ls -l ~/inbox
What a working listener looks like
serve says up front that it has no authorized peers, then prompts on the first push from a new machine and stays silent on every later one. The sender exits 0 and the file is in --dir.
$ relayium serve --dir ~/inbox
no authorized peers yet — you'll be asked to approve each new peer on its first push.
Incoming push from 203.0.113.7:54021
fingerprint: 74318e3b…
Accept and remember this peer? [y/N] y- --dir sets where files land (defaults to the current directory).
- --port sets the listening port (defaults to 9031); open it on your firewall if the sender is elsewhere.
- With no --once, serve keeps running and accepting pushes until you stop it or a process manager restarts it — that's what makes it an always-on service.
Approve who's allowed to push
The first time a new peer pushes, serve — if it's running in a terminal — shows you where the push came from and its fingerprint and asks you to approve it, the same way SSH asks about an unknown host on first connect:
Incoming push from 203.0.113.7:54021
fingerprint: 74318e3b…
Accept and remember this peer? [y/N] y
- Answer y once and that fingerprint is written to authorized_fingerprints; every later push from the same machine goes through with no prompt.
- A fingerprint identifies a machine, not a network address, so it survives the sender's IP changing.
- This is interactive by design — it needs someone at the keyboard, which won't be true once serve moves to systemd (next).
Pre-authorize peers for non-interactive setups
When serve has no terminal to prompt on — a systemd service, a background process, a pipe — it can't ask, so it rejects any fingerprint it doesn't already recognize. Authorize peers ahead of time instead. On the machine that will push, run relayium id to print its fingerprint; on the receiver, add it before the first push arrives:
# on the machine that will PUSH: print its fingerprint
relayium id
# on this always-on RECEIVER: authorize it in advance
relayium authorize 74318e3b...
On the machine that will push, print its fingerprint. It is 64 hex characters and identifies that machine, not its address.
relayium idOn this receiver, authorize it — with the same --config-dir the service will run under. Authorize as the wrong user, or with the default path while the unit uses another, and the fingerprint lands in a file the service never reads.
relayium authorize 74318e3b… --config-dir /etc/relayium
- authorize is idempotent — running it again for a fingerprint you already trust does nothing.
- Identity and trust files live under --config-dir, defaulting to ~/.config/relayium (id.key/id.crt is this host's identity, authorized_fingerprints is the peer allow-list).
Run it under systemd
For a service that survives reboots and crashes, hand serve to systemd. Point --config-dir at a fixed path so the host's identity and its authorized-peer list stay put across restarts:
# /etc/systemd/system/relayium-serve.service
[Unit]
Description=Relayium always-on receiver
After=network-online.target
[Service]
ExecStart=/usr/local/bin/relayium serve --dir /srv/drop --port 9031 --config-dir /etc/relayium --allow-delete
Restart=always
User=relayium
[Install]
WantedBy=multi-user.target
Authorize every peer that should be able to push, before the service exists. It cannot prompt, so anything not already trusted is rejected.
Write the unit file above to /etc/systemd/system/relayium-serve.service, with --config-dir pointing at the same fixed path you authorized under.
Reload systemd and start the service, enabling it so it comes back after a reboot.
sudo systemctl daemon-reloadsudo systemctl enable --now relayium-serveConfirm it is running and that the reject-everything warning is absent from its log. Only the second of those is specific to this setup.
systemctl is-active relayium-servejournalctl -u relayium-serve -n 20 --no-pager
What a working service looks like
is-active answers active, and the startup warning about having no authorized peers does not appear. That warning is the one line that tells you, before any sender complains, that this service will refuse every push.
$ systemctl is-active relayium-serve
active
$ journalctl -u relayium-serve -n 20 --no-pager | grep -c 'all pushes will be rejected'
0- Before enabling the service, run relayium authorize <fingerprint> for every peer that should be able to push — the service itself can't prompt.
- systemctl enable --now relayium-serve starts it and brings it back on every boot.
- /etc/relayium/id.key must be mode 0600 exactly, owned by the unit's User=. relayium refuses anything else — including 0400, so hardening it past 0600 stops the service just as surely as leaving it world-readable.
Run it at boot on macOS (launchd)
macOS has no systemd — its service manager is launchd. To keep serve running on a Mac (a Mac mini left on as a drop point, say), install it as a LaunchDaemon so it starts at boot, before anyone logs in. Set UserName so it runs as you rather than root, and give --dir and --config-dir absolute paths so its identity and trust files stay in your own ~/.config/relayium:
<!-- /Library/LaunchDaemons/com.relayium.serve.plist (replace YOU with your macOS username) -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key> <string>com.relayium.serve</string>
<key>UserName</key> <string>YOU</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/relayium</string>
<string>serve</string>
<string>--dir</string> <string>/Users/YOU/inbox</string>
<string>--port</string> <string>9031</string>
<string>--config-dir</string> <string>/Users/YOU/.config/relayium</string>
<string>--allow-delete</string>
</array>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>StandardOutPath</key> <string>/Users/YOU/relayium-serve.log</string>
<key>StandardErrorPath</key> <string>/Users/YOU/relayium-serve.log</string>
</dict>
</plist>
# 1) authorize each pusher first — launchd gives serve no terminal to prompt on:
relayium authorize <fingerprint> # get the fingerprint from the pusher's relayium id
# 2) save the plist above to that path, then load it (root-owned, starts at boot):
sudo chown root:wheel /Library/LaunchDaemons/com.relayium.serve.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.relayium.serve.plist
# check it's running / follow logs / stop it:
sudo launchctl print system/com.relayium.serve | grep state
tail -f ~/relayium-serve.log
sudo launchctl bootout system/com.relayium.serve
- launchd gives serve no terminal, so it can't prompt — run relayium authorize <fingerprint> for each pusher first, the same as with systemd. The fingerprint comes from that machine's relayium id.
- KeepAlive restarts serve if it crashes; RunAtLoad plus a LaunchDaemon in /Library/LaunchDaemons starts it at boot without a login — the right fit for a headless Mac mini.
- Want a login-scoped service instead? Put the same plist (drop the UserName key) in ~/Library/LaunchAgents/ and load it with launchctl bootstrap gui/$(id -u) <path> — it starts when you log in rather than at boot.
- If the macOS application firewall is on, allow incoming connections for relayium (System Settings → Network → Firewall), or pushes to your port are blocked.
Let a sync --delete sender mirror deletions
By default serve only ever adds or updates files — a sender running sync --delete against it still copies new and changed files, but any deletions it asks for are skipped, with a warning logged on the receiver. Start serve with --allow-delete to opt in to true mirroring, where files removed on the sender's side are removed here too:
relayium serve --dir /srv/mirror --allow-delete
- --allow-delete is a receiver-side opt-in; the sender still has to ask for it with sync --delete.
- Without it, nothing is ever deleted on this machine, no matter what a sender requests.
When it doesn't work
Four of these five failures leave the service running and looking healthy — a listener that rejects everything is still a listener. Each has a line to read or a command to run that settles it.
Symptom, check, fix
- The service starts and stays up, but every push is rejected.
journalctl -u relayium-serve -n 20 --no-pager # warning: no authorized peers and no terminal to approve on; all pushes will be rejected.A service has no terminal, so it can never run the first-push approval prompt, and an unknown fingerprint is all it can see. Pre-authorize each sender: relayium id on the sending machine, relayium authorize <fingerprint> here — with the same --config-dir the unit uses. That warning is printed at startup, so it is in the log from the first line.
- The service will not start at all, complaining about insecure permissions on id.key.
stat -c '%a %U %n' /etc/relayium/id.key # 400 relayium /etc/relayium/id.keyThe key must be exactly 0600. Not 0644, and — the part that catches people — not 0400 either, so tightening it further breaks the service just as surely as loosening it. chmod 600 the key and make sure it is owned by the unit's User=.
- The sender reports that the connection was refused.
relayium push ./build relayium://drop.example.com:9031 # hint: if the peer refused the connection, it may not have authorized this host.The receiver did not recognise this sender. Run relayium id on the sender and relayium authorize that fingerprint on the receiver. If you already did, check you did it under the unit's --config-dir: the trust file is per-directory, and a fingerprint authorized into ~/.config/relayium is invisible to a service reading /etc/relayium.
- Pushes work when you run serve by hand, but not through the service or not from another machine.
sudo ss -tlnp | grep 9031Two separate causes with one check. If nothing is listening, the unit is not enabled — systemctl is-enabled relayium-serve. If it is listening, the port is blocked: open 9031 on the host firewall and in any cloud security group. A non-default --port must match the port in the sender's relayium://host:N.
- A sync --delete sender's deletions never happen on this machine.
journalctl -u relayium-serve | grep -i deleteDeletion is a receiver-side opt-in and is off by default: new and changed files still copy, and each skipped deletion is logged here as a warning. Add --allow-delete to the unit's ExecStart and restart. The sender asking for it is not enough, and that asymmetry is deliberate — a receiver never loses files because of a flag typed somewhere else.
Frequently asked questions
What port does serve listen on by default?
9031. Change it with --port on both the listener (serve --port N) and the pusher's target (relayium://host:N).
Do I have to approve every single push by hand?
Only the first push from a given fingerprint, and only when serve is running with a terminal attached. It's remembered after that. Running serve non-interactively (systemd, a pipe) skips the prompt entirely and rejects unknown peers — pre-authorize them with relayium authorize instead.
Can a sender delete files on my always-on receiver?
Only if you started serve with --allow-delete and the sender is running sync --delete. Without --allow-delete, deletions are silently skipped and everything else still transfers.
Is running an always-on receiver free?
Yes. relayium serve is part of the free, self-hostable CLI — no account, no paid tier, on either side of the connection.
Where does serve keep its identity and peer list?
In ~/.config/relayium by default (id.key/id.crt for this host's identity, authorized_fingerprints for the allow-list). Point --config-dir somewhere fixed, like /etc/relayium, for a systemd service.
How do I run serve at boot on macOS?
macOS has no systemd — use launchd. Install serve as a LaunchDaemon in /Library/LaunchDaemons (starts at boot; set UserName to run as you), or as a LaunchAgent in ~/Library/LaunchAgents (starts at login). This guide has a ready-to-edit plist; there's no Homebrew service. Pre-authorize pushers with relayium authorize first, since launchd gives serve no terminal to prompt on.
Turn any machine you own into an always-on, free receiver — direct pushes over pinned TLS, no relay involved.
Get the CLI