Zero-Downtime WordPress Deployment with rsync and SSH

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.

Leave Comment

Your email address will not be published. Required fields are marked *