Relayium

Sync a large folder between two servers (resumable, in the background)

Last updated: 2026-08-05

You have a large folder — tens of gigabytes — on one server and you want an exact copy on another. You can't watch a terminal for hours, and a transfer that dies halfway shouldn't start over from zero. relayium sync is built for this: a one-way incremental mirror that skips what's already there, resumes a half-sent file from where it stopped, and verifies every file it sends, end to end.

This guide sets up an unattended, self-healing transfer: authorize the sender once, run the listener in the background, and drive relayium sync from a retry loop inside tmux so it keeps going across dropped connections until the whole folder has landed.

Why relayium sync fits this job

sync is a one-way incremental mirror over the native protocol (install relayium on both ends). Three properties make it safe to run and re-run unattended:

Prerequisites

What you need

  • This guide uses daemon direct (relayium://), so the two servers don't need SSH access to each other.
  • Open the listener's port (9031 by default) to the sender on the receiver's firewall or security group.
  • Room for the whole folder on the receiver. Compare du -sh /root/workspace on the sender against df -h /root on the receiver before you start a multi-hour transfer.

Install relayium on both servers (sync speaks the native protocol, so it must be present on each end):

# on BOTH servers
curl -fsSL https://relayium.com/install.sh | sh

Authorize the sender once (on the receiver)

The receiver approves the sending machine one time; the approval is written to disk and stays valid across restarts, so you never repeat it. Start the listener in a terminal, and point --dir at the parent directory — relayium sync /root/workspace reproduces workspace/... on the receiver, so --dir /root lands the files at /root/workspace/.

On the sender's first connection (next section), serve shows its address and fingerprint and asks you to approve it; answer y and it's remembered for good:

# on the RECEIVER (foreground, to approve interactively)
relayium serve --dir /root --port 9031
# on the RECEIVER, at the first connection:
Incoming push from 203.0.113.9:52140
  fingerprint: 9f2c41ab…
Accept and remember this peer? [y/N] y

Run the listener in the background (on the receiver)

Once the fingerprint is authorized, stop the foreground serve (Ctrl-C) and re-launch it detached so it survives your logout. It loads the saved fingerprint and accepts the sender silently — no prompt this time. The same line records the new process's PID in ~/relayium-serve.pid, which is how the last step of this guide stops the listener it started rather than every relayium command on the box:

# on the RECEIVER
nohup relayium serve --dir /root --port 9031 > ~/relayium-serve.log 2>&1 & echo $! > ~/relayium-serve.pid

Run the sync in a retry loop under tmux (on the sender)

Long transfers get interrupted — a dropped session, a flaky network, a reboot. The fix isn't a fancy tool; it's a loop that reruns sync until it succeeds, plus a terminal multiplexer so it survives you logging out. tmux is cleaner than nohup here: no output redirection to get wrong, and you can reattach to watch progress.

Start a tmux session, then run the mirror in an until loop — it retries every 10 seconds until sync returns success, then exits on its own:

  1. Start a tmux session on the sender, so the loop outlives the ssh session you started it from.

    # on the SENDER
    tmux new -s xfer      # apt install -y tmux if it's missing
  2. Run the mirror inside an until loop. It retries every 10 seconds until sync returns success, then exits on its own.

    until relayium sync /root/workspace relayium://203.0.113.43:9031; do echo "$(date) retrying"; sleep 10; done
  3. Detach with Ctrl-b then d. The loop keeps running; reattach whenever you want to watch it.

    tmux attach -t xfer

What a successful pass looks like

Each pass prints one line per file it actually sent, then a summary counting what it sent against what the receiver already had. The loop ends the first time sync returns success, and that summary is the state of the mirror.

relayium sync /root/workspace relayium://203.0.113.43:9031
  workspace/data/part-004.bin (1073741824 bytes)
synced: 1 sent, 812 unchanged

Verify and finish

The transfer is complete when the until loop ends and you're back at a normal shell prompt. Confirm both sides match, then stop the listener:

  1. Wait for the until loop to exit on its own. Being back at an ordinary shell prompt is the transfer finishing, not you interrupting it.

  2. Compare the totals on both servers.

    # compare totals on BOTH servers
    du -sh /root/workspace
  3. Stop the listener on the receiver once the totals match. It signals the PID you recorded when you launched it rather than substring-matching every relayium command on the box, and the PID file is removed only if that signal succeeded. A PID file left over from an earlier run can name a PID the system has since reused, so when you are not certain the file is still yours, print that PID's command line first and only kill it if serve is what comes back.

    # on the RECEIVER, once verified
    ps -p "$(cat ~/relayium-serve.pid)" -o command=
    kill "$(cat ~/relayium-serve.pid)" && rm ~/relayium-serve.pid

What a finished mirror looks like

du -sh reports the same total on both servers, and the until loop has returned you to an ordinary shell prompt rather than retrying again. Matching totals are a coarse completeness check, not proof of integrity: sync decided by size and mtime which files not to send, so the totals say nothing about the contents of the files it skipped.

# the same total, on BOTH servers
46G	/root/workspace

Troubleshooting

Six things come up on a multi-hour mirror. Three of them look like failures and are not; the other three are real, and each has a command that says which one you are looking at.

Symptom, check, fix

Nothing has printed for a long time and the transfer looks stalled.
# on the SENDER, twice, a few seconds apart
ss -tinp dst :9031
# ESTAB    the listener was reached; not proof that bytes are moving
# SYN-SENT it cannot reach the listener

Progress prints only when a file finishes, so one large file transfers in complete silence. ESTAB proves reachability only — an established socket can sit idle or stall — so it is never on its own evidence of forward progress. Run the check twice a few seconds apart and compare the bytes_acked counter that -i prints for that socket: a rising count is a transfer that is moving, an unchanged one is a real stall.

The socket sits in SYN-SENT and the transfer never starts at all.
# on the RECEIVER
sudo ufw allow from 203.0.113.9 to any port 9031 proto tcp
ss -tlnp | grep 9031

The port is blocked. Open 9031/TCP to the sender alone — substitute the sender's own address for 203.0.113.9, its public IP or its private one when the two servers share a network — scope the cloud security group to that same source, then confirm serve is really listening. This is the most common cause of a transfer that never begins.

Pasting the background command leaves the shell at a > continuation prompt.
tmux new -s xfer
until relayium sync /root/workspace relayium://203.0.113.43:9031; do echo "$(date) retrying"; sleep 10; done

A multi-line nohup command with quotes and a > redirect usually breaks on the redirect when pasted. Use tmux plus this single-line loop instead: there is no redirection to get wrong, and you can reattach to watch it.

Cleaning up killed something you did not mean to kill.
pgrep -af relayium
ps -p "$(cat ~/relayium-serve.pid)" -o command=
tmux kill-session -t xfer
kill "$(cat ~/relayium-serve.pid)" && rm ~/relayium-serve.pid

Killing by command-line pattern signals every process whose command line contains that text, which on a machine running more than one transfer is not the one you meant. It also does not stop the mirror: the until loop owns the sync, so a killed child is simply started again ten seconds later. Look with pgrep -af, then end the things this guide actually owns — tmux kill-session -t xfer ends the loop, and kill on the PID in ~/relayium-serve.pid ends the listener you launched. Print that PID's command line before you signal it if the file has been sitting there since an earlier run: a PID the system has reused belongs to something else entirely.

You want to leave one subdirectory out and there is no exclude flag.
relayium sync /root/workspace/src /root/workspace/data relayium://203.0.113.43:9031

sync takes -i and -p, --delete, --watch and --config-dir — nothing that filters a path out mid-tree. Name the subdirectories you do want instead: each source arrives under the receiver's --dir by its own name, so against serve --dir /root/workspace this rebuilds /root/workspace/src and /root/workspace/data and never walks a regenerable venv.

A source you expected to mirror arrives with nothing in it.
relayium sync ./links relayium://203.0.113.43:9031
# warning: no regular files to send (symlinks and special files are skipped)

sync transfers regular files only, and that warning is exactly what a tree of nothing but symlinks looks like. Point sync at the directories the links refer to, and handle any symlinks you need on the receiver separately.

Frequently asked questions

What happens if the transfer is interrupted halfway?

Nothing is lost. Rerun relayium sync — it skips files already on the receiver and resumes a half-sent file from the byte offset already on disk. The until loop in this guide does that automatically until the whole folder is mirrored.

How is this different from rsync?

Both do incremental one-way mirroring, but relayium sync runs over a pinned TLS connection with no SSH account required (daemon direct), authenticates the two machines by certificate fingerprint, and SHA-256 verifies every file it transfers. Like rsync's default, a file whose size and mtime already match on the receiver is skipped rather than re-hashed. It's the same transfer engine as relayium's other modes.

Does sync delete files on the receiver that I removed from the source?

Only if you ask. By default sync only adds and updates. Pass --delete to mirror deletions, and the receiver must run serve with --allow-delete for it to be honored — otherwise the delete is ignored and reported back.

Can I keep two folders in sync continuously?

Yes. Add --watch and sync stays running, re-mirroring on any change under the source. For a one-time move of a large folder you don't need it — the retry loop plus a plain sync is enough.

Do I have to open a port?

For daemon direct, yes — the listener's port (9031 by default) must be reachable from the sender. If you'd rather not open a port and already have SSH between the servers, sync also works over SSH: relayium sync /path user@host:/path (relayium must be installed on the remote).

Mirror a folder between two of your own servers — incremental, resumable, no babysitting.

Get the CLI

Keep reading