Skip to main content

Secure Transfers with Rsync

Every rsync transfer over a network should be encrypted. Rsync uses SSH as its transport layer by default, which means your file data, credentials, and metadata are protected with the same encryption used for SSH connections.

info

On modern systems (OpenSSH 7.0+), rsync over SSH is the default behavior. You don't need to explicitly add -e ssh — but understanding SSH configuration gives you control over ports, keys, and access restrictions.

How Rsync + SSH Works

flowchart LR
SRC["Source Server<br/>rsync client"] -->|"SSH tunnel<br/>(encrypted)"| DST["Destination Server<br/>rsync server process"]

subgraph "SSH Layer"
AUTH["Authentication<br/>(keys or password)"]
ENC["AES-256 Encryption<br/>(data in transit)"]
end

When you run rsync -avz user@server:/path/, rsync:

  1. Opens an SSH connection to the remote server
  2. Starts an rsync process on the remote end
  3. All file data flows through the encrypted SSH tunnel

SSH Key Authentication Setup

Key-based authentication is essential for automated rsync operations (cron jobs, scripts). It's also more secure than password authentication.

Step 1: Generate an SSH Key Pair

# Create a dedicated key for backup operations
ssh-keygen -t ed25519 -C "rsync-backup-key" -f ~/.ssh/rsync_backup_key

# Or use RSA for compatibility with older systems
ssh-keygen -t rsa -b 4096 -C "rsync-backup-key" -f ~/.ssh/rsync_backup_key
tip

Use a dedicated key pair for backup operations rather than your personal SSH key. This allows you to revoke backup access without affecting your login access.

Step 2: Copy the Public Key to the Remote Server

ssh-copy-id -i ~/.ssh/rsync_backup_key.pub user@backup-server

# For non-standard SSH port
ssh-copy-id -i ~/.ssh/rsync_backup_key.pub -p 2222 user@backup-server

Step 3: Use the Key with Rsync

rsync -avz -e "ssh -i ~/.ssh/rsync_backup_key" \
/var/www/html/ user@backup-server:/backups/www/

Step 4: Simplify with SSH Config

~/.ssh/config
Host backup-server
HostName 198.51.100.5
User backupuser
Port 2222
IdentityFile ~/.ssh/rsync_backup_key
# Optional: Disable forwarding for security
ForwardAgent no
ForwardX11 no

Now rsync commands are clean:

rsync -avz /var/www/html/ backup-server:/backups/www/

Custom SSH Port

If your SSH server runs on a non-standard port:

# Using -e flag
rsync -avz -e "ssh -p 2222" /var/www/ user@server:/backup/www/

# Using SSH config (cleaner)
# Add Port 2222 to the Host entry in ~/.ssh/config
rsync -avz /var/www/ myserver:/backup/www/

Restricting Rsync Access

For production backup systems, limit what the rsync user can do on the remote server.

Option 1: Force a Specific Command in authorized_keys

Restrict an SSH key to only run rsync:

~/.ssh/authorized_keys on backup server
command="rsync --server --sender -avz . /backups/",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... rsync-backup-key

This ensures:

  • The key can only run rsync (not login interactively)
  • No port forwarding or X11 forwarding allowed
  • Restricted to a specific directory

Option 2: Dedicated Rsync User

Create a user specifically for backup operations:

# On the backup server
sudo useradd -m -s /bin/bash rsyncuser
sudo mkdir -p /backups
sudo chown rsyncuser:rsyncuser /backups

# Copy your public key
ssh-copy-id -i ~/.ssh/rsync_backup_key.pub rsyncuser@backup-server

Option 3: Using --rsync-path with sudo

When the rsync user needs root-level read access:

rsync -avz --rsync-path="sudo rsync" \
user@server:/etc/ /backup/config/

Add a sudoers rule so the user can only run rsync:

/etc/sudoers.d/rsync-backup
rsyncuser ALL=(root) NOPASSWD: /usr/bin/rsync --server *

SSH Hardening for Rsync

Server-Side SSH Configuration

/etc/ssh/sshd_config — recommended hardening
# Use a non-standard port
Port 2222

# Disable root login
PermitRootLogin no

# Allow only specific users
AllowUsers deployuser rsyncuser

# Disable password authentication (require keys)
PasswordAuthentication no
PubkeyAuthentication yes

