All WordPress HTML Templates Forms & Webhooks AI & Tools
WordPress

WP-CLI for People Who Avoid the Terminal

A practical wp-cli guide for developers who avoid the terminal: the 12 commands that matter, search-replace gotchas with Elementor JSON, aliases and real

A real working moment illustrating the theme of an article about wp-cli. Wide 16:9 banner, one strong focal point, magazine editorial quality, authentic and unstaged.
Key Takeaways

  • Browser-based updates run inside a single PHP request with a timeout; wp-cli runs with no HTTP timeout and exits with a status code you can act on. That difference alone justifies learning it.
  • Twelve commands cover roughly 90 percent of real maintenance work: core update, plugin update, db export, search-replace, transient delete, post delete, option list, cron event list, user update, cache flush, rewrite flush, media regenerate.
  • wp search-replace handles serialized PHP correctly, but it does not handle JSON with escaped slashes, which is how Elementor and several builders store URLs. You need a second pass with https:\/\/.
  • --dry-run exists on the destructive commands. Use it every time, and run wp db export first anyway. The export takes four seconds.
  • File ownership is the most common self-inflicted wound: installing plugins as the wrong SSH user leaves files the web server can’t update, which surfaces weeks later as a mysterious failed auto-update.

Why the admin screen is the riskier interface

Every action in wp-admin is an HTTP request bounded by maxexecutiontime, PHP’s memory_limit, and whatever your host’s reverse proxy decides is too long. Bulk plugin updates, large imports, regenerating 8,000 thumbnails: all of them are a bet that the request finishes before something kills it.

WP-CLI runs under the CLI SAPI, where maxexecutiontime defaults to 0. Unlimited. It also gives you three things the browser cannot:

  • Output you can read and keep. Pipe it to a file. Paste it into the ticket.
  • Exit codes. wp plugin update --all && wp cache flush only flushes if the update actually worked.
  • Repeatability. A command is a thing you can send to a colleague. “Click the blue button, then scroll down” is not.

The trade-off is real: wp-cli gives you no confirmation dialogs. wp db reset --yes will empty the database of whatever site your current directory points at, immediately, with no undo. The browser’s friction is sometimes doing you a favour.

Getting a shell without becoming a sysadmin

Most decent managed hosts ship wp-cli preinstalled with SSH access on every plan: Kinsta, WP Engine, Cloudways, SiteGround on GrowBig and up, Rocket.net. Log in over SSH, cd to the WordPress root, type wp --info. If you get a version banner, you’re done. Skip ahead.

On a VPS or anywhere it’s missing, the install is three lines and has not changed in years:

curl -O https://raw.githubusercontent.com/wp-cli/wp-cli/v2.12.0/utils/wp-completion.bash
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar && sudo mv wp-cli.phar /usr/local/bin/wp

Run wp cli version afterwards. Anything from 2.9 upward behaves identically for everything in this article. For local work, Local by Flywheel and DDEV both bundle it (ddev wp plugin list works out of the box). On Laravel Herd or a plain Homebrew PHP, brew install wp-cli is fine.

One critical detail: which user you SSH as matters. If you log in as root or as your personal account and then run wp plugin install, the extracted files are owned by that user. The web server, usually www-data or nginx, cannot write to them, and WordPress’s own auto-updates silently fail from then on. Check with ls -l wp-content/plugins and fix it:

# match ownership to whatever the rest of wp-content already uses
sudo -u www-data wp plugin install wordpress-seo --activate

The twelve wp cli commands that do the real work

Here’s the actual working set. Not the full command reference, which has hundreds of subcommands you will never type.

wp db export backup-$(date +%F-%H%M).sql   # always first
wp core update && wp core update-db
wp plugin update --all --dry-run           # see what would change
wp plugin update --all
wp plugin list --status=active --field=name
wp theme activate twentytwentyfive
wp user update admin --user_pass='...'     # locked out of a client site
wp cache flush                             # object cache, not page cache
wp rewrite flush                           # fixes most 404-on-every-page issues
wp transient delete --expired
wp media regenerate --only-missing --yes
wp option get siteurl

