rsync over SSH is the most reliable way to deploy a WordPress site — it transfers only changed files, preserves permissions, and can exclude sensitive files like wp-config.php without a Git checkout on the server.
Problem: Deploying a WordPress theme or plugin update to a production server requires either manual FTP uploads, a downtime window for a full sync, or a complex CI/CD pipeline — there is no native zero-downtime mechanism.
Solution: Use rsync with the --delete flag and SSH key authentication to sync only changed files in seconds. Combine it with a --link-dest hard-link snapshot for atomic rollback: the new release syncs to a new directory, then a symlink is atomically updated to point at it, giving zero-downtime deploys with instant rollback.
The examples below show a full deploy command with the right exclusions, a zero-downtime swap using a staging directory, and a shell script you can call from a CI/CD pipeline.
# Basic rsync deploy — push local build to production
rsync -avz --delete --exclude='.git/' --exclude='.env' --exclude='wp-config.php' --exclude='wp-content/uploads/' --exclude='wp-content/cache/' --exclude='node_modules/' --exclude='*.log' -e "ssh -p 22 -i ~/.ssh/deploy_key" ./ deploy@production.example.com:/var/www/html/
# Flags:
# -a archive mode (preserve permissions, symlinks, timestamps)
# -v verbose
# -z compress during transfer
# --delete remove files on server that no longer exist locally
Zero-downtime deploy using a staging directory and atomic symlink swap:
#!/usr/bin/env bash
# deploy.sh — zero-downtime WordPress deploy via rsync + symlink swap
set -euo pipefail
REMOTE_USER="deploy"
REMOTE_HOST="production.example.com"
REMOTE_BASE="/var/www/releases"
REMOTE_CURRENT="/var/www/current" # Nginx/Apache document root → symlink
RELEASE="$(date +%Y%m%d_%H%M%S)"
REMOTE_RELEASE="${REMOTE_BASE}/${RELEASE}"
SSH_KEY="~/.ssh/deploy_key"
echo "→ Syncing files to new release directory..."
rsync -az --delete --exclude='.git/' --exclude='wp-config.php' --exclude='wp-content/uploads/' --exclude='wp-content/cache/' -e "ssh -i ${SSH_KEY}" ./ "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_RELEASE}/"
echo "→ Linking shared wp-config.php and uploads..."
ssh -i "${SSH_KEY}" "${REMOTE_USER}@${REMOTE_HOST}" bash </dev/null || true
echo "→ Keeping last 5 releases..."
ls -1dt ${REMOTE_BASE}/*/ | tail -n +6 | xargs rm -rf
ENDSSH
echo "Deploy complete: ${RELEASE}"
NOTE: Store the deploy key in your CI/CD secret manager (GitHub Actions secrets, GitLab CI variables) and never commit it to the repository. Always test the rsync command with --dry-run first to verify the exclusion list before a real deployment.