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​