termique
Blog
Guide10 min read

SFTP vs rsync for automated backups

sftp vs rsync for automated backups: the real difference, when to use each, and a production-ready cron script that won’t wipe your data.

SFTP vs rsync for automated backups

Every automated backup pipeline built on SSH eventually runs into the same question: sftp vs rsync for automated backups, which one actually belongs in the cron job? Most write-ups treat it as a coin flip between two competing file-transfer tools. It isn’t. SFTP and rsync solve different problems, and picking the wrong one is how a backup job quietly stops doing its job while cron keeps reporting success every single night.

The failure mode is rarely dramatic. It’s a nightly job that keeps re-uploading every file in full because the tool never checked what already existed on the other end, until a slow link times out halfway through and nobody notices for three weeks. Or it’s a --delete flag pointed the wrong direction, quietly turning a backup destination into a mirror of an empty folder. Choosing the right tool, and scripting it defensively, is what actually prevents both.

SFTP vs rsync for automated backups: what’s the actual difference?

Both run over SSH, both are encrypted in transit by default, and both show up in the same man pages next to each other, which is exactly why they get compared as if they were interchangeable. They aren’t solving the same problem. If you want the fundamentals of the protocol itself before getting into backups specifically, start with the complete guide to SFTP for developers.

SFTP: a stateless file transfer protocol riding on SSH

SFTP (SSH File Transfer Protocol, unrelated to plain FTP despite the name) opens one SSH connection and exposes a small set of remote file operations on top of it: open, read, write, list, remove, rename. Every transfer is a full push or pull of a file’s current bytes. SFTP has no concept of what a similar file already looks like on the other end, and no built-in mechanism to skip a file that hasn’t changed. It’s a file-copying protocol, not a synchronization tool.

rsync: a delta-transfer sync tool that happens to use SSH

rsync is a synchronization utility. Given a source and destination, it works out which files differ, and for files that exist on both sides, it transfers only the changed portions rather than the whole file. SSH isn’t rsync’s own transport, it’s the tunnel rsync rides through to reach a remote host securely, the same way a browser rides through TLS. Left to its defaults, rsync will use SSH automatically for any remote path in the form user@host:/path.

rsync isn’t a competitor to SSH or SFTP. It’s a smarter client that happens to use an SSH connection to get its work done, and can even fall back to invoking the remote system’s SFTP subsystem in some configurations. The comparison that matters isn’t “which protocol is better”, it’s “which transfer behavior does this backup job actually need”.

Which one should you actually automate: SFTP or rsync?

The decision comes down to two questions: does the destination give you a shell to run commands in, and do most files stay unchanged between runs.

When SFTP is the safer default

  • The destination account is locked to ForceCommand internal-sftp inside a chroot jail, common on managed backup vaults and client-provided servers, with no shell and no rsync binary reachable.
  • You’re pushing to shared hosting, a compliance-locked storage gateway, or any third-party target where you don’t control what’s installed server-side.
  • The job moves a small number of already-compressed archives (a nightly pg_dump, a tarball) where delta transfer has nothing to save anyway.
  • You need a dead-simple, auditable transcript of exactly which files moved, without depending on a matching rsync version on both ends.

When rsync saves real bandwidth and time

  • You’re backing up a large directory tree (application storage, media uploads, log archives) where most files are unchanged night to night.
  • The link between source and destination is metered or slow, and re-uploading unchanged bytes is a real cost, not a rounding error.
  • You need the destination to mirror the source exactly, including removing files that were deleted locally, via --delete.
  • A large single file (a database dump, a VM image) sometimes only changes in parts, and resuming an interrupted transfer with --partial matters.

How does rsync’s delta-transfer algorithm actually work?

For a file that already exists on both ends, rsync splits the destination copy into fixed-size blocks and computes two checksums per block: a fast, weak rolling checksum and a stronger MD5-family checksum. It sends only those checksums, not the file, to the source side. The source then slides a window over its own copy of the file looking for blocks whose rolling checksum matches one it received, confirms the match with the strong checksum, and only ships the byte ranges that don’t match anything already present remotely. A one-line change in a 2GB log file can transfer as a few kilobytes instead of the full file. SFTP has no equivalent step: a put is a put, full file, every time.

Building an automated backup pipeline with rsync over SSH

This is the version you actually want in cron, not a one-off command typed by hand.

Generate a dedicated, restricted backup SSH key

Don’t reuse your personal deploy key for an unattended job. Generate one scoped to this task alone, so a leak of this key doesn’t hand over shell access to the whole box. See end-to-end encrypted SSH credential storage for why a key that’s going to sit unattended on a cron box, passphrase-less by necessity, deserves the same treatment as any other credential rather than living as a bare file in a home directory.

ssh-keygen -t ed25519 -f ~/.ssh/backup_key -C "backup-cron@$(hostname)" -N ""

On the destination, restrict what that key is allowed to do in ~/.ssh/authorized_keys instead of granting it a normal login shell:

command="rrsync /srv/backups/incoming/",no-agent-forwarding,no-port-forwarding,no-pty,no-X11-forwarding ssh-ed25519 AAAAC3Nza... backup-cron@app01

rrsync (shipped with rsync, usually under /usr/share/doc/rsync or similar) is a wrapper that restricts the key to rsync operations inside one directory tree. If that key leaks, it can push files into one folder and nothing else, no shell, no port forwarding, no agent forwarding.

A cron-ready rsync backup script

#!/usr/bin/env bash
set -euo pipefail

