Skip to main content

Rsync Operators and Keywords

Rsync operators and keywords give you fine-grained control over exactly which files are transferred. This page covers the special characters, wildcards, and filter rules that make rsync surgically precise.

Operators

Operators are special characters that rsync interprets in paths and patterns.

The Colon Operator (:)

The colon separates a remote hostname from a path:

# Format: user@host:path
rsync -avz /local/path/ user@server:/remote/path/
SyntaxMeaning
user@server:/path/Remote path via SSH
/local/path/Local path (no colon)
server::moduleRsync daemon connection (double colon)

The Double Dash (--)

Terminates option parsing — everything after -- is treated as a path, not a flag. Useful when directory names start with a hyphen:

# Without -- rsync might interpret -mydir as a flag
rsync -av -- -mydir/ /backup/

# Safe way to handle unusual directory names
rsync -av -- ./--weird-name/ /destination/

Wildcards (Pattern Matching)

Rsync uses glob-style wildcards in --exclude, --include, and --filter patterns.

WildcardMatchesExampleWhat It Matches
*Zero or more characters (not /)*.logerror.log, access.log
**Zero or more characters including /**/*.logapp/error.log, logs/2024/access.log
?Exactly one characterfile?.txtfile1.txt, fileA.txt
[...]Any single character in the set[0-9]*.sql1_dump.sql, 3_backup.sql
[!...]Any single character NOT in the set[!.]*.confnginx.conf but not .hidden.conf

Important: * vs **

This distinction catches many people off guard:

# Matches only in the current directory
--exclude '*.log' # Matches: error.log
# Does NOT match: logs/error.log

# Matches at any depth
--exclude '**/*.log' # Matches: error.log AND logs/error.log AND deep/path/error.log

Filter Keywords

--exclude — Skip Files/Directories

The most commonly used filter — tells rsync to skip matching files:

# Skip a specific directory
rsync -av --exclude 'cache/' /var/www/ /backup/www/

# Skip files by extension
rsync -av --exclude '*.log' --exclude '*.tmp' /var/www/ /backup/www/

# Skip build artifacts across all application types
rsync -av \
--exclude 'node_modules/' \
--exclude 'vendor/' \
--exclude '__pycache__/' \
--exclude '.git/' \
/var/www/app/ /backup/app/

--include — Force Inclusion

Include overrides a broader exclude. Order matters — rsync processes rules top to bottom:

# Sync ONLY PHP files (include PHP, exclude everything else)
rsync -av --include='*.php' --exclude='*' /var/www/ /backup/php-only/

# Sync only specific config files
rsync -av \
--include='*.conf' \
--include='*.yml' \
--include='*.yaml' \
--exclude='*' \
/etc/ /backup/configs/
Rule Order

Include/exclude rules are processed in order. The first matching rule wins. Always put --include before --exclude:

# Correct: includes first, then broad exclude
rsync -av --include='*.php' --exclude='*' /src/ /dest/

# Wrong: exclude matches everything before include is checked
rsync -av --exclude='*' --include='*.php' /src/ /dest/

--exclude-from and --include-from — Rules from Files

For complex or reusable filter sets, store patterns in a file:

/etc/rsync/web-excludes.txt
# Build artifacts and dependencies
node_modules/
vendor/
__pycache__/
.git/

# Temporary and cache files
*.log
*.tmp
*.swp
cache/
tmp/

# Sensitive files
.env
*.pem
*.key
id_rsa*
rsync -av --exclude-from=/etc/rsync/web-excludes.txt /var/www/ /backup/www/
tip

An exclude file is the cleanest approach when you have more than 3–4 exclude patterns. It keeps commands readable and makes patterns easy to maintain across multiple scripts.

--filter — Advanced Rule Syntax

The --filter flag provides more powerful rule processing:

Filter RuleMeaningExample
- PATTERNExclude--filter='- cache/'
+ PATTERNInclude--filter='+ *.php'
:- FILERead excludes from FILE--filter=':- .gitignore'
. FILERead merge-file rules--filter='. /etc/rsync/rules'

