When using rsync with directory arguments like rsync -avv source/ destination/
, the tool performs a bidirectional comparison by default. This means it checks files in both directories and synchronizes changes in both directions based on modification times and file sizes.
The fundamental issue occurs because rsync treats the trailing slash differently than users might expect. When you specify:
rsync -avv left/ right/
You're actually telling rsync to synchronize the contents of left/ into right/, not the directory itself. This creates the potential for reverse synchronization when files exist in both locations.
To strictly enforce one-way synchronization from source to destination, we have several effective approaches:
# Method 1: Using --ignore-existing
rsync -avv --ignore-existing left/ right/
# Method 2: Using --update
rsync -avv --update left/ right/
# Method 3: Using --existing
rsync -avv --existing left/ right/
--ignore-existing skips updating files that already exist on the receiver, effectively making the transfer one-way for existing files while still allowing new files to be copied.
--update (or -u) skips files that are newer on the receiver, which prevents overwriting changes made on the destination side.
--existing tells rsync to skip creating new files on the receiver, only updating files that already exist.
Consider this directory structure before sync:
left/
a.txt (modified 2023-01-01)
b.txt (new file)
right/
a.txt (modified 2023-01-02)
c.txt (existing file)
Running rsync -avv --update left/ right/
would result in:
right/
a.txt (unchanged, because it's newer)
b.txt (copied from left)
c.txt (unchanged)
For a complete mirror where you want the destination to exactly match the source:
rsync -avv --delete --ignore-existing left/ right/
This will:
1. Copy new files from left to right
2. Delete files in right that don't exist in left
3. Skip updating existing files that might be newer in right
Another technique is to preserve the destination timestamps while copying:
rsync -avv --times left/ right/
This ensures the destination files keep their original modification times, making subsequent rsync operations less likely to overwrite them.
For most one-way synchronization needs, --update
provides the best balance between safety and functionality. It prevents accidental overwrites while still allowing new files to propagate.
Many developers encounter this surprising behavior when using rsync for the first time: the seemingly unidirectional command rsync -avv source/ dest/
can actually perform bidirectional synchronization under certain conditions. This occurs when files in the destination directory are newer than their counterparts in the source.
The fundamental misunderstanding stems from rsync's default delta-transfer algorithm. When you run:
rsync -avv left/ right/
rsync doesn't simply copy from left to right - it compares files in both directories and makes decisions based on:
- File timestamps
- File sizes
- Checksums (when using --checksum)
To strictly enforce left-to-right transfer, you need these key options:
rsync -avv --ignore-existing --times --omit-dir-times left/ right/
Or more strictly:
rsync -avv --update --existing --ignore-non-existing left/ right/
Consider this deployment case where we only want to push new files from development to production:
# Initial state
dev/
config.yml (modified 2023-01-01)
app.js
prod/
config.yml (modified 2023-06-01)
# Safe one-way sync command
rsync -avz --ignore-existing --progress dev/ prod/
For critical systems, combine these techniques:
# 1. Use checksum verification
rsync -avc --ignore-existing src/ dst/
# 2. Make destination read-only first
chmod -R u-w dst/ && rsync -av src/ dst/
# 3. Dry-run verification
rsync -avun --delete src/ dst/
When absolute unidirectional transfer is required, consider:
- Using
scp
instead for simple copies - Implementing a read-only filesystem at the destination
- Creating a custom rsync filter file
- Don't use --delete with unidirectional transfers unless you fully understand the implications
- Avoid trailing slashes when you mean to sync entire directories
- Remember that permission changes might still propagate