LOCKFILE=/var/run/backup-rsync.lock
LOGFILE=/var/log/backup-rsync-$(date +%F).log

exec 9>"$LOCKFILE"
flock -n 9 || { echo "backup already running, skipping"; exit 1; }

rsync -az \
  --delete \
  --delete-after \
  --exclude='*.tmp' \
  --exclude='cache/' \
  -e "ssh -i /home/deploy/.ssh/backup_key -o StrictHostKeyChecking=yes" \
  /var/www/app/storage/ \
  backup-cron@backup.example.com:/srv/backups/incoming/ \
  >> "$LOGFILE" 2>&1

echo "backup finished $(date -Iseconds)" >> "$LOGFILE"

The flock guard stops a slow run from overlapping with the next cron tick. Logging to a dated file, not /dev/null, is what turns “cron said it worked” into something you can actually check when a client asks whether last Tuesday’s file made it across.

0 2 * * * /usr/local/bin/backup-rsync.sh

The –delete flag: the trap that wipes backups

--delete removes anything on the destination that no longer exists on the source, which is exactly what you want for a faithful mirror, and exactly what turns a typo into a disaster: reverse source and destination by mistake, or point the source at an empty or half-mounted directory, and rsync will dutifully delete every file on the destination to match.

Always test a new or edited rsync command with --dry-run (-n) before the first real run, and read the output line by line. For anything that matters, prefer --backup --backup-dir=/srv/backups/deleted-$(date +%F) over a hard --delete, so a mistaken deletion lands in a dated trash folder instead of gone.

Building an automated backup pipeline with SFTP instead

Sometimes the choice isn’t yours to make. The destination dictates the protocol.

When a chrooted or shell-less remote forces SFTP over rsync

rsync’s remote mode works by invoking the rsync binary on the far end through a shell command carried over the SSH session (unless you’re running a dedicated rsync daemon on its own port, which most managed targets don’t expose). If the destination account is locked to ForceCommand internal-sftp inside a chroot, a setup that’s common for a managed backup vault, a compliance-locked storage gateway, or a client-owned server you were only handed one restricted account on, there’s no shell to invoke rsync in and often no rsync binary reachable at all. SFTP is the only option, not a preference.

Scripting SFTP with batch mode

SFTP has no delta transfer, so pushing an entire directory tree with it every night re-uploads everything, every time. The fix is to stage only what changed locally first, then push a single archive instead of thousands of individual files. For the fuller walkthrough on scheduling raw SFTP transfers safely, including retrying partial uploads and handling connection drops mid-batch, see automating SFTP transfers with cron, safely.

MARKER=/var/backups/.last-run-marker
STAGE=/tmp/incremental-$(date +%F).tar.gz

find /var/www/app/storage -newer "$MARKER" -type f -print0 \
  | tar --null -czf "$STAGE" --files-from=-

touch "$MARKER"
cd /srv/backups/incoming
put /tmp/incremental-2026-07-19.tar.gz
bye
sftp -b backup.batch -i ~/.ssh/backup_key backup-cron@backup.example.com

Can you combine SFTP and rsync in one backup strategy?

Yes, and for a mixed fleet it’s often the right answer. Use rsync where you control both ends: a fast, delta-efficient mirror from production storage onto a staging volume you manage. Then push that staging volume’s periodic archive over SFTP to wherever you don’t control the far end, an offsite vault, a client’s chroot-locked account, a compliance gateway that only speaks SFTP. You get rsync’s bandwidth efficiency for the leg you can optimize, and SFTP’s universal compatibility for the leg you can’t.

Common mistakes that break automated SSH-based backups

  • Enabling --delete on a new or edited rsync command without a --dry-run pass first.
  • Leaving a backup key with a normal login shell instead of restricting it with command= in authorized_keys, so a leaked key grants full access rather than access to one folder.
  • No alerting on cron failure. A job that silently stops running for three weeks looks identical, from the outside, to one that’s working.
  • Testing that the backup step exits 0, but never testing an actual restore from it.
  • Reusing one SSH key across every automated job on every host instead of a key per purpose per host, so a single leak affects everything at once. See managing multiple SSH keys across devices for how to keep that from becoming unmanageable as the fleet grows.
  • Letting the backup log itself grow unbounded on the same disk the backup is meant to protect.

Keeping the backup key itself secure

A dedicated, restricted backup key is only as good as your ability to say, months later, exactly which hosts hold it, when it was generated, and whether it’s still needed. That gets harder the moment there’s more than one server involved, which is most real setups within a few months.

This is the part of the pipeline that’s easy to neglect because it isn’t in the cron job itself. termique’s SSH key vault generates or imports keys and stores them in the OS keychain rather than a bare file sitting in a home directory, with the fingerprint and algorithm visible at a glance instead of buried in ssh-keygen -l output you have to remember to run. Its SFTP file browser, open on every plan since v0.3.0, is useful for manually checking a chroot-locked backup target’s contents before you trust a new cron job to write into it unattended. And its per-command audit log gives a real answer, with a timestamp, when someone asks whether last night’s automated session touched anything outside the storage directory it was supposed to.

None of that changes how rsync or SFTP behave; the protocols work the same from a bare terminal or a full SSH manager. It just means the key your backup job depends on doesn’t quietly become the one nobody remembers granting.

Try termique free.

SSH manager with end-to-end encrypted credentials, AI assistant, and cross-device sync.

Download free

Keep reading

All articles ⟶