All Articles

Stop Memorizing rsync Flags: Use a Wrapper Instead

`rsync-profiles` is a simple bash wrapper that replaces long, error-prone `rsync` commands with named profiles. Define your source, target, and exclusions in an INI-style config file, then sync with a single command like `rsync-profiles myprofile`. It supports remote servers via SSH, dry-run previews, custom rsync flags, and profile listing. Great for anyone tired of fat-fingering remote paths or digging through bash history to find that one sync command they ran three months ago.

I work frequently with remote servers, and one of my biggest challenges is keeping static assets synchronized—especially as the file exclusion list grows. While bash history helps mitigate this issue, I've accidentally triggered rsync to the wrong remote directory on more than one occasion.

rsync-profiles

rsync-profiles is a lightweight bash script that lets you create named sync profiles so you can mirror directories with a single command — rsync-profiles target — instead of typing out long rsync commands each time.

https://github.com/robertz/rsync-profiles/tree/main

Generate a starter config file with example profiles:

rsync-profiles --init

This creates the file at:

~/.config/rsync-profiles/profiles.conf

Open it in any text editor to define your own profiles. The --init command will prompt before overwriting an existing file.

Custom config location: Set the RSYNC_PROFILES_CONFIG_DIR environment variable to use a different directory.

export RSYNC_PROFILES_CONFIG_DIR=~/Dropbox/config/rsync-profiles

Config File Format

The config file uses a simple INI-style format. Each profile starts with a [profile-name] header and contains key-value pairs beneath it.

# Lines starting with # are comments and are ignored

[target]
source = ~/projects/target/
target = /backups/target
exclude = .git
exclude = node_modules
exclude = *.log
exclude = .env

Keys

KeyRequiredDescription
source✅ YesThe directory to sync from
target✅ YesThe directory to sync to (local path or user@host:/path)
excludeNoA file or folder pattern to skip. Repeat for multiple exclusions
rsync_optsNoExtra rsync flags, space-separated (e.g., --compress)

Source path: trailing slash matters

A trailing / on the source path syncs the contents of the directory rather than the directory itself. This is almost always what you want.

# Syncs the CONTENTS of website/ into /backups/website/
source = ~/projects/website/

# Syncs the FOLDER itself, creating /backups/website/website/
source = ~/projects/website

Syncing a Profile

rsync-profiles <profile-name>

Example:

rsync-profiles target

This runs rsync with the settings defined in the [target] profile. You'll see output like:

▸ Profile:  target
▸ Source:   /Users/rob/projects/target/
▸ Target:   /backups/target
▸ Excludes: .git node_modules *.log .env

▸ Running: rsync -avh --delete --exclude .git --exclude node_modules ...

sending incremental file list
src/main.py
src/utils.py

sent 4.2K bytes  received 89 bytes  8.58K bytes/sec
total size is 48.3K  speedup is 10.98

✓ Sync complete for [target]

Dry run — preview changes without syncing

Pass --dry-run to see exactly what would be transferred before committing:

rsync-profiles target --dry-run

This is highly recommended the first time you run a new profile.

Default rsync behavior

The script runs rsync with -avh --delete by default:

FlagEffect
-aArchive mode — preserves permissions, timestamps, and symlinks
-vVerbose output showing transferred files
-hHuman-readable file sizes
--deleteRemoves files from the target that no longer exist in the source

⚠️ --delete is on by default. Files deleted from the source will also be deleted from the target. Use --dry-run first if you're unsure.


Excluding Files and Folders

Add one exclude line per pattern in your profile. Patterns follow standard rsync exclude rules.

[target]
source = ~/projects/target/
target = /backups/target
exclude = .git           # Exact directory name
exclude = node_modules   # Exact directory name
exclude = *.log          # Wildcard: any file ending in .log
exclude = .env           # Exact filename
exclude = dist/          # Trailing slash: only match directories
exclude = **/.DS_Store   # Double wildcard: match in any subdirectory

Common exclusions to consider:

  • .git — version control history
  • node_modules — JavaScript dependencies (reinstallable)
  • __pycache__, *.pyc — Python bytecode
  • .env — environment files containing secrets
  • *.log — log files
  • dist/, build/ — generated output

Syncing to a Remote Server

Set target to a remote path using standard SSH syntax:

[target]
source = ~/projects/target/
target = [email protected]:/var/www/target
exclude = .git
exclude = node_modules
rsync_opts = --compress

rsync uses SSH under the hood. For passwordless syncing, ensure your SSH key is added to the remote server's ~/.ssh/authorized_keys.

You can also sync from a remote server to a local path by swapping source and target:

[pull-backups]
source = [email protected]:/var/backups/
target = ~/local-backups

Listing Profiles

See all configured profiles at a glance:

rsync-profiles --list

Output:

Configured profiles:

  target        ~/projects/target/ → [email protected]:/var/www/target  (3 exclusions)

All Options

Usage: rsync-profiles <profile-name> [options]
       rsync-profiles --list
       rsync-profiles --init

Options:
  --dry-run     Preview what would be transferred without making changes
  --verbose     Show more detailed rsync output
  --list        List all configured profiles
  --init        Create a starter config file
  -h, --help    Show this help message

Tips and Troubleshooting

Profile not found Make sure the profile name matches the header in your config exactly (case-sensitive). Run rsync-profiles --list to confirm the name.

Always dry-run a new profile first Before running a sync on important data, use --dry-run to verify the source, target, and exclusions look correct.

Automate with cron Run a profile on a schedule by adding it to your crontab (crontab -e):

# Sync website every day at 2am
0 2 * * * /usr/local/bin/rsync-profiles website >> ~/logs/rsync.log 2>&1

Using a shared config across machines Store your config file in a synced folder (Dropbox, iCloud, etc.) and point the script at it:

# Add to ~/.bashrc or ~/.zshrc
export RSYNC_PROFILES_CONFIG_DIR=~/Dropbox/config/rsync-profiles

https://github.com/robertz/rsync-profiles/tree/main


Cover Photo by Ato Aikins on Unsplash