# Limit authentication attempts
MaxAuthTries 3

# Idle timeout
ClientAliveInterval 300
ClientAliveCountMax 2

After editing, restart SSH:

sudo systemctl restart ssh
warning

Before disabling password authentication, make sure your SSH key works. Test with:

ssh -i ~/.ssh/rsync_backup_key user@server

If key authentication fails after disabling passwords, you'll be locked out.

Firewall Integration

# UFW: Allow SSH only on custom port
sudo ufw allow 2222/tcp
sudo ufw deny 22/tcp

# Or allow SSH only from specific IPs
sudo ufw allow from 203.0.113.10 to any port 2222 proto tcp

Protecting Sensitive Files During Transfer

Exclude Secrets from Sync

Always exclude environment files, private keys, and credentials:

rsync -avz \
--exclude='.env' \
--exclude='*.pem' \
--exclude='*.key' \
--exclude='id_rsa*' \
--exclude='wp-config.php' \
/var/www/app/ user@staging:/var/www/app/

Encrypt Backup Data at Rest

For offsite backups, encrypt before transfer:

# Encrypt database dump with GPG before rsyncing
mysqldump -u root --single-transaction --all-databases \
| gzip | gpg --symmetric --cipher-algo AES256 \
> db_backup.sql.gz.gpg

rsync -avzP db_backup.sql.gz.gpg user@backup:/offsite/db/

# To decrypt:
gpg --decrypt db_backup.sql.gz.gpg | gunzip | mysql -u root -p

Automated Secure Backup Script

#!/bin/bash
# secure-backup.sh — Encrypted rsync backup with key auth
set -e

REMOTE="backup-server" # Defined in SSH config
TIMESTAMP=$(date +%F)
LOG="/var/log/rsync-backup.log"

echo "[$TIMESTAMP] Starting secure backup" >> "$LOG"

# Sync application files
rsync -avz \
--exclude='.env' \
--exclude='cache/' \
--exclude='*.log' \
/var/www/html/ "$REMOTE:/backups/$TIMESTAMP/www/" \
>> "$LOG" 2>&1

# Sync database dump
rsync -avzP /var/backups/db/ "$REMOTE:/backups/$TIMESTAMP/db/" \
>> "$LOG" 2>&1

echo "[$TIMESTAMP] Backup complete" >> "$LOG"

Schedule with cron (SSH config handles the key automatically):

0 3 * * * /usr/local/bin/secure-backup.sh

Troubleshooting Secure Connections

ErrorCauseFix
Permission denied (publickey)SSH key not acceptedCheck key permissions: chmod 600 ~/.ssh/id_rsa
Connection refusedSSH not on expected portVerify port: ssh -p 2222 user@server
Host key verification failedServer key changedUpdate ~/.ssh/known_hosts or verify server identity
rsync: connection unexpectedly closedFirewall blocking, SSH timeoutCheck firewall rules, increase ClientAliveInterval
Permission denied on remote filesRsync user can't access targetUse --rsync-path="sudo rsync" or fix permissions

Common Pitfalls

PitfallRiskPrevention
Using password auth for automated backupsCredentials in scripts or exposed via expectUse SSH key pairs
Not restricting backup SSH keysCompromised key gives full server accessUse command= restriction in authorized_keys
Running rsync as rootExcessive privileges if script is compromisedUse dedicated user with minimal permissions
No firewall on SSH portExposed to brute force attacksUse UFW/iptables + Fail2Ban
Syncing .env / wp-config.php to stagingExposes production credentialsAlways --exclude sensitive files
Not encrypting offsite backupsData exposed if backup server is compromisedGPG-encrypt before transfer

Quick Reference

# Generate backup SSH key
ssh-keygen -t ed25519 -f ~/.ssh/rsync_backup_key

# Copy key to remote
ssh-copy-id -i ~/.ssh/rsync_backup_key.pub user@server

# Rsync with specific key
rsync -avz -e "ssh -i ~/.ssh/rsync_backup_key" /src/ user@server:/dest/

# Custom SSH port
rsync -avz -e "ssh -p 2222" /src/ user@server:/dest/

# Rsync with sudo on remote
rsync -avz --rsync-path="sudo rsync" user@server:/etc/ /backup/

# Exclude secrets
rsync -avz --exclude='.env' --exclude='*.key' /app/ user@server:/app/

What's Next