If you are a website owner or administrator, one of many things you do is keep this website safe and protected. To achieve it, you need all the tools at your disposal to help you be productive and ensure you’re doing it right. A command-line interface for WordPress, WP-CLI, is one of those tools. If you think that’s too advanced for you and could never understand it, buckle up because this article is for you. Hopefully, you will feel confident enough to try some commands by the end of it.
No installation needed
Installation of the WP-CLI tool is done via terminal and can be intimidating, especially because it varies depending on the operating system and server. We will look at ready-to-use options. Many hosting providers offer WP-CLI out of the box. Furthermore, many of them have their own custom commands to help you do some tasks specific to their servers. All of them have instructions on how to find and use WP-CLI on their platform. Search for WP-CLI in their knowledge base, documentation, blog, FAQs, etc.
It’s worth noting here that when you have WP-CLI available for your hosting, this means you have SSH access to your website. You don’t need to know anything about it, besides that, it is a very secure connection. In time, after you get more comfortable with the terminal, you might want to learn how to do more than just WP-CLI. But it is completely fine if this never happens. This is the beauty of CLI tools – you can use it at as basic or as advanced a level as you want, without ever breaking anything.
Agenda
Now that the heavy part of installing WP-CLI is completed, let’s see what we will cover. The first step in ensuring your website’s security is keeping WordPress core, plugins, and themes up to date. You also want to make sure these updates won’t break your website.
Another important way to remove stress is to prevent hacking. Occasionally, this cannot be done, but there are many things you can do in this area, such as monitoring file changes. Also, with WP-CLI, you have a powerful user management tool in your pocket.
Additional backups can be yet another provider of a good night’s sleep.
It looks like we have our agenda
Table of Contents
Anatomy of a command
Before we even start running commands, here are some quick basics that can make it easier to memorise commands. A WP-CLI command, almost always, has the following structure:
wp <noun> <verb> --flag-1 --flag-2
Noun and verb rarely switch order, but we won’t be looking at those commands now. Nouns are commands that represent entities you want to work with, such as core
, plugin
, theme
, post
, user
etc. Verbs are a deeper level of subcommands which describe what you intend to do with this entity. You’ll find the same verbs across many entities, such as list
, create
, delete
, add
, remove
etc. Flags are additional arguments to make your request more specific.
Get help
At any level of the command structure (main, noun, or verb), you can use a global parameter named --help
, which will reveal all subcommands and flags you can use for the current command or subcommand.
For example, if you want to see which subcommands (verbs) you can use with the plugin
command, you can type:
wp plugin --help
If you want to know all the flags you can use with plugin update subcommand, you’ll type:
wp plugin update --help
To get out of the help screen, just type q
and press Enter
.
Are you in your WordPress’ root?
All the commands you’ll see in this article, are to be run inside the WordPress root folder. The WordPress root folder can be identified by the presence of following directories: /wp-admin, /wp-includes, and /wp-content.
When you log in to your hosting terminal, it is highly likely you won’t be in the WordPress root. This differs depending on the provider, but the most common scenario is that you are at least one level above.
To see in which directory you are, use the ls
command.
In some cases, you’ll see public_html
, in other www
, maybe even the directory named the same as your username on the server. Your WordPress root is in one of those. To see what’s inside those directories, without going there, use the same command, followed by the name of the directory:
ls public_html/
Now that we have located WordPress root, let’s go there. Use the cd
command:
cd public_html/
You can run ls
command once again, just to be sure that you are, indeed, in WordPress root. This is the place we will stay in until the end of the article. Make yourself at home.
Secure updates
To make sure your WordPress core is secure, you want to keep it up to date with secure updates. Those updates are also called “minor.”
There are two types of updates to the WordPress core software that become available for your website:
– Source: Learn.WordPress.org, Managing Updates
- Major core updates that include new WordPress features. You can tell an update is considered a “major” release because the number will follow the pattern: 4.0, 4.1, 4.2, 4.3, 4.4, etc.
- Minor core updates often include maintenance releases, security fixes, and updates to translation files. You can tell an update is considered a “minor” release because the number identifying it will be appended to the number for the current major release. For example: 4.4.1, 4.4.2, 4.4.3, etc.
For updating to a major release, you want to consult your developer. This has to be tested in a local environment to make sure everything works as expected with new features.
Minor updates, however, you can perform yourself. First, check which version you have:
wp core version
This will give you the number of major and minor versions, currently installed for your website, e.g. 6.5.4
.
Then we want to check if there is any update available:
wp core check-update --minor
Omitting the --minor
flag will tell WP-CLI to look for major releases. If your WordPress site is up-to-date with minor releases, you’ll see the following message:
Success: WordPress is at the latest minor release.
If, however, you see a table with version
, update_type
, and package_url
, then there is an update available, and you should update your website sooner rather than later:
Minor release updates should never break anything on your website—these are only bug fixes. Regardless, you should check your website to make sure everything works as expected. If something is broken, there are two things you should do:
- Revert update with:
wp core update --version=6.5.4 --force
(this is why you checked your version first). You have to use the--force
flag every time you’re updating to the version that is not the latest, regardless of whether it’s a minor or major release. - Check with your developer for the possibility to replace the plugin or theme which caused the break. If they don’t work with minor releases, then the code inside is most likely not following best practices.
And that’s it. You ran your first WP-CLI commands and did something useful. Most importantly, your website is still alive. Congratulations!
Similar principles can be applied to updating themes and plugins. Both have a --dry-run
flag to allow you to just check which plugins and themes have available updates, without actually updating them:
wp plugin update --all --dry-run
To check the minor version, the --minor
flag is also available:
wp theme update --all --minor --dry-run
If there are no updates available, you’ll see a No theme/plugin updates available
message. If there are, you’ll see a table with all the plugins or themes that have available updates, their current and available versions, and the status (active or inactive).
To update only some plugins or themes, you can specify them by using the name from the above table:
wp plugin update akismet --minor
If you, for whatever reason, don’t want to run these updates in production, you can always test them first on a staging website. All serious hosting providers will provide you with a staging version of your website for these kinds of tests.
Monitoring file changes
Often, when your website is hacked, it will have some kind of changes in files. It can be in the form of a new file anywhere in your file system, or a change in existing files. You can use WP-CLI to monitor these changes.
Checking changes to your files is done with the verify-checksums subcommand:
wp core verify-checksums
This will check your WordPress core files against known WordPress versions. If you want to check a specific version, you can specify it:
wp core verify-checksums --version=6.5.2
If the check fails, you’ll see this message:
Error: WordPress installation doesn't verify against checksums.
Before this message, you’ll also see the list of files that didn’t verify against checksum.
This might mean that your WordPress install is corrupted, but it can also be that you mistyped the version. To always be certain you typed the correct version, you can use:
wp core verify-checksums --version=$(wp core version)
As you can see, inside the command substitution, $()
, we are executing the command to get the version number. So you don’t have to even know which version you have because different CLI tools work perfectly together, and you can provide values by nesting commands. Just wrap them inside $()
.
If all is good, you’ll see the following message:
Success: WordPress installation verifies against checksums.
It is possible to get this success message while malicious changes still happen. For example, there might be a new file in your installation even though no core files are modified. If this new file is added in /wp-includes
or /wp-admin
folders you will see a warning about the file that shouldn’t exist there:
Warning: File should not exist: wp-includes/install-wp.php
To include the root folder of your WordPress into this check, add the --include-root
flag.
wp core verify-checksums --include-root --version=$(wp core version)
Now you are left with wp-content
as the only unchecked directory. In this directory, you should only have an index.php
file and folders (or single PHP files) named as plugins you see in your WordPress dashboard. To check them all, you can run the same verify-checksums subcommand. If you want to also check small changes (such as in the readme.txt file), add the --strict
flag.
wp plugin verify-checksums --all --strict
For any plugin that’s not hosted at WordPress.org you will get following warning:
Warning: Couldn't fetch response from https://downloads.wordpress.org/plugin-checksums/<plugin>/<version>.json (HTTP code 404).
Warning: Could not retrieve the checksums for version <version> of plugin <plugin>, skipping.
Again, this doesn’t necessarily mean that you have malicious code in your WordPress install. It only means that this plugin is not hosted at WordPress.org. It could be a custom plugin your developer created specifically for your website, or it can be a premium version of a plugin that you bought at some marketplace.
If you are not sure how to deal with all of these warnings you see, you should copy and paste the output from the terminal into another document and send it to your developer for further investigation. This will decrease debugging time drastically, or prove to be a false alarm. Both of which are more desired than their alternatives.
User management
Sporadically, when hackers breach the website, they will change the users’ access. They can completely remove one or more users (mainly administrators) or just limit the access. In this situation, it is highly likely they breached through the WordPress dashboard by hacking the password or creating a new user by executing files in your WordPress install.
But you have SSH access. And that makes you the most powerful person on your server. So first you want to check all users with administrator access:
wp user list --role=administrator
You will get a table with user IDs, usernames, display names, emails, roles, and registration dates. The most recent registered users should be suspicious if the usernames and emails don’t ring any bells.
Keep in mind that you CAN see this list and perform all the actions even if your user doesn’t exist anymore. You don’t need access to the WordPress dashboard to be able to make any changes via WP-CLI.
From this point, you can silently revoke access to a suspicious user with following command (you need their username, email address, or user ID):
wp user remove-role <user_name|user_email|ID>
When a user has no role, WordPress doesn’t know what to do with them, so they have no access whatsoever. Even subscribers have access to their profile in the dashboard, while roleless users have none.
If you are not sure which user is malicious, you can remove roles of all administrators by combining other CLI tools:
for i in $(wp user list --role=administrator --field=ID); do wp user remove-role $i; done
You might recognize command substitution here – $()
. We are using it to get user IDs for every administrator. Instead of ID
, you can use user_name
or user_email
. As there might be more than one, we need to execute the above command to remove the role for each user. That’s why we are running our IDs through a for
loop where i
represents the value for the user. Now we want to run wp user remove-role
for each of the users, where the individual value is represented with $i
. When we loop through all, we are done
.
After running it, you will see a message that informs you about the success or possible failure:
Success: Removed <user_login> (<ID>) from <URL>.
Now you can create your own new user if needed but, since we don’t know how suspicious users were created, you want to run checks for file changes.
When you are not sure which, if any, flags are mandatory for the subcommand, and you don’t want to go back and forth to help or documentation, a great help is the --prompt
global parameter. It will prompt you with all the available flags (mandatory ones are inside <>
tags), so you only have to type what’s unique for your use-case. This will make your commanding much easier and more intuitive:
wp user create --prompt
After this, you want to delete the history of your commands because you don’t want that password to be saved there. To do this, run:
history -c && history -w
Or you can omit the password here and use the “Forgotten password” feature on a WordPress login screen to set one.
Backups
As with staging websites, any serious hosting provider has some kind of backup service. However, some hostings don’t provide you with a download option but rather offer only restoring possibility.
The only things you really want to have a backup for are the wp-content
folder and database.
First, let’s create backup folder:
mkdir backup
For databases, you can specify the file name or go with the WP-CLI default structure: <database_name>-<date>-<random_hash>.sql
. The random hash is used to make sure no one can guess the name of a database file on your server but in full honesty, you should never keep any database backups on your server so you can name it as you wish, we are going to delete it soon anyway:
wp db export backup/database.sql
Now we want to prepare the wp-content
folder by compressing it to zip and moving to new backup directory:
zip -rv backup/wp-content.zip ./wp-content -i "wp-content/themes/*" "wp-content/plugins/*" "wp-content/uploads/*"
Here, I’m adding only the /themes
, /plugins
, and /uploads
folders to the zip file because I want to avoid unnecessary folders, such as /upgrades
and all the folders various plugins create for their needs.
Breakdown of the zipping command:
zip
– obviously, use the zip command-rv
– go inside every folder (recurse-paths
) and tell me what you are doing (verbose
messages)backup/wp-content.zip
– save the archive in this path (and name itwp-content.zip
)./wp-content
– use/wp-content
as a source for the archive-i
– include only the following files"wp-content/themes/*"
– everything you find inside /wp-content/themes/ directory (and repeat that for/wp-content/plugins
and/wp-content/uploads
)
Now I can zip the whole backup folder to be able to download it easily:
zip -rv backup.zip ./backup/
This is how your root folder looks now:
.
├── backup
├── wp-admin
├── wp-content
├── wp-includes
├── backup.zip
├── index.php
├── license.txt
├── readme.html
├── wp-activate.php
├── wp-blog-header.php
├── wp-comments-post.php
├── wp-config.php
├── wp-config-sample.php
├── wp-cron.php
├── wp-links-opml.php
├── wp-load.php
├── wp-login.php
├── wp-mail.php
├── wp-settings.php
├── wp-signup.php
├── wp-trackback.php
└── xmlrpc.php
There are two ways to download backup.zip file:
- With
curl
command from your local terminal, - by navigating to the URL where your backup.zip file is.
I’m going to keep this simple and assume you never opened the terminal on your local machine (but you do have one) so we’ll go with the second option.
If you run these commands inside your WordPress root, then you can easily download the zip file by navigating to https://<your-website.com>/backup.zip
. The download will start automatically and, when finished, you should delete both backups from your server, zip, and directory:
rm -rf backup*
Now you have all the necessary backups on your machine, and you did it in less than 5 minutes, with no traces on your website 🤫
While this way of creating and downloading your backups is possible, it is not recommended to expose those files in such a way, especially NOT as a regular backup solution. For regular backups consult your developer and hosting provider.
It is recommended to create the backup folder one level above the WordPress root and use scp
command from your local terminal to download it.
Maintenance mode
There is a great explanation of how to use WP-CLI for maintenance mode in official documentation. I suggest you go over there and get additional information about it.
Want to explore more?
If you feel confident enough for more advanced checks and actions, I recommend looking into:
None of these commands come with WP-CLI out of the box, so you have to install them as separate packages.
A bonus here is a list of plugins that have their own custom commands, as well as some useful tips on how to combine WP-CLI with other CLI tools for more magic performances.
Conclusion
If you’ve read this far, I hope you came to the following conclusions:
- WP-CLI is an extremely powerful tool for managing WordPress websites, and one you want to utilize when fast actions are needed, but also to prevent these necessities.
- You can use WP-CLI as long as you have SSH access to your website. Nothing ever needs to be broken, you always have help available.
- What you’ve seen in this article is just the tip of the iceberg. So many things are possible, and you can explore them all inside the nearest terminal.
Props to @bph and @greenshady for peer review, and @westonruter for recommendation.
Leave a Reply