Two of those deserve a note. wp cache flush clears the object cache, so if the site runs Redis or Memcached this is how you clear it. It does nothing to your page cache or Cloudflare. And wp rewrite flush fixes the single most common post-migration symptom: the homepage loads, every other URL 404s.

The three flags worth memorising

  • --path=/srv/www/site/public so you can run commands from anywhere.
  • --skip-plugins --skip-themes bootstraps WordPress without loading any of them. This is how you recover a site that’s fataling: wp --skip-plugins plugin deactivate woocommerce works even when the plugin itself is the thing crashing PHP.
  • --url=https://shop.example.com for multisite, where almost every command needs to know which site you mean.

search-replace: the command that pays for the learning curve

Migrating a site from staging to production used to be a 20-minute job with a plugin, a serialization-aware find and replace, and a prayer. Now it’s one command with a dry run first.

wp search-replace 'https://staging.example.com' 'https://example.com' \
  --all-tables-with-prefix \
  --report-changed-only \
  --dry-run

--all-tables-with-prefix catches tables that plugins created and that wp db export knows about but the default (core tables only) skips. --report-changed-only cuts the output from 60 rows of zeros to the handful that matter. Drop --dry-run when the report looks right.

The thing the listicles don’t tell you: search-replace unserializes PHP arrays safely, but it does not touch JSON with escaped forward slashes. Elementor, Bricks and several other builders store post content as JSON, where your URL lives in the database as https:\/\/staging.example.com. The first pass misses all of it, your staging URLs stay in the page, and the client finds them before you do. Second pass:

wp search-replace 'https:\/\/staging.example.com' 'https:\/\/example.com' \
  --all-tables-with-prefix --precise

--precise forces the slower PHP-based replacement instead of letting MySQL do it, which matters on mixed-encoding columns. On a 2 GB database expect this to take minutes rather than seconds. Worth it. If you’re deciding which builder to standardise on, this kind of storage format difference is part of the real trade-off between Elementor, Bricks and Gutenberg.

Maintenance work that’s genuinely faster this way

Database bloat is where the command line stops being a preference and becomes the only sane tool. Revisions, expired transients and autoloaded options accumulate quietly until TTFB doubles.

# total weight of autoloaded options, in bytes
wp option list --autoload=on --format=total_bytes

the worst offenders

wp option list --autoload=on --fields=optionname,sizebytes \ --orderby=size_bytes --order=desc | head -20

nuke post revisions (check the count first with --format=count)

wp post delete $(wp post list --post_type=revision --format=ids) --force

expired transients only; --all also clears valid ones

wp transient delete --expired

Anything over roughly 800 KB of autoloaded options is loading on every single request, including admin-ajax calls. We’ve seen 6 MB on sites where an abandoned plugin wrote a cache into wp_options with autoload = yes. The full diagnosis process is in our piece on WordPress database bloat.

Scheduled tasks are the other one. wp cron event list shows you what’s queued and whether it’s overdue, which is impossible to see from wp-admin without a plugin:

wp cron event list --fields=hook,nextrunrelative,recurrence
wp cron event run --due-now

If every hook shows a nextrunrelative in the past, WP-Cron isn’t firing. That usually means low traffic plus the default loopback trigger. The fix is a real system cron calling wp cron event run --due-now, and we wrote up why WordPress cron is not cron in detail.

Aliases: the part that makes it stick

This is the feature that converted the terminal-avoiders on our team. Create wp-cli.yml in your project root:

cat > wp-cli.yml <<'YAML'
@prod:
  ssh: [email protected]/srv/www/example/public
@staging:
  ssh: [email protected]/srv/www/staging/public
@local:
  path: /Users/you/Sites/example
YAML

Now you never SSH manually again:

