The rsync command that copies files to a server
Rsync sends only the parts of a file that changed, over SSH, which is why a second copy of a 2 GB site takes seconds. The whole syntax hangs on which side the remote host sits.
| Direction | Command | Use it for |
|---|---|---|
| Push | rsync -avz ./site/ user@203.0. | Laptop or build server to the live server |
| Pull | rsync -avz user@203.0. | Backup, or copying production down to test |
| Through a port | rsync -avz -e "ssh -p 2222" ./site/ user@host: | Any server that is not on port 22 |
The remote side needs rsync installed as well, even for a pull. On Debian or Ubuntu the package is rsync; on RHEL and its clones it is also rsync, from the base repository.
The trailing slash decides what gets copied
This is the rule that catches everyone once. /var/www/site/ means the contents of that folder. /var/www/site means the folder itself.
rsync -av ./site/ host:/var/putswww/site/ index.phpat/var/www/. Correct for a deploy.site/index. php rsync -av ./site host:/var/puts it atwww/site/ /var/www/. The site disappears behind a folder.site/site/ index.php - A trailing slash on the destination changes nothing. Only the source matters.
Which flags matter on a web server
-avz is the default most people type. Two of those three letters do real work, and one is often a waste.
| Flag | What it does | When to keep it |
|---|---|---|
-a | Archive mode: recursive, keeps permissions, timestamps, links and ownership | Almost always, for a site deploy |
-z | Compresses during transfer | Slow or metered links. Drop it for JPEGs, video and zips, which are already compressed |
-P | Progress plus resumable partial files | Large uploads on a connection that drops |
--checksum | Compares content instead of size and mtime | Verifying a backup, not routine syncs |
--bwlimit=8000 | Caps transfer at about 8 MB/s | A daytime sync that must not saturate the link |
-e "ssh -p 2222" | Passes options to SSH | Non-standard port or a specific identity file |
Exclude the files that should never be copied
A raw copy of a project folder carries things the server does not need and should not serve: version history, build caches, logs and local environment files.
- Start with the obvious four:
--exclude='.git/' --exclude='node_modules/ ' --exclude='*.log' --exclude='.env' - Move the list into a file once it passes three entries:
--exclude-from=/etc/ rsync-excludes. txt - Keep
.well-known/and any upload folder out of the exclude file. Both are needed on the live server. - Check the paths rsync actually sees with
-v. Patterns match from the source root, socache/will not matchwp-content/cache/.
Dry run, then mirror with --delete
--delete removes anything on the server that is not in the source. That is what makes a mirror a mirror, and it is also how a wrong source path empties a live site. Never run it first.
- Dry run with the exact command you intend to use, plus
-n:rsync -avzn --delete ./site/ user@host:/var/www/ site/ - Read the deletions in the output line by line. A wall of
deletinglines usually means a wrong source path or a missing trailing slash. - Take a server-side copy before the first real run:
ssh user@host "cp -a /var/www/site /var/www/site.bak" - Drop the
-nand run again. Keep-vfor the first run, then remove it.
If the site writes uploads or cache files on the server, exclude those folders instead of using --delete. A mirror will delete every file a visitor created since the last sync.
Run the sync from cron with an SSH key
A sync that needs a typed password cannot run on a schedule. Generate a key for this job alone, copy the public half to the server, then test the login before you touch cron.
ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -C "deploy"and leave the passphrase empty for a cron job, or load it into an agent.ssh-copy-id -i ~/.ssh/deploy_key.pub user@203.0.113.10 - Add the host to
~/.ssh/configso the command stays short and cron does not parse quotes badly:Host live, thenHostName,User,PortandIdentityFile. - Write the job as a script with absolute paths:
/usr/bin/rsync -az --delete -e "ssh -i /home/deploy/.ssh/deploy_ key" /var/www/build/ deploy@live: /var/www/ site/ - Add the log redirect so a failure is visible:
0 2 * * * /usr/local/bin/deploy. sh >> /var/log/ deploy.log 2>&1
Fix ownership after the files land
Rsync with -a keeps the numeric owner and group from the source machine. If your laptop user is 501 and the web user is 1001, Apache or PHP will return 403 on files it cannot read.
Set ownership to the web user after the transfer: chown -R www-data:www-data /var/www/site on Debian and Ubuntu, or the account user on a cPanel or DirectAdmin server. Files go to 644 and folders to 755, never 777. On a WordPress site the uploads folder usually needs to stay writable by the web user, which chown already handles.
If you sync as a non-root deploy user, add --no-o --no-g so rsync stops trying to set ownership it is not allowed to set. The transfer stops reporting errors, and the chown step becomes the only place ownership is set.
Rsync exit codes and common failures
| Symptom | Cause | Fix |
|---|---|---|
| Exit code 23, "some files could not be transferred" | Usually a permission error, or a file that changed while being read | Read the list above the code, fix ownership, re-run. Code 23 on a live site is normal and harmless |
| Exit code 24, "some files vanished" | A file was deleted during the transfer, often a cache file | Re-run once. If it repeats every time, exclude that folder |
| Permission denied (publickey) | The key is not offered to the host, or ~/.ssh is 755 instead of 700 | Test with ssh -v host, fix the permissions, confirm the key is in authorized_keys |
| rsync: command not found | rsync is missing on the remote end, even for a pull | Install the package on both machines |
| Files transfer again on every run | Timestamps are not preserved on the destination filesystem | Add --checksum once to confirm, then fix the mount or use a filesystem that keeps mtime |
Rsync on a WebJi® VPS
Rsync over SSH needs a server where you have a shell account and a key of your own. A WebJi® VPS gives you root access from the first boot, so the deploy user, the key and the cron job are yours to create.
VPS plans start at ₹499 a month, before 18% GST, with the configuration quoted by email at support@webji.
Frequently Asked Questions
How do I copy files to a server with rsync?
Run rsync -avz /local/path/ user@server:
What does the trailing slash in rsync do?
It changes the source from the folder to its contents. With the slash, files land directly in the destination. Without it, rsync creates one more folder level, which is the usual reason a deploy shows an empty site.
Is rsync --delete safe?
Only after a dry run. Add -n to the exact command and read the deletion list before running it for real. Keep a server-side copy of the site, and never point --delete at a source path you have not checked.
How do I run rsync from cron without a password?
Use an SSH key with no passphrase, or load the key into an agent. Copy the public key with ssh-copy-id, use absolute paths inside the script, and redirect stdout and stderr to a log so a failed run is visible.
Why do files get 403 after an rsync deploy?
Rsync with -a copies numeric ownership from the source machine, so the web server may not be able to read the files. Run chown -R www-data:www-data on the site folder, set files to 644 and folders to 755.
Can I use rsync on shared hosting?
Only if your host gives you SSH and rsync on the account. On shared hosting you usually deploy through the file manager, SFTP or git. Rsync needs a shell account, which is what a VPS or a dedicated server provides.

