How to Use rsync to Exclude All Directories Except Specific Ones: A Complete Guide


2 views

When working with rsync, a common but tricky scenario is needing to exclude all directories except a few specific ones you want to sync. The directory structure might look like:

source_dir/
  some_dir/
  another_dir/
  some_file.php
  some_other_file.txt
  include_this_dir/
  include_that_dir/
  yet_another_dir/

Most solutions you'll find online suggest using multiple --exclude parameters or complex pattern matching, but these approaches have limitations:

  • They require explicitly listing every directory to exclude
  • They don't handle new directories that might appear later
  • They often have unexpected behavior with file exclusion

Here's the correct way to include only specific directories while excluding everything else:

rsync -av --dry-run --delete \
--include='include_this_dir/' \
--include='include_that_dir/' \
--include='*/' \
--exclude='*' \
source_dir/ dest_dir

The magic happens through careful ordering of include/exclude filters:

  1. --include='include_this_dir/' - Explicitly includes our target directories
  2. --include='include_that_dir/' - Another explicit include
  3. --include='*/' - Allows rsync to scan all directories (but not their contents yet)
  4. --exclude='*' - Excludes everything else

When implementing this solution, keep in mind:

# This will NOT work (order matters!):
rsync -av --exclude='*' --include='special_dir/' source dest

# This WILL work:
rsync -av --include='special_dir/' --exclude='*' source dest

Also note that trailing slashes matter in your patterns. special_dir is different from special_dir/.

For complex scenarios, you might prefer using a filter file:

# rsync_filter.txt
+ include_this_dir/
+ include_that_dir/
+ */
- *

Then run:

rsync -av --filter='. rsync_filter.txt' source_dir/ dest_dir

Always test with --dry-run first to verify your patterns:

rsync -avn --delete \
--include='include_this_dir/' \
--include='include_that_dir/' \
--include='*/' \
--exclude='*' \
source_dir/ dest_dir

Suppose you need to sync only node_modules and src from a project directory:

rsync -av --delete \
--include='node_modules/' \
--include='src/' \
--include='*/' \
--exclude='*' \
project/ backup_server:/backups/project/
  • If nothing is being included, check your filter order
  • Use -vv for verbose output to see filtering decisions
  • Remember rsync processes filters in order until first match
  • Trailing slashes matter - they distinguish files from directories

When working with rsync, one common yet tricky scenario is syncing only specific directories while excluding all others by default. This pattern becomes crucial when:

  • You want future-proof exclusions (newly added directories should be automatically excluded)
  • The source contains numerous directories where manual exclusion would be tedious
  • You need precise control over directory-level synchronization

Many developers attempt solutions like:

rsync -av --exclude="*" --include="include_this_dir/" --include="include_that_dir/" source/ dest/

But encounter these issues:

  1. Excluded parent directories prevent child directory matching
  2. File-level patterns don't properly handle directory structures
  3. Order of include/exclude rules affects the outcome

After extensive testing across rsync versions 3.x, here's the reliable approach:

rsync -av \
  --include="/include_this_dir/" \
  --include="/include_that_dir/" \
  --include="/include_this_dir/**" \
  --include="/include_that_dir/**" \
  --exclude="*" \
  --exclude="*/" \
  source_dir/ dest_dir/

The magic happens through these carefully ordered rules:

  • --include="/dir_name/" explicitly includes the directory itself
  • --include="/dir_name/**" includes all contents recursively
  • --exclude="*" handles all files
  • --exclude="*/" catches all other directories

Case 1: Including multiple specific directories

rsync -av \
  --include="/dir1/" --include="/dir2/" --include="/special_assets/" \
  --include="/dir1/**" --include="/dir2/**" --include="/special_assets/**" \
  --exclude="*" \
  --exclude="*/" \
  project/ backup/

Case 2: With delete option to maintain exact mirror

rsync -av --delete \
  --include="/config/" --include="/config/**" \
  --include="/templates/" --include="/templates/**" \
  --exclude="*" \
  --exclude="*/" \
  source_server:/path/ /local_mirror/

Always test with --dry-run first. For verbose output:

rsync -av --dry-run --verbose \
  --include="/important/" --include="/important/**" \
  --exclude="*" \
  --exclude="*/" \
  source/ destination/ | grep -E "^(>|c)"

This filters output to show only actual transfer decisions.