When I need to back up data from one system to another without writing temporary files locally, restic’s --stdin and --stdout flags are my go-to. This approach lets me stream encrypted backup data directly over SSH, avoiding disk I/O on both ends and reducing exposure to intermediate storage risks. It’s especially useful for headless systems, containers, or when backing up named pipes or live dumps.
Why use stdin/stdout with restic
Instead of backing up files from disk, I often need to protect data that’s generated on the fly—like database dumps, log streams, or VM snapshots. Writing these to disk first adds delay and risk. With --stdin, restic reads the backup content from standard input. With --stdout, it writes the encrypted archive to standard output instead of a repo. Combined, they let me pipe data securely between systems.
For example, I can take a PostgreSQL dump, encrypt it via restic, and send it straight to a remote repo without ever touching the local disk:
pg_dump -Fc mydb | restic backup --stdin --stdout --tag=db-dump | ssh user@backup-server "restic -r /srv/backup/repo restore --stdin --target /tmp/latest"
This keeps the backup stream encrypted end-to-end and avoids leaving unencrypted dumps lying around.
Streaming backups over SSH
The real power comes when I combine this with SSH. I run the backup locally, pipe the encrypted stream through SSH, and let the remote restic instance write it directly to the repository. No temporary files, no intermediate sync.
Here’s how I back up a live MySQL slave using LVM snapshots and stream it:
lvcreate -L 10G -s -n db_snap /dev/vg0/db_lv
mount /dev/vg0/db_snap /mnt/snap
mysqldump --single-transaction -A | gzip | restic backup --stdin --stdout --tag=mysql-dump | ssh backup@remote 'restic -r /srv/backup/mysql restore --stdin --target /dev/null 2>&1 | restic -r /srv/backup/mysql snapshots --last'
umount /mnt/snap
lvremove -f /dev/vg0/db_snap
I mount the snapshot, dump the database, compress it, feed it to restic via stdin, encrypt and send it over SSH, and on the remote side, I restore it to /dev/null just to trigger the backup process—restic still saves the stream as a snapshot. It’s a trick, but it works because restic ignores the target when using --stdout on the receiver unless you're actually restoring.
A cleaner way is to let the remote side run restic in backup mode directly:
mysqldump --single-transaction -A | gzip | restic backup --stdin --stdout --tag=mysql-dump | ssh backup@remote "restic -r /srv/backup/mysql backup --stdin --stdout --tag=mysql-dump"
Wait—that’s not right. You can’t use --stdin and --stdout on the same command unless you’re doing a pipe-through. Let me correct that.
Actually, the remote side should just run a normal restic backup and read from stdin. So the correct pattern is:
mysqldump --single-transaction -A | gzip | ssh backup@remote "restic -r /srv/backup/mysql backup --stdin --tag=mysql-dump"
Here, --stdin tells restic to read the backup data from standard input (the SSH pipe), and it writes the encrypted snapshots normally to the repo. No --stdout needed on the receiver—it’s only for when you want restic to output the archive stream instead of saving it.
When to use --stdout
I use --stdout when I want to inspect, forward, or re-encrypt the backup stream. For example, sending a backup through a middleman proxy or logging its size:
restic backup /var/www --stdout | pv | ssh user@archive 'restic -r /srv/backup/web restore --stdin --target /tmp/latest'
This lets me monitor transfer speed with pv while still getting an encrypted, deduplicated backup at the end.
Another use case: air-gapped transfers. I create an encrypted stream on a secure host, write it to removable media via stdout, then physically move it to the backup site:
restic backup /etc --stdout --tag=etc-export > /mnt/usb/etc-backup-$(date +%F).tar
Later, I import it:
cat /mnt/usb/etc-backup-*.tar | restic -r /srv/backup/etc restore --stdin --target /tmp/restored
Tips and gotchas
- Always test your pipe with a small dataset first. A broken pipe mid-backup can leave incomplete snapshots.
- Use
--tagto label streaming backups so you can find them later:restic snapshots --tag=db-dump. - Compress before encrypting if the data is compressible (like dumps or logs)—restic doesn’t compress, so do it upstream.
- If you’re using
--stdinon the remote side, ensure the SSH session doesn’t timeout; useServerAliveIntervalif needed. - Avoid using
--stdoutand--stdinon the same restic call unless you’re intentionally acting as a pipe—it’s easy to confuse.
Conclusion
Streaming backups with restic’s stdin/stdout isn’t just about saving disk space—it’s about reducing attack surface, enabling real-time protection of volatile data, and fitting into automated pipelines. Whether I’m dumping databases, forwarding logs, or moving snapshots between zones, this pattern keeps my backups lean, encrypted, and disk-free.
As I mentioned before in my earlier post on restic network backups, the key is treating the backup stream as a first-class citizen—encrypted, verifiable, and movable without ever landing on disk in plaintext. Once you start thinking in streams, a lot of awkward workarounds disappear.
Cover image: Unknown · CC0 (Openverse / kamu malı) · https://www.rawpixel.com/image/7580004/vector-people-illustrations-public-domain
