Ctrl K

Transfer Files Between Machines with rsync over SSH

Move or copy folders between two Linux machines on the same network using SSH for the connection and rsync for the transfer, with a dry-run and verify step.

Copy or move folders between two Linux machines on the same local network. SSH provides the connection and rsync does the transfer, resuming and skipping unchanged files on a re-run. The direction does not matter: the machine receiving the files runs the SSH server, and the machine sending the files runs rsync. Replace SOURCE_PATH, DEST_PATH, and the user and IP placeholders as needed.

Quick reference

# on the receiving machine: enable SSH and find its IP
sudo systemctl enable --now sshd
ip -4 addr show | grep inet

# on the sending machine: dry-run, then the real transfer
cd SOURCE_PATH
rsync -avh --dry-run ./ "$USER"@192.168.1.17:DEST_PATH/
rsync -avh --progress ./ "$USER"@192.168.1.17:DEST_PATH/

# verify on the receiving machine, then remove the source if this was a move
du -sh DEST_PATH
rm -rf SOURCE_PATH

Enable SSH on the receiving machine

The machine that receives the files needs an SSH server running. Enable and start it in one step, then confirm it is active.

sudo systemctl enable --now sshd
systemctl status sshd

Active: active (running) confirms it is up. On Debian or Ubuntu the unit and package are named ssh / openssh-server instead of sshd.

Find the receiving machine IP

Read the local network address of the receiving machine. Use ip directly, since a minimal install may not ship hostname -I.

ip -4 addr show | grep inet
inet 127.0.0.1/8 scope host lo
inet 192.168.1.17/24 brd 192.168.1.255 scope global dynamic wlo1

Use the LAN address, usually 192.168.1.x on the active interface. Do not use 127.0.0.1 (loopback) or a 172.x Docker bridge address. For more on reading this output, see Find local network IP.

Test the SSH connection

From the sending machine, confirm SSH works before transferring anything.

ssh "$USER"@192.168.1.17

On the first connection SSH asks whether to trust the host fingerprint; type yes, then enter the receiving user's password if prompted. Run exit once the login succeeds. To connect with a short alias instead of the full user and IP, add a host block to ~/.ssh/config (see SSH into a local network device).

Understand the rsync trailing slash

A trailing slash on the source changes what gets copied. This is the most common source of mistakes.

Source formResult
rsync SOURCE_DIR/ DEST_DIR/Copies the contents of SOURCE_DIR into DEST_DIR
rsync SOURCE_DIR DEST_PARENT/Copies SOURCE_DIR itself into DEST_PARENT

The reliable pattern is to cd into the source folder and copy ./, which avoids mistyping a long source path.

Dry-run first

Always preview with --dry-run. rsync lists what it would copy without moving any file content. The flags are -a (archive: recurse and preserve permissions and timestamps), -v (verbose), and -h (human-readable sizes).

cd SOURCE_PATH
rsync -avh --dry-run ./ "$USER"@192.168.1.17:DEST_PATH/

If you see change_dir "...source..." failed: No such file or directory, the source path is wrong. Confirm where you are with pwd before retrying.

Run the transfer

Once the dry-run output looks correct, drop --dry-run and add --progress to watch larger files transfer.

rsync -avh --progress ./ "$USER"@192.168.1.17:DEST_PATH/

rsync prints an incremental file list and finishes without an error code. Re-run the same command anytime to copy only what changed.

Mirror with --delete

By default rsync only ever adds and updates: it never removes anything on the destination. So if you delete a file on the source (or a tool renames or moves an attachment), the old copy still sits on the destination forever, and over repeated runs the destination accumulates orphans that are no longer in the real source.

--delete removes files on the destination that no longer exist on the source, making the destination an exact mirror instead of an ever-growing pile.

# preview exactly what would be deleted before committing
rsync -avh --progress --delete --dry-run ./ "$USER"@192.168.1.17:DEST_PATH/

# then run it for real
rsync -avh --progress --delete ./ "$USER"@192.168.1.17:DEST_PATH/

The key thing to understand: --delete deletes on the destination side. It is safe only when the sync is strictly one-way and the source is the single source of truth, so "make the destination match the source" is exactly what you want. It is dangerous the moment that assumption breaks: if you point the direction the other way, or keep unique files that live only on the destination, --delete wipes them. Always run it with --dry-run the first time to see what it would remove.

Verify on the receiving machine

SSH into the receiving machine and confirm the destination size and a sample of the files before trusting the transfer.

ssh "$USER"@192.168.1.17

du -sh DEST_PATH
find DEST_PATH -maxdepth 2 -type f | head -50
exit

Remove the source after verifying

Only after the destination checks out, delete the original on the sending machine. This step applies to a move, not a copy.

cd ~
rm -rf SOURCE_PATH

rm -rf is irreversible. Verify the destination first, and prefer an absolute SOURCE_PATH (or cd ~ first) so a typo cannot delete the wrong folder.

Safety notes

  • Always dry-run before the real transfer.
  • Prefer cd SOURCE_PATH then rsync ... ./ ... over typing a long source path.
  • Verify the destination before deleting the source.
  • Do not add --delete unless you explicitly want the destination mirrored.
  • Be careful transferring secrets: .env files, SSH keys, cloud credentials, age identities, browser profiles, and app state folders.
  • For project code, prefer Git. Use rsync over SSH for notes, archives, scans, and one-time folder moves.

See also