Server-to-Server Transfer: A Practical and Painless Guide

Do server-to-server transfers the right way: choose your tool based on data volume, resume interrupted transfers, and verify once the job is done.

6 min Updated 4 Nov 2026

You've run the rsync command, the progress bar is stuck at 40 percent, and ten minutes later the SSH connection drops. Now you don't know whether to delete the half-transferred files or resume from where you left off. This is exactly the point where bad decisions begin. A server-to-server transfer goes well when, before you type the command, you've already determined the data volume, the bandwidth, and the verification method.

First measure the volume and type of data, then choose the tool

Before anything else, run this on the source:

du -sh /var/www /home /var/lib/mysql
df -h
df -i

Don't underestimate the output of df -i. If the source's inodes are exhausted, transferring many small files may halt on the destination with a No space left on device error, even while df -h still shows free space. More detail on reading these same outputs is available in Linux disk commands: space, inodes, and disk health.

To choose a tool, consider this table:

Data volumeRecommended toolReason
Less than 1 GBscp or rsyncSetup speed matters more than optimization
1 to 500 GBrsync over SSHResumable, repeatable, can delete extra files
More than 500 GBrsync or tar + netcatReduces encryption overhead on the internal network
Live databasedump + transfer the dump fileCopying raw MySQL files usually corrupts them

A server-to-server transfer on a datacenter's internal network with 1 Gbps bandwidth moves roughly 100 to 110 MB per second in real-world conditions. If SSH encryption is also active on that same link, this number drops to 60 to 80 MB per second. For 200 GB of data, that difference means about 40 minutes of extra time.

The right rsync command for a server-to-server transfer

Run this command from the destination server, not the source:

rsync -avzP --partial --append-verify \
  -e "ssh -p 22 -o ServerAliveInterval=30" \
  root@192.0.2.10:/var/www/ /var/www/

A few points that make a difference in practice:

  • -P is a combination of --partial and --progress; it keeps the half-finished file so the next run resumes from where it left off.
  • --append-verify only makes sense when the source file has only been appended to. For files whose middle changes, it produces wrong results.
  • ServerAliveInterval=30 prevents the connection from dropping during long transfers.
  • The trailing slash on the source path matters. /var/www/ copies the contents of the directory, /var/www copies the directory itself.

If the transfer is interrupted, just run the same command again. rsync compares already-transferred files by checksum and sends only the differences. This feature is the main reason rsync is chosen for server-to-server transfers.

This is where people go wrong

A common mistake is adding --delete on the first run. If the destination path was typed incorrectly, rsync will happily wipe the contents of that directory. The sign is obvious too: the transfer finishes without errors, but the amount of transferred data is less than expected and the destination files have vanished. Only add --delete on the second or third run, and only after a --dry-run.

Transferring a MySQL database without corruption

Directly copying /var/lib/mysql on a running server almost always leads to a corrupted database. The correct approach:

mysqldump --single-transaction --routines --triggers \
  --databases shop_db > /tmp/shop_db.sql
gzip /tmp/shop_db.sql
rsync -avzP /tmp/shop_db.sql.gz root@192.0.2.20:/tmp/

Then on the destination:

gunzip < /tmp/shop_db.sql.gz | mysql
mysql -e "SELECT COUNT(*) FROM shop_db.orders;"

The --single-transaction option only makes sense on InnoDB. If you have MyISAM tables, you must lock the database or use another method. For databases larger than 50 GB, dumping and transferring all at once may take hours and fill up the destination disk; in that case it's better to take the dump in chunks or use tools like mydumper.

Verification: the step most people skip

The transfer is done, but is the data intact? Run this on both servers and compare the output:

find /var/www -type f -exec md5sum {} \; | sort -k2 | md5sum

If the hashes match, the transfer is complete. If not, run it again with rsync -avzP --checksum so only the differing files are transferred. For the database, compare record counts and table sizes with SHOW TABLE STATUS.

One point that's rarely mentioned: if services like Nginx or PHP-FPM are running on the destination server, they may create temporary files or logs and throw off the hash comparison. Before verifying, stop the destination services. You can see where logs are written in Linux log locations: where does each service write?.

Transferring between cloud and dedicated servers

If the destination is a cloud server, there's usually an option to connect over the internal network, and you can use the internal IP instead of the public IP. This both increases speed and reduces outbound traffic. For a dedicated server, if both servers are in the same rack, the same rule applies.

When transferring between different datacenters, the actual bandwidth is usually far lower than the nominal bandwidth. Before starting a large transfer, measure the real speed between the two endpoints with iperf3:

iperf3 -c 192.0.2.20 -t 30

If the resulting number is less than 20 MB per second, transferring 100 GB will take more than an hour and a half. In that case, compressing before transfer (for example tar czf on text data) can noticeably reduce the time, provided the CPU doesn't become the bottleneck.

Don't forget the firewall and ports

If the transfer halts with a Connection refused or No route to host error, the SSH port on the destination is probably closed, or the source firewall isn't allowing outbound traffic. A quick command reference is available in Linux firewall commands: a quick reference for iptables, nftables, and ufw. You can also see the default ports for services in the reference of common service ports.

Frequently asked questions

Why does rsync re-transfer files that are already there?

If a file's modification time (mtime) or size has changed, rsync sends it again. To prevent this, use the --checksum option so the comparison is based on content hash rather than time. This uses more CPU but is more accurate for repeated transfers.

Can I run a server-to-server transfer in the background?

Yes. Open a session with screen or tmux and run the command inside it. If SSH drops, the transfer continues. Another method is using nohup, but in that case you don't see the progress output and debugging becomes harder.

How long does a server-to-server transfer take on low bandwidth?

Transfer time equals data volume divided by the actual link speed. For 100 GB on a 100 Mbps link, that's about two and a half hours in the ideal case, and more in practice. Always measure the actual speed with iperf3, not the nominal speed.

After the transfer, when should I change DNS?

First complete verification, then change the DNS record. Lower the record's TTL to 300 seconds at least 24 hours before the change so that, if there's a problem, you can roll back quickly. If you have systemd-dependent services on the destination server, check the startup order in systemd unit files: sections, restart, and dependencies so services come up correctly after the DNS change.

If you want to perform the transfer in a fresh, controlled environment, a suitable destination Linux host can be a good starting point. But the most important thing you should do today is this: before any large transfer, run it once with --dry-run and read the output. Those five seconds save you hours of trouble.

Was this page helpful?