“I want to deploy my WordPress site built locally to production. But using a plugin hits the file size limit, and I’m hesitant to use phpMyAdmin every time…” Many of you probably share this concern.
In this article, I’ll explain how to safely deploy from a local environment to a production environment using the WP-CLI `wp db` command and rsync. Since this process relies solely on the command line without plugins, there are no file size limits, and you can repeat it as many times as needed.
It might feel more daunting compared to GUI-based migration plugins, but once you understand the mechanism, it boils down to three simple steps: “Export the database → Replace the URLs → Sync the files.” Once you’ve established the workflow, future deployments will take just a few minutes.
- Overall Deployment Workflow
- Prerequisites
- Step 1: Backing Up the Production Database
- Step 2: Database Migration
- Step 3: URL Replacement (search-replace)
- Step 4: File Synchronization with rsync
- Items that must never be synced
- A Safe Deployment Workflow via Staging
- Recommendation for Shell Scripting
- Overview of Automated Deployment with GitHub Actions
- Troubleshooting
- Frequently Asked Questions
- Summary
Overall Deployment Workflow
Deployment from local to production proceeds in the following four steps.
- Backup the production DB (as a safety net for rollback)
- Export the local DB → Import to production
- URL replacement (
wp search-replacereplace local URLs with production URLs) - File synchronization with rsync (transfer themes, plugins, and uploaded images all at once)
The most critical step is Step 1: the backup. Since this operation overwrites the production database, you must ensure you have a state that allows for rollback in case of an emergency before proceeding with deployment.
Differences from plugin migration
Plugins like All-in-One WP Migration are convenient, but the free version has an import size limit, so you’ll quickly hit a wall with sites that have a lot of images. Additionally, since you have to manually open the WordPress admin panel and perform Export → Import each time, the time lost increases significantly as deployment frequency rises.
On the other hand, the combination of WP-CLI and rsync has no size limits and can be executed with a single command, which is a major advantage. Integrating this into a shell script can further streamline the deployment process.
| Comparison Criteria | Plugin Migration | WP-CLI and rsync |
|---|---|---|
| File Size Limit | Free version has a limit (typically 512MB) | No limit |
| Operation | GUI (Control Panel) | CLI (Terminal) |
| Reproducibility | Manual each time | Automated via scripting |
| URL substitution | Plugin handles it automatically | wp search-replace Explicitly executed |
| Differential transfer | Not supported (full transfer) | rsync transfers only the differences |
| Required skills | Low | SSH and command-line operations |
Prerequisites
Requirements
- WordPress in a local environment (Herd, Local, Docker, etc.—any setup is fine)
- WP-CLI (installed on both the local and production servers)
- SSH connection (ability to log in to the production server via SSH)
- rsync (WSL2 is recommended for Windows. The standard macOS version is outdated, so we recommend using the Homebrew version)
Use the Homebrew version of rsync on macOS
The rsync included by default on macOS is an older version (2.6 series), so you may run into issues due to option compatibility or differences in behavior. It is safer to use the newer Homebrew version of rsync.
# Homebrew が未導入なら先にインストール
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# rsync をインストール
brew install rsync
# バージョン確認(3.x ならOK)
rsync --version
If you haven’t installed Homebrew yet, follow the instructions displayed after installation PATH .which rsync The output should be /opt/homebrew/bin/rsync(Apple Silicon) or /usr/local/bin/rsync(Intel), you are using the Homebrew version.
Verifying WP-CLI Installation
Check if WP-CLI is available on both your local machine and your production environment.
# ローカル
wp --version
# 本番サーバー(SSH接続後)
wp --version --allow-root
If the version information is displayed, you’re all set. If you haven’t installed it yet, please refer to the WP-CLI user guide.
Configuring SSH Connection
An SSH connection is required for remote execution of rsync and WP-CLI. To avoid having to enter your password every time,~/.ssh/config it’s convenient to set up the connection settings in advance.
# ~/.ssh/config
Host my-server
HostName 203.0.113.10
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519
After configuring, ssh my-server verify that you can log in without a password.
Step 1: Backing Up the Production Database
Always back up your current production database before deployment. If you skip this step, you will have no way to revert changes if a problem occurs.
# 本番サーバーでDBをエクスポート(SSHログイン後)
wp db export /tmp/prod-backup-$(date %Y%m%d%H%M%S).sql --path=/var/www/html
--path=/var/www/html This specifies “which WordPress installation the command should be executed on.”wp-config.php refers to the WordPress root directory. Since it does not depend on the current directory immediately after logging in via SSH, it makes it less likely to accidentally manipulate the wrong site.
Using filenames with timestamps prevents multiple backups from getting mixed up.
It is also possible to run the command remotely without logging in via SSH.
# ローカルからSSH経由でリモート実行
ssh my-server "wp db export /tmp/prod-backup-$(date %Y%m%d%H%M%S).sql --path=/var/www/html"
Step 2: Database Migration
Exporting the Local Database
wp db export Export the local database to an SQL file.
# ローカル環境で実行
wp db export /tmp/local-db.sql --path=/path/to/local/wordpress
Importing to Production
Import the exported SQL into the production server’s database. There are two methods.
Method A: Transfer the SQL file and then import it
# SQLファイルを本番サーバーに転送
scp /tmp/local-db.sql my-server:/tmp/
# SSHログインしてインポート
ssh my-server "wp db import /tmp/local-db.sql --path=/var/www/html"
Method B: Import directly via pipe (transfer and import in one step)
# ローカルのSQLをパイプでリモートに流し込む
cat /tmp/local-db.sql | ssh my-server "wp db import - --path=/var/www/html"
Method B is more efficient since it eliminates the file transfer step. However, because no intermediate files are left behind, Method A makes it easier to troubleshoot if a problem occurs.
Step 3: URL Replacement (search-replace)
If you import the database as-is, the URLs contained in article content, widgets, and settings will remain as local URLs (e.g.,https://mysite.test). These must be replaced with the production URLs (e.g.,https://example.com).
Use wp search-replace . It is dangerous to perform this replacement directly using phpMyAdmin or the SQL REPLACE function. The WordPress database contains a large amount of serialized data (PHP objects converted to strings), and simple string replacement will corrupt the data.
# 本番サーバーで実行
wp search-replace 'https://mysite.test' 'https://example.com'
--all-tables
--precise
--recurse-objects
--path=/var/www/html
Meaning of each option
| Option | Description |
|---|---|
--all-tables | Includes tables created by plugins as well as standard WordPress tables |
--precise | Perform precise replacements on a per-column basis (to prevent incorrect replacements) |
--recurse-objects | Recursively replace within serialized data |
Replaces escaped URLs
The WordPress database contains locations where URLs are stored in a slash-escaped state (https://mysite.test). This applies to JSON-based Block Editor data, among other things. In addition to regular URLs, perform replacements on the escaped format as well.
# エスケープ済みURLの置換
wp search-replace 'https://mysite.test' 'https://example.com'
--all-tables
--precise
--path=/var/www/html
Flush the cache and permalinks
After URL replacement, flush the cache and permalinks to ensure no old values remain.
wp cache flush --path=/var/www/html
wp rewrite flush --path=/var/www/html
Step 4: File Synchronization with rsync
Once the database migration is complete, the next step is file synchronization. Using rsync allows you to transfer only the files that have changed, making subsequent syncs extremely fast.
Specify rsync rsync [options] 転送元 転送先 in that order. In the example below,/path/to/local/... is the source (local),my-server:/var/www/html/... is the destination (production).
Additionally, the behavior changes depending on whether / .themes/ As shown here, if there is a trailing slash, only the contents are synchronized,themes but if there is no trailing slash, as shown here, the entire directory is synchronized. To prevent unintended directory structure discrepancies, we recommend using a configuration where both the source and destination / .
Synchronizing Themes
rsync -az --delete
-e "ssh"
/path/to/local/wp-content/themes/
my-server:/var/www/html/wp-content/themes/
Plugin Synchronization
rsync -az --delete
-e "ssh"
/path/to/local/wp-content/plugins/
my-server:/var/www/html/wp-content/plugins/
Synchronizing Uploaded Images
rsync -az --delete
-e "ssh"
--exclude='cache/'
/path/to/local/wp-content/uploads/
my-server:/var/www/html/wp-content/uploads/
Key rsync Options
| Options | Meaning |
|---|---|
-a | Archive mode. Recursively copies files while preserving permissions and timestamps |
-z | Compress during transfer (effective on slow connections) |
--delete | Delete files from the destination that do not exist on the source (mirroring) |
-e "ssh" | Transfer via SSH |
--exclude | Exclude specified patterns |
--dry-run | Preview what will happen without actually transferring |
Be sure to check with `--dry-run` on your first run.--delete Because options are enabled, specifying an incorrect path will delete the actual files.
# まず dry-run で差分を確認
rsync -az --delete --dry-run
-e "ssh"
/path/to/local/wp-content/themes/
my-server:/var/www/html/wp-content/themes/
# 問題なければ --dry-run を外して実行
Items that must never be synced
When syncing files with rsync, there are certain files and directories that must not be included in the sync. If these are transferred by mistake, the production environment may stop working or security risks may arise.
| Items | Reason |
|---|---|
wp-config.php | DB connection information and authentication keys differ between local and production environments. Overwriting them will cause the production environment to stop working |
.htaccess | May contain server-specific rewrite rules |
wp-content/cache/ | There is no point in transferring local cache plugin data to the production environment |
wp-content/upgrade/ | Temporary files for updates |
object-cache.php | Drop-in files for object caching. Settings vary by environment |
advanced-cache.php | Page cache drop-in files. Same as above |
db.php | DB drop-in files. Same as above |
debug.log | Debug logs. There is no need to transfer local logs to the production environment |
Exclude these --exclude , or synchronize only the subdirectories under the WordPress root directory wp-content/ .
It is safer to limit file synchronization wp-content/ to the following: the WordPress core files (wp-admin/、wp-includes/) and files directly under the root directory should wp core update .
A Safe Deployment Workflow via Staging
While deploying directly from your local machine to production is convenient, we recommend using a staging environment for sites that are live.
Recommended Workflow
- Local → Staging: Perform the DB migration and rsync described above on the staging server
- Test on staging: Check for layout issues, broken links, form submissions, and plugin behavior
- Staging → Production: If no issues are found, apply the changes to production using the same procedure
It is easy to set up a staging environment using a subdomain (e.g.,staging.example.com). If using a VPS, you can set it up simply by adding an Nginx virtual host.
Rollback Procedure
If issues are found after deployment, restore using the backup obtained in Step 1.
# バックアップからDBを復元
ssh my-server "wp db import /tmp/prod-backup-20260411120000.sql --path=/var/www/html"
# キャッシュをクリア
ssh my-server "wp cache flush --path=/var/www/html"
If you need to revert files, simply check them out if they are managed with Git. If you are not using Git, it is a good idea to back up the files before running rsync.
Recommendation for Shell Scripting
Typing out these commands manually every time is not practical. By consolidating them into a shell script, you can complete the deployment with a single command.
Key Points for Script Design
Here are the key points to keep in mind when creating your own deployment script.
- Separate Environment Variables into an External File: Write production hostnames, paths, and URLs
.envin a file, keeping them separate from the main script. Design the script so that authentication credentials are not committed to Git - Confirmation prompt for production pushes: Include a “Are you sure you want to proceed?” confirmation before deploying to production. This prevents accidental execution
- Automatic Backups: Incorporate a process to automatically back up the production database before deployment
- Component-by-component execution: Design the system to allow separate synchronization of the database, themes, plugins, and uploads. There are many situations where transferring everything at once is unnecessary
- Replacement of escaped URLs: Automatically perform replacements not only for standard URLs but also for URLs in escaped format (
//) - Dry-run support: Configure the system so that rsync runs in dry-run mode when
DRY_RUN=1, rsync runs in dry-run mode
The following is a simplified example of the script’s basic structure.
#!/usr/bin/env bash
set -euo pipefail
# --- 設定 ---
PROD_HOST="my-server"
PROD_PATH="/var/www/html"
PROD_URL="https://example.com"
LOCAL_PATH="/path/to/local/wordpress"
LOCAL_URL="https://mysite.test"
# --- 本番push時の確認 ---
echo "[WARN] 本番環境にデプロイします。"
read -r -p "続行しますか? [y/N] " confirm
[[ "${confirm}" != "y" ]] && echo "中止しました。" && exit 1
# --- 本番DBバックアップ ---
TIMESTAMP=$(date %Y%m%d%H%M%S)
ssh "${PROD_HOST}" "wp db export /tmp/prod-backup-${TIMESTAMP}.sql --path='${PROD_PATH}'"
# --- ローカルDBエクスポート → 本番インポート ---
wp db export /tmp/local-db.sql --path="${LOCAL_PATH}"
cat /tmp/local-db.sql | ssh "${PROD_HOST}" "wp db import - --path='${PROD_PATH}'"
# --- URL置換 ---
ssh "${PROD_HOST}" "wp search-replace '${LOCAL_URL}' '${PROD_URL}' --all-tables --precise --recurse-objects --path='${PROD_PATH}'"
ssh "${PROD_HOST}" "wp cache flush --path='${PROD_PATH}'"
ssh "${PROD_HOST}" "wp rewrite flush --path='${PROD_PATH}'"
# --- ファイル同期 ---
rsync -az --delete
-e "ssh"
"${LOCAL_PATH}/wp-content/themes/"
"${PROD_HOST}:${PROD_PATH}/wp-content/themes/"
echo "デプロイ完了"
The above is merely a skeleton. In actual production use, you will need to add exclude rules, perform double replacement of escaped URLs, implement error handling, and more.
Simplify operations with a Makefile
To make the script even easier to use, you can wrap it in a Makefile.make push-db、make push-theme Being able to run it on a per-component basis, as shown above, makes daily deployments significantly easier.
# --- Makefile の例 ---
push-db:
@bash sync.sh local prod db
push-theme:
@bash sync.sh local prod theme
push-all:
@bash sync.sh local prod all
pull-db:
@bash sync.sh prod local db
make push-db Use `db-sync` for database synchronization,make push-theme and syncing just the theme, allowing you to naturally switch between them. If you also set up a pull-type process in the opposite direction (production → local), you can fetch production data to your local environment with a single command.
Overview of Automated Deployment with GitHub Actions
To further evolve your operations, you can automate deployments using GitHub Actions.main You can set up a system that automatically applies theme files to production whenever a push is made to a branch.
Basic Workflow
- Modify the theme locally and push to GitHub
- GitHub Actions are triggered
- Within the workflow,
rsync, use to transfer the theme files to production via SSH - Deployment complete
Register the SSH private key and host information in GitHub Secrets, and add the rsync command to the .yml file.
# .github/workflows/deploy.yml(簡易例)
name: Deploy Theme
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy via rsync
env:
SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
rsync -az --delete
-e "ssh -o StrictHostKeyChecking=no"
./themes/my-theme/
${{ secrets.PROD_USER }}@${{ secrets.PROD_HOST }}:/var/www/html/wp-content/themes/my-theme/
However, it is safer not to automate database synchronization. Overwriting a database has a wide-ranging impact, so this step should include manual verification. We recommend limiting automation in GitHub Actions to file synchronization for themes, plugins, and similar items.
Troubleshooting
Images do not display after URL replacement
The image URL may still be pointing to the local file.wp search-replace Run --dry-run to check if any items remain to be replaced.
wp search-replace 'https://mysite.test' 'https://example.com' --all-tables --dry-run --path=/var/www/html
If the number of replacements is not 0, run the process again to resolve any omissions. It is also common to forget to replace escaped URLs.
Unable to log in to the admin panel
The production password may have changed because it was overwritten with local user information. You can reset the password using WP-CLI.
ssh my-server "wp user update admin --user_pass='new-password' --path=/var/www/html"
“Permission denied” error occurs with rsync
Check that SSH key authentication is configured correctly,~/.ssh/config the hostname and port number. Also, check whether the file owner on the production server matches the user running rsync. In KUSANAGI environments, the file owner is kusanagi user, so you must connect to rsync using that user.
Permalinks are not working (404 error)
wp rewrite flush to regenerate the permalinks. If the issue persists, check the Nginx configuration to ensure the try_files is configured correctly in the Nginx configuration. For Apache, .htaccess check the RewriteRule.
Frequently Asked Questions
- How can I test without affecting the production environment?
The best approach is to set up a staging environment. If you build it on a subdomain of the same server as the production environment, you can test under the same infrastructure conditions.
wp search-replaceBy replacing the URL with a staging version, you can create a separate testing environment.- What about plugin licenses?
Many paid plugins manage licenses on a domain-by-domain basis. Even if you deploy from a local environment, it’s fine as long as the license is activated on the production domain. Please check with the plugin provider regarding the staging domain.
- Is it possible to bring production data to my local machine?
Yes, it is possible. Simply reverse the steps outlined in this article: Export the database from production → Import it to your local machine → Replace the URLs (production URL → local URL), and run rsync from production to local.
- Does this work in a multisite environment?
wp search-replaceYes, it supports multisite. However,--urlthere are additional considerations, such as the need to specify sites via options. In the case of multisite, please thoroughly test it in a test environment beforehand.
Summary
By using wp db commands and rsync, you can achieve highly reproducible deployments without relying on plugins.
- DB Migration:
wp db export→wp db import→wp search-replace - File Synchronization: Transfer differences using rsync.
--dry-runAlways verify beforehand - Safety measures: A production backup before deployment is mandatory. Using a staging environment adds an extra layer of security
- Efficiency: Automate with shell scripts → Simplify operations with a Makefile → Automate theme deployment with GitHub Actions
At first glance, it may seem like a lot of commands, but once you’ve scripted it, make push-all it can be completed with a single command like this. If you’ve been struggling with plugin limitations, please give this method a try.
If you haven’t set up a local development environment yet, check out “The Optimal Solution for a WordPress Local Development Environment” for a comparison of tools, and “Build a WordPress Local Environment in 5 Minutes with Herder and DBngin” for specific setup instructions. If you’re interested in HMR development for themes, also take a look at “Implementing HMR for WordPress Themes with Vite.”

Comment