wp @prod plugin list --status=active
wp @prod db export - | wp @local db import -   # pull prod down in one pipe
wp @all core version                           # run against every alias

That db export - | db import - pipe replaces an entire category of migration plugins. It streams, so a 4 GB database doesn’t need 4 GB of local disk for the dump file. Follow it with a search-replace and you have a two-command refresh of your local environment. On client retainers we keep these in the repo so every developer gets the same aliases.

Where this goes wrong, and when not to bother

Four failure modes we’ve actually hit:

  • Wrong site. You SSH in, forget to cd, and wp-cli walks up the tree to find a different wp-config.php. Run wp option get siteurl before anything destructive. It takes one second and has saved us twice.
  • Memory on big operations. wp media regenerate on 15,000 images will exhaust a 128 MB CLI limit. Override it per-command: php -d memory_limit=1G "$(which wp)" media regenerate --yes.
  • Page cache and CDN. wp-cli knows nothing about your Cloudflare zone or your host’s full-page cache. After a bulk change the site can look unchanged for 20 minutes. Clear those separately.
  • Long-running commands and dropped SSH. Anything over a couple of minutes should run under tmux or with nohup ... &. Your laptop lid closing will kill a half-finished import.

The honest limit: if you manage one brochure site on cheap shared hosting with no SSH access, learning wp-cli has a poor return. Use your host’s tooling. The payoff scales with the number of sites you touch and the frequency of the work. Past about five sites, or any site where a broken update costs money, it stops being optional.

Frequently Asked Questions

Can I use wp-cli without SSH access?

Not really, and the plugin-based workarounds are a bad idea because they run commands back inside a timed PHP request, which defeats the main benefit. Your options are to upgrade to a host with SSH, or run wp-cli locally against a copy of the site and push the database up. Most hosts above the entry tier include SSH now; it’s worth asking before you migrate away.

Is wp-cli safe to run on a live production site?

Yes, with two habits: run wp db export first, and use --dry-run on search-replace and plugin update. The commands themselves are no more dangerous than the equivalent admin screens, and they fail more visibly. The genuinely destructive ones (db reset, db drop, site empty) all require --yes, so you cannot trigger them by accident.

Why does wp-cli say “Error establishing a database connection” when the site works fine?

Almost always because wp-config.php defines DBHOST as something only the web server can reach, or because you’re in the wrong directory and wp-cli found a different config. Check with wp config get DBHOST --path=. and confirm your working directory. On some managed hosts the database socket is only readable by the web user, so prefix the command with sudo -u www-data.

Do wp cli commands work with multisite?

They do, but most commands need --url= to know which site in the network you mean, otherwise they apply to the main site. Network-wide commands have their own forms: wp site list, wp plugin activate x --network, and wp db export dumps the whole network’s tables in one file. Test on a staging copy first, because a careless search-replace across a 200 site network is a long rollback.

What’s the fastest way to recover a site showing a white screen?

Run wp --skip-plugins --skip-themes plugin list to get a working bootstrap, then deactivate suspects one at a time with the same flags. If a theme is the culprit, wp --skip-themes theme activate twentytwentyfive gets the front end back. This works when wp-admin itself is fataling, which is precisely when the browser gives you nothing.

Where to start tomorrow

Pick one site, SSH in, and run three commands: wp option get siteurl, wp db export backup.sql, wp plugin update --all --dry-run. Nothing changes, nothing breaks, and you’ll have confirmed your shell works and your backup path is writable.

Then on the next migration, use search-replace instead of a plugin. Time it. On a mid-sized site the plugin route takes 15 to 20 minutes of clicking and waiting. The two-pass command version is usually under two. After that, set up the aliases file. Once wp @prod plugin list is in your muscle memory, you won’t go back to the admin screen for maintenance work again.

fix wordpress white screen with wp-cli wordpress command line migration wp cli commands list wp cli skip-plugins recovery wp search-replace serialized data wp-cli wp-cli aliases wp-cli.yml wp-cli for beginners