Pull Script (Remote to Local)
The pull.sh script syncs a remote site’s database and files to your local WP Local environment.
Prerequisites
Section titled “Prerequisites”- Local by WP installed with the site set up
- SSH access to the remote server
- The script must be run from the Local Site Shell (Open Local → select site → “Open Site Shell”)
What it does
Section titled “What it does”- Exports the remote database via SSH + WP-CLI
- Downloads the SQL file via rsync
- Cleans up the remote backup
- Detects the remote table prefix and updates
wp-config.phpto match (e.g.wp_→PFzYaBYi_) - Resets the local database to remove any leftover tables
- Imports the database as-is (no SQL file modification needed)
- Runs search-replace on the domain (catches both
http://andhttps://) - Flushes cache, transients, and rewrite rules
- Syncs
wp-content/files via rsync with--delete(removes local files that don’t exist on remote)
Full script
Section titled “Full script”Copy this to the site root (e.g. /Users/.../Local Sites/sitename/pull.sh) and update the config block.
#!/bin/bash
# ============================================================# Pull Script — Production → Local# Pulls database + files from remote server to local environment.## NOTE: This must be run from the Local (WP Local) Site Shell.# Open Local → select the site → click "Open Site Shell".# This opens the container with access to wp-cli and MySQL.## Usage: bash pull.sh# ============================================================
# ── Configuration ─────────────────────────────────────REMOTE_SSH_HOST="user@sftp.example.com"REMOTE_SSH_PORT="2222"REMOTE_PATH="/site-www"REMOTE_TEMP_DIR=".tmp-exports" # safe dir for temp DB exports
LOCAL_WP_PATH="/Users/you/Local Sites/sitename/app/public"
REMOTE_SITE_DOMAIN="example.com"LOCAL_SITE_DOMAIN="dev.sitename.local"
# Files/folders to exclude from syncEXCLUDES=( "cache/" "uploads/cache/" "upgrade/" "debug.log" "*.log" "object-cache.php" "advanced-cache.php" ".DS_Store")
# ── Preflight ─────────────────────────────────────────echo "🔍 Checking requirements..."
command -v wp >/dev/null 2>&1 || { echo "❌ WP-CLI is required. Install: brew install wp-cli"; exit 1; }
echo "✓ Ready to pull from: $REMOTE_SITE_DOMAIN"echo " → To local: $LOCAL_SITE_DOMAIN"echo ""read -p "Continue? (y/n) " -n 1 -recho ""[[ ! $REPLY =~ ^[Yy]$ ]] && exit 0
# ── Database Export ───────────────────────────────────TIMESTAMP=$(date +%Y%m%d_%H%M%S)DB_FILE="wp_backup_${TIMESTAMP}.sql"
echo ""echo "📦 Exporting remote database..."ssh -p $REMOTE_SSH_PORT $REMOTE_SSH_HOST "wp db export $REMOTE_TEMP_DIR/$DB_FILE --quiet"
if [ $? -ne 0 ]; then echo "❌ Failed to export remote database." exit 1fiecho "✓ Database exported (stored in $REMOTE_TEMP_DIR — not publicly accessible)"
# ── Download Database ─────────────────────────────────echo "⬇️ Downloading database..."rsync -az -e "ssh -p $REMOTE_SSH_PORT" "${REMOTE_SSH_HOST}:${REMOTE_PATH}/${REMOTE_TEMP_DIR}/${DB_FILE}" "${LOCAL_WP_PATH}/${DB_FILE}"
if [ ! -f "${LOCAL_WP_PATH}/${DB_FILE}" ]; then echo "❌ Failed to download database file." exit 1fiecho "✓ Database downloaded"
# ── Clean Up Remote ───────────────────────────────────echo "🧹 Cleaning up remote backup..."ssh -p $REMOTE_SSH_PORT $REMOTE_SSH_HOST "rm ${REMOTE_TEMP_DIR}/${DB_FILE}"
# ── Match Table Prefix ────────────────────────────────cd "$LOCAL_WP_PATH"LOCAL_PREFIX=$(grep "table_prefix" wp-config.php | sed "s/.*'\(.*\)'.*/\1/")REMOTE_PREFIX=$(grep "CREATE TABLE" "$DB_FILE" | head -1 | sed "s/.*\`\([^_]*_\).*/\1/")
if [ "$REMOTE_PREFIX" != "$LOCAL_PREFIX" ] && [ -n "$REMOTE_PREFIX" ]; then echo "🔧 Updating wp-config.php table prefix ($LOCAL_PREFIX → $REMOTE_PREFIX)..." sed -i '' "s/\\\$table_prefix = '${LOCAL_PREFIX}'/\\\$table_prefix = '${REMOTE_PREFIX}'/" wp-config.php echo "✓ wp-config.php updated"fi
# ── Reset & Import Database ──────────────────────────echo "📥 Resetting local database and importing..."wp db reset --yes --quiet
wp db import "$DB_FILE"
if [ $? -ne 0 ]; then echo "❌ Failed to import database. File saved at: ${LOCAL_WP_PATH}/${DB_FILE}" exit 1fiecho "✓ Database imported"
# ── Search & Replace ──────────────────────────────────echo "🔄 Running search-replace..."wp search-replace "$REMOTE_SITE_DOMAIN" "$LOCAL_SITE_DOMAIN" --all-tables --quietecho "✓ URLs updated"
# ── Flush Cache ───────────────────────────────────────wp cache flush --quiet 2>/dev/nullwp transient delete --all --quiet 2>/dev/nullwp rewrite flush --quiet 2>/dev/null
# ── Sync Files ────────────────────────────────────────echo "📂 Syncing wp-content files..."
# Build exclude flagsEXCLUDE_FLAGS=""for exc in "${EXCLUDES[@]}"; do EXCLUDE_FLAGS="$EXCLUDE_FLAGS --exclude=$exc"done
rsync -avz --progress --delete \ $EXCLUDE_FLAGS \ -e "ssh -p $REMOTE_SSH_PORT" \ "${REMOTE_SSH_HOST}:${REMOTE_PATH}/wp-content/" \ "${LOCAL_WP_PATH}/wp-content/"
if [ $? -ne 0 ]; then echo "⚠️ File sync had issues — check output above."else echo "✓ Files synced"fi
# ── Cleanup ───────────────────────────────────────────# Keeping database file for debugging: ${LOCAL_WP_PATH}/${DB_FILE}# rm -f "${LOCAL_WP_PATH}/${DB_FILE}"
echo ""echo "✅ Pull complete!"echo " Database: imported and URLs replaced"echo " Files: synced from remote"echo " Local site: https://$LOCAL_SITE_DOMAIN"Configuration
Section titled “Configuration”Update these 6 values for each site:
| Variable | Description |
|---|---|
REMOTE_SSH_HOST | SSH connection string (e.g. user@sftp.host.com) |
REMOTE_SSH_PORT | SSH port (often 22 or 2222) |
REMOTE_PATH | Remote WordPress root directory |
REMOTE_TEMP_DIR | A non-public directory on the server for temp DB exports |
LOCAL_WP_PATH | Full path to the local WordPress public directory |
REMOTE_SITE_DOMAIN / LOCAL_SITE_DOMAIN | Domain only, no protocol |
Important: domain-only search-replace
Section titled “Important: domain-only search-replace”The search-replace uses domain only (no https:// prefix). This ensures both http:// and https:// references are caught — remote databases sometimes store URLs with inconsistent protocols.
The --delete flag
Section titled “The --delete flag”The rsync uses --delete so that files which exist locally but not on the remote server are removed. This ensures your local environment is an exact mirror of the remote wp-content/ directory. Without this flag, stale local files (e.g. a child theme template you deleted on production) would persist indefinitely.
File excludes
Section titled “File excludes”These are excluded from the file sync:
cache/uploads/cache/upgrade/debug.log*.logobject-cache.phpadvanced-cache.php.DS_StoreSetting up for a new site
Section titled “Setting up for a new site”- Copy
pull.shto the new site’s root (e.g./Users/.../Local Sites/newsite/) - Update the 6 config variables at the top
- Ensure you have SSH access to the remote server (
ssh -p PORT USER@HOST) - Open Local Site Shell and run
bash pull.sh
How the table prefix works
Section titled “How the table prefix works”Rather than rewriting every table prefix inside the SQL dump (which can fail on non-UTF8 binary data), the script updates wp-config.php to match whatever prefix the remote database uses. This means:
- The SQL file is imported exactly as exported — no
sedmodifications - The local database is fully reset before import to prevent leftover tables from a previous pull
wp-config.php’s$table_prefixis kept in sync with the actual database
Troubleshooting
Section titled “Troubleshooting”| Issue | Fix |
|---|---|
wp: command not found | Run from Local Site Shell, not regular terminal |
wp db import fails | Check MySQL is running in Local |
| SSH connection refused | Verify SSH host, port, and key are correct |
| URLs still pointing to remote | Check the remote DB uses the same domain you configured — try wp option get siteurl after import |
| Site redirects to live domain | Clear browser cache or use incognito — browsers cache 301 redirects aggressively |
| PHP memory exhaustion | Increase memory_limit in Local’s conf/php/php.ini.hbs and add WP_MEMORY_LIMIT to wp-config.php |