Using .gitignore as an Exclude Source

One of the most useful filter tricks for deploying code from a development directory:

# Automatically exclude everything listed in .gitignore
rsync -av --filter=':- .gitignore' /var/www/app/ user@prod:/var/www/app/

This is perfect for deployment workflows where .gitignore already excludes build artifacts, dependencies, and local configs.

Pattern Anchoring Rules

How rsync interprets the position of patterns:

PatternBehavior
cache/Matches any directory named cache at any depth
/cache/Matches only cache at the root of the transfer
*.logMatches .log files in the immediate directory
**/*.logMatches .log files at any depth
logs/Trailing / means match directories only, not files named logs

Examples

# Exclude ALL cache directories at any depth
rsync -av --exclude 'cache/' /var/www/ /backup/

# Exclude ONLY the top-level cache directory
rsync -av --exclude '/cache/' /var/www/ /backup/

# Exclude .log files at any depth
rsync -av --exclude '**/*.log' /var/www/ /backup/

# Exclude directories only (not files with the same name)
rsync -av --exclude 'tmp/' /var/www/ /backup/

Practical Filter Combinations

Deploy Code Without Secrets or Build Artifacts

rsync -avz \
--exclude='.env' \
--exclude='.git/' \
--exclude='node_modules/' \
--exclude='vendor/' \
--exclude='*.log' \
/var/www/app/ user@production:/var/www/app/

Backup Only Database Dumps (SQL Files)

rsync -av \
--include='*.sql' \
--include='*.sql.gz' \
--exclude='*' \
/var/backups/ /mnt/external/db-backups/

Sync Configuration Files Only

rsync -av \
--include='*/' \
--include='*.conf' \
--include='*.yml' \
--include='*.yaml' \
--include='*.json' \
--include='.env.example' \
--exclude='*' \
/etc/ /backup/config/
Why --include='*/' Is Needed

When combining includes and excludes, you need --include='*/' so rsync can descend into subdirectories. Without it, the broad --exclude='*' prevents rsync from entering any directory at all.

Sync Everything Except Large Media Files

rsync -av \
--exclude='*.mp4' \
--exclude='*.mov' \
--exclude='*.zip' \
--exclude='*.tar.gz' \
--max-size='50M' \
/var/www/uploads/ /backup/uploads/

Testing Patterns with Dry Run

Before applying any filter rules to production, always verify with --dry-run:

# Preview what will be transferred with your filters
rsync -av --dry-run \
--exclude='cache/' \
--exclude='*.log' \
/var/www/ /backup/www/

# Add --itemize-changes for more detail
rsync -av --dry-run --itemize-changes \
--exclude-from=/etc/rsync/excludes.txt \
/var/www/ /backup/www/

The --itemize-changes flag shows exactly what rsync would do for each file:

  • >f+++++++++ — New file being sent
  • >f.st...... — File with changed size and timestamp
  • *deleting — File that would be deleted (with --delete)

Common Pitfalls

PitfallWhat HappensFix
Include after excludeInclude rule never triggers because exclude already matchedPut --include rules before --exclude
Missing **/ prefixPattern only matches at root levelUse **/ to match at any depth
Trailing / missing on directoriesMatches files AND directories with that nameAdd / to match only directories
Not including */ with selective includesRsync can't descend into subdirectoriesAdd --include='*/' before your specific includes
Shell expanding wildcardsShell resolves * before rsync sees itQuote patterns: '*.log' not *.log

Quick Reference

# Exclude a directory
--exclude 'cache/'

# Exclude by extension
--exclude '*.log'

# Include specific types, exclude rest
--include '*.php' --exclude '*'

# Load excludes from file
--exclude-from=excludes.txt

# Follow .gitignore rules
--filter=':- .gitignore'

# Match at any depth
--exclude '**/*.tmp'

# Anchor to transfer root
--exclude '/cache/'

# Always test first
rsync -av --dry-run --itemize-changes ...

What's Next