Your WordPress or WooCommerce store's health is absolutely critical nowadays both in terms of performance and security. Having a slow site or one that has been defaced does not exactly demonstrate technical proficiency and inspire trust from your visitors. Generally I like to be proactive with performance and security rather than re-active which means we find out about a website health problem before visitors do and fix it ASAP!
In this tutorial we will go over WordPress health check automation using the Linux command line with the WP-CLI doctor command. You can also read about how to use WP-CLI's doctor command to clean the wp_options
table of unnecessary bloated autoloaded data.
Install WP-CLI Doctor Command
Installing doctor command from the github repository, note this does require composer is installed on the host.
wp package install wp-cli/doctor-command --allow-root
You should see some output like this showing the doctor command installed
Installing package wp-cli/doctor-command (dev-master)
Updating /root/.wp-cli/packages/composer.json to require the package...
Using Composer to install the package...
---
Loading composer repositories with package information
Updating dependencies
Resolving dependencies through SAT
Dependency resolution completed in 0.137 seconds
Analyzed 5360 packages to resolve dependencies
Analyzed 311678 rules to resolve dependencies
Package operations: 1 install, 0 updates, 0 removals
Installs: wp-cli/doctor-command:dev-master 9656de3
- Installing wp-cli/doctor-command (dev-master 9656de3)
Writing lock file
Generating autoload files
---
Success: Package installed.
Here is a list of all of the doctor commands
wp doctor list --allow-root
Here they are!
+----------------------------+--------------------------------------------------------------------------------+
| name | description |
+----------------------------+--------------------------------------------------------------------------------+
| autoload-options-size | Warns when autoloaded options size exceeds threshold of 900 kb. |
| constant-savequeries-falsy | Confirms expected state of the SAVEQUERIES constant. |
| constant-wp-debug-falsy | Confirms expected state of the WP_DEBUG constant. |
| core-update | Errors when new WordPress minor release is available; warns for major release. |
| core-verify-checksums | Verifies WordPress files against published checksums; errors on failure. |
| cron-count | Errors when there's an excess of 50 total cron jobs registered. |
| cron-duplicates | Errors when there's an excess of 10 duplicate cron jobs registered. |
| file-eval | Checks files on the filesystem for regex pattern `eval\(.*base64_decode\(.*`. |
| option-blog-public | Confirms the expected value of the 'blog_public' option. |
| plugin-active-count | Warns when there are greater than 80 plugins activated. |
| plugin-deactivated | Warns when greater than 40% of plugins are deactivated. |
| plugin-update | Warns when there are plugin updates available. |
| theme-update | Warns when there are theme updates available. |
| cache-flush | Detects the number of occurrences of the `wp_cache_flush()` function. |
| php-in-upload | Warns when a PHP file is present in the Uploads folder. |
| language-update | Warns when there are language updates available. |
+----------------------------+--------------------------------------------------------------------------------+
We can run all of these health checks or just do a selection of them.
Run All WP-CLI doctor Health Checks
Here is how to run just one WP-CLI doctor check
wp doctor check core-update --allow-root
Here is a sample output showing the WordPress version is current.
+-------------+---------+-------------------------------------+
| name | status | message |
+-------------+---------+-------------------------------------+
| core-update | success | WordPress is at the latest version. |
+-------------+---------+-------------------------------------+
You can run all of the WP-CLI doctor command health checks with this command
wp doctor check --all --allow-root
In this example you can see that there are some PHP warnings which are a good idea to document and fix afterwards.
Running checks 6 % [============> ] 0:00 / 0:00PHP Warning: file_get_contents(/var/www/wp-bullet.online/wp-content/db.php): failed to open stream: No such file or directory in /root/.wp-cli/packages/vendor/wp-cli/doctor-command/inc/checks/class-file-contents.php on line 66
Running checks 12 % [=========================> ] 0:01 / 0:15PHP Warning: file_get_contents(/var/www/wp-bullet.online/wp-content/db.php): failed to open stream: No such file or directory in /root/.wp-cli/packages/vendor/wp-cli/doctor-command/inc/checks/class-file-contents.php on line 66
Running checks 100% [==========================================================================================================================================================================================================] 0:12 / 0:10
Error: 4 checks report 'error'.
Warning: count(): Parameter must be an array or an object that implements Countable in /var/www/wp-bullet.online/xhprof/xhprof_lib/utils/favicon_ef3846.ico(104) : eval()'d code(165) : eval()'d code(196) : eval()'d code on line 200
Warning: array_splice() expects parameter 1 to be array, string given in /var/www/wp-bullet.online/xhprof/xhprof_lib/utils/favicon_ef3846.ico(104) : eval()'d code(165) : eval()'d code(196) : eval()'d code on line 206
Warning: Invalid argument supplied for foreach() in /var/www/wp-bullet.online/xhprof/xhprof_lib/utils/favicon_ef3846.ico(104) : eval()'d code(165) : eval()'d code(196) : eval()'d code on line 207
+----------------------------+---------+--------------------------------------------------------------------+
| name | status | message |
+----------------------------+---------+--------------------------------------------------------------------+
| core-verify-checksums | error | WordPress doesn't verify against its checksums. |
| file-eval | error | 1 'php' file failed check for 'eval\(.*base64_decode\(.*'. |
| cache-flush | warning | Use of wp_cache_flush() detected. |
| autoload-options-size | success | Autoloaded options size (164.73kb) is less than threshold (900kb). |
| constant-savequeries-falsy | success | Constant 'SAVEQUERIES' is undefined. |
| constant-wp-debug-falsy | error | Constant 'WP_DEBUG' is defined 'true' but expected to be falsy. |
| core-update | success | WordPress is at the latest version. |
| cron-count | success | Total number of cron jobs is within normal operating expectations. |
| cron-duplicates | success | All cron job counts are within normal operating expectations. |
| option-blog-public | error | Site is private but expected to be public. |
| plugin-active-count | success | Number of active plugins (2) is less than threshold (80). |
| plugin-deactivated | warning | Greater than 40 percent of plugins are deactivated. |
| plugin-update | warning | 1 plugin has an update available. |
| theme-update | warning | 1 theme has an update available. |
| php-in-upload | warning | PHP files detected in the Uploads folder. |
| language-update | success | Languages are up to date. |
+----------------------------+---------+--------------------------------------------------------------------+
Here is output from the doctor command with some warnings to address like excessive calling of wp_cache_flush()
, plugins to update and PHP files in the upload folder which be a potential security risk.
Running checks 100% [========================================================================================================================================================] 0:09 / 0:10
+----------------------------+---------+--------------------------------------------------------------------+
| name | status | message |
+----------------------------+---------+--------------------------------------------------------------------+
| core-verify-checksums | success | WordPress verifies against its checksums. |
| file-eval | success | All 'php' files passed check for 'eval\(.*base64_decode\(.*'. |
| cache-flush | warning | Use of wp_cache_flush() detected. |
| autoload-options-size | success | Autoloaded options size (91.39kb) is less than threshold (900kb). |
| constant-savequeries-falsy | success | Constant 'SAVEQUERIES' is undefined. |
| constant-wp-debug-falsy | success | Constant 'WP_DEBUG' is defined falsy. |
| core-update | success | WordPress is at the latest version. |
| cron-count | success | Total number of cron jobs is within normal operating expectations. |
| cron-duplicates | success | All cron job counts are within normal operating expectations. |
| option-blog-public | success | Site is public as expected. |
| plugin-active-count | success | Number of active plugins (28) is less than threshold (80). |
| plugin-deactivated | success | Less than 40 percent of plugins are deactivated. |
| plugin-update | warning | 2 plugins have updates available. |
| theme-update | success | Themes are up to date. |
| php-in-upload | warning | PHP files detected in the Uploads folder. |
| language-update | success | Languages are up to date. |
+----------------------------+---------+--------------------------------------------------------------------+
Here is sample output from a site that shows only wp_cache_flush()
was detected.
+----------------------------+---------+--------------------------------------------------------------------+
| name | status | message |
+----------------------------+---------+--------------------------------------------------------------------+
| core-verify-checksums | success | WordPress verifies against its checksums. |
| file-eval | success | All 'php' files passed check for 'eval\(.*base64_decode\(.*'. |
| cache-flush | warning | Use of wp_cache_flush() detected. |
| autoload-options-size | success | Autoloaded options size (274.06kb) is less than threshold (900kb). |
| constant-savequeries-falsy | success | Constant 'SAVEQUERIES' is undefined. |
| constant-wp-debug-falsy | success | Constant 'WP_DEBUG' is defined falsy. |
| core-update | success | WordPress is at the latest version. |
| cron-count | success | Total number of cron jobs is within normal operating expectations. |
| cron-duplicates | success | All cron job counts are within normal operating expectations. |
| option-blog-public | success | Site is public as expected. |
| plugin-active-count | success | Number of active plugins (52) is less than threshold (80). |
| plugin-deactivated | success | Less than 40 percent of plugins are deactivated. |
| plugin-update | success | Plugins are up to date. |
| theme-update | success | Themes are up to date. |
| php-in-upload | success | No PHP files found in the Uploads folder. |
| language-update | success | Languages are up to date. |
+----------------------------+---------+--------------------------------------------------------------------+
We can also show only the failures in the next section below
Automation Batch Processing
To only show the errors and warnings from the doctor check add the --spotlight
flag.
wp doctor check --all --spotlight --allow-root
Now we only see the errors and warnings
+-----------------------+---------+------------------------------------------------------------+
| name | status | message |
+-----------------------+---------+------------------------------------------------------------+
| cache-flush | warning | Use of wp_cache_flush() detected. |
| autoload-options-size | warning | Autoloaded options size (1.46mb) exceeds threshold (900kb) |
| cron-count | error | Total number of cron jobs exceeds expected threshold. |
| plugin-update | warning | 5 plugins have updates available. |
| php-in-upload | warning | PHP files detected in the Uploads folder. |
+-----------------------+---------+------------------------------------------------------------+
Error: 1 check reports 'error'.
Here is another sample from another site.
+-----------------------+---------+------------------------------------------------------------+
| name | status | message |
+-----------------------+---------+------------------------------------------------------------+
| cache-flush | warning | Use of wp_cache_flush() detected. |
| autoload-options-size | warning | Autoloaded options size (1.73mb) exceeds threshold (900kb) |
| option-blog-public | error | Site is private but expected to be public. |
| plugin-update | warning | 6 plugins have updates available. |
| php-in-upload | warning | PHP files detected in the Uploads folder. |
+-----------------------+---------+------------------------------------------------------------+
Error: 1 check reports 'error'.
Now we can move on to doing selective WP-CLI doctor health checks.
Run Custom List of WP-CLI doctor Health Checks
Here are some of my favorite doctor checks to run on their own.
The autoload options size check is a good one to run (see here for how to clean up the autoloaded data)
wp doctor check autoload-options-size --allow-root
You will get a pretty human readable output about the amount of autoloaded data
+-----------------------+---------+------------------------------------------------------------+
| name | status | message |
+-----------------------+---------+------------------------------------------------------------+
| autoload-options-size | warning | Autoloaded options size (1.73mb) exceeds threshold (900kb) |
+-----------------------+---------+------------------------------------------------------------+
You can read this post on how to clean the options table.
Checking if the SAVEQUERIES
constant is defined is important because having it enabled can cause performance issues.
wp doctor check constant-savequeries-falsy --allow-root
You want this undefined unless you are doing some development debugging work.
+----------------------------+---------+--------------------------------------+
| name | status | message |
+----------------------------+---------+--------------------------------------+
| constant-savequeries-falsy | success | Constant 'SAVEQUERIES' is undefined. |
+----------------------------+---------+--------------------------------------+
Having WP_DEBUG
enabled constantly is not a good idea!
wp doctor check constant-wp-debug-falsy
You want WP_DEBUG
disabled unless you are actively debugging an issue.
+-------------------------+---------+---------------------------------------+
| name | status | message |
+-------------------------+---------+---------------------------------------+
| constant-wp-debug-falsy | success | Constant 'WP_DEBUG' is defined falsy. |
+-------------------------+---------+---------------------------------------+
Check WordPress core is up to date
wp doctor check core-update --allow-root
It should be the latest version to ensure all security patches are updated.
+-------------+---------+-------------------------------------+
| name | status | message |
+-------------+---------+-------------------------------------+
| core-update | success | WordPress is at the latest version. |
+-------------+---------+-------------------------------------+
You can also check the WordPress core version with the wp core
command
wp core version --allow-root
Output
5.3.2
Check the number of scheduled tasks in wp-cron
wp doctor check cron-count --allow-root
If the nubmer is too high it will complain, this generally means a broken wp-cron which needs to be fixed.
+------------+---------+--------------------------------------------------------------------+
| name | status | message |
+------------+---------+--------------------------------------------------------------------+
| cron-count | success | Total number of cron jobs is within normal operating expectations. |
+------------+---------+--------------------------------------------------------------------+
It is a good idea to check the cron jobs in wp-cron as well
wp cron event list --allow-root
There should be no cronjobs from plugins which have been deleted or deactivated, there should also be no duplicates.
+------------------------------------+---------------------+-----------------------+------------+
| hook | next_run_gmt | next_run_relative | recurrence |
+------------------------------------+---------------------+-----------------------+------------+
| ao_ccss_queue | 2020-01-26 04:55:00 | 3 minutes 51 seconds | 10 minutes |
| wp_privacy_delete_old_export_files | 2020-01-26 05:37:50 | 46 minutes 41 seconds | 1 hour |
| prli_cleanup_visitor_locks_worker | 2020-01-26 05:48:46 | 57 minutes 37 seconds | 1 hour |
| ao_ccss_maintenance | 2020-01-26 11:29:17 | 6 hours 38 minutes | 12 hours |
| mwp_update_public_keys | 2020-01-26 12:41:40 | 7 hours 50 minutes | 1 day |
| ao_cachechecker | 2020-01-26 13:25:05 | 8 hours 33 minutes | 12 hours |
| wp_version_check | 2020-01-26 16:49:06 | 11 hours 57 minutes | 12 hours |
| wp_update_plugins | 2020-01-26 16:49:06 | 11 hours 57 minutes | 12 hours |
| wp_update_themes | 2020-01-26 16:49:06 | 11 hours 57 minutes | 12 hours |
| recovery_mode_clean_expired_keys | 2020-01-26 18:28:38 | 13 hours 37 minutes | 1 day |
| wpp_cache_event | 2020-01-27 00:00:00 | 19 hours 8 minutes | 1 day |
| wp_scheduled_auto_draft_delete | 2020-01-27 02:30:53 | 21 hours 39 minutes | 1 day |
| delete_expired_transients | 2020-01-27 03:41:10 | 22 hours 50 minutes | 1 day |
| wpseo-reindex-links | 2020-01-27 04:49:06 | 23 hours 57 minutes | 1 day |
| wp_scheduled_delete | 2020-01-27 04:49:06 | 23 hours 57 minutes | 1 day |
+------------------------------------+---------------------+-----------------------+------------+
You can check for duplicate cron jobs in WordPress too
wp doctor check cron-duplicates --allow-root
You should have no duplicate cron jobs for your site
+-----------------+---------+---------------------------------------------------------------+
| name | status | message |
+-----------------+---------+---------------------------------------------------------------+
| cron-duplicates | success | All cron job counts are within normal operating expectations. |
+-----------------+---------+---------------------------------------------------------------+
If you do get an error that there are duplicate cronjobs you can use this command to find the duplicates
wp cron event list --field=hook --allow-root | sort | uniq -d
On this particular installation the theme was adding an extra wp_update_themes
cronjob
wp_update_themes
If you are on a production site then the blog_public
option should be 1 or Google will not index the site leading to SEO issues and your site disappearing from Google.
wp doctor check option-blog-public --allow-root
You will get a notice that if the blog_public
option is set to 0
which is fine if you are on a staging site but not on production!
Error: 1 check reports 'error'.
+--------------------+--------+--------------------------------------------+
| name | status | message |
+--------------------+--------+--------------------------------------------+
| option-blog-public | error | Site is private but expected to be public. |
+--------------------+--------+--------------------------------------------+
You can make the site index-able by Google and other search engines again by changing the blog_public
option back to 1
.
wp option update blog_public 1 --skip-plugins --skip-themes --allow-root
Check the number of active plugins
wp doctor check plugin-active-count --allow-root
The threshold is 80 active plugins
+---------------------+---------+------------------------------------------------------------+
| name | status | message |
+---------------------+---------+------------------------------------------------------------+
| plugin-active-count | success | Number of active plugins (34) is less than threshold (80). |
+---------------------+---------+------------------------------------------------------------+
Deactivated WordPress and WooCommerce plugins pose a security risk so that is why this check is included.
wp doctor check plugin-deactivated --allow-root
You should always be vigilant of keeping too many deactivated plugins on a site.
+--------------------+---------+--------------------------------------------------+
| name | status | message |
+--------------------+---------+--------------------------------------------------+
| plugin-deactivated | success | Less than 40 percent of plugins are deactivated. |
+--------------------+---------+--------------------------------------------------+
Check how many plugins have updates available
wp doctor check plugin-update --allow-root
Make sure to test your updates on a staging site first 🙂
+---------------+---------+-----------------------------------+
| name | status | message |
+---------------+---------+-----------------------------------+
| plugin-update | warning | 5 plugins have updates available. |
+---------------+---------+-----------------------------------+
Check if any theme updates are available.
wp doctor check theme-update --allow-root
Outdated themes can pose a security threat. Note also that some premium themes do not have the updater mechanism working correctly so be sure to check the premium them's homepage to be certain there is not a new release available.
+--------------+---------+------------------------+
| name | status | message |
+--------------+---------+------------------------+
| theme-update | success | Themes are up to date. |
+--------------+---------+------------------------+
WordPress has internal caches it uses to speed up sites , this command checks if any plugin or theme code is flushing the cache.
wp doctor check cache-flush --allow-root
This is something to investigate further and make sure that the cache is only flushed under necessary conditions and not excessively.
+-------------+---------+-----------------------------------+
| name | status | message |
+-------------+---------+-----------------------------------+
| cache-flush | warning | Use of wp_cache_flush() detected. |
+-------------+---------+-----------------------------------+
You can check which code may be calling the cache_flush function with grep
.
grep -ril cache_flush wp-content/plugins
grep -ril cache_flush wp-content/mu-plugins
grep -ril cache_flush wp-content/themes/$(wp theme list --status=active --field=name --allow-root)
Checking for PHP files in the upload folder is important too
wp doctor check php-in-upload
You will want to make sure any PHP files in the wp-content/uploads folder are supposed to be there!
+---------------+---------+-------------------------------------------+
| name | status | message |
+---------------+---------+-------------------------------------------+
| php-in-upload | warning | PHP files detected in the Uploads folder. |
+---------------+---------+-------------------------------------------+
This find command will help find the PHP files in your wp-content/uploads
folder.
cd wp-content/uploads
find . -type f -iname "*.php"
Here is my custom doctor command list for running multiple checks at once.
The file-eval
and php-in-upload
checks can be quite time consuming if the uploads folder is huge, here is a list that tends to go much faster
wp doctor check autoload-options-size constant-savequeries-falsy constant-wp-debug-falsy core-update core-verify-checksums cron-count cron-duplicates option-blog-public plugin-active-count plugin-deactivated plugin-update theme-update cache-flush language-update
Here is the output showing the report from the above doctor command.
Error: 2 checks report 'error'.
+----------------------------+---------+--------------------------------------------------------------------+
| name | status | message |
+----------------------------+---------+--------------------------------------------------------------------+
| core-verify-checksums | error | WordPress doesn't verify against its checksums. |
| cache-flush | warning | Use of wp_cache_flush() detected. |
| autoload-options-size | warning | Autoloaded options size (1.37mb) exceeds threshold (900kb) |
| constant-savequeries-falsy | success | Constant 'SAVEQUERIES' is undefined. |
| constant-wp-debug-falsy | success | Constant 'WP_DEBUG' is defined falsy. |
| core-update | success | WordPress is at the latest version. |
| cron-count | success | Total number of cron jobs is within normal operating expectations. |
| cron-duplicates | success | All cron job counts are within normal operating expectations. |
| option-blog-public | error | Site is private but expected to be public. |
| plugin-active-count | success | Number of active plugins (51) is less than threshold (80). |
| plugin-deactivated | success | Less than 40 percent of plugins are deactivated. |
| plugin-update | warning | 4 plugins have updates available. |
| theme-update | warning | 1 theme has an update available. |
| language-update | success | Languages are up to date. |
+----------------------------+---------+--------------------------------------------------------------------+
Automation with Notifications
You can also create your own custom doctor check automation scripts with YAML (these are from the github page).
Here is a sample doctor.yml file specifying to check the W3 Total Cache plugin is installed
plugin-w3-total-cache:
check: Plugin_Status
options:
name: w3-total-cache
status: uninstalled
You can now specify the custom doctor.yml
file using the --config
parameter:
wp doctor check --fields=name,status --all --config=doctor.yml --allow-root
If your custom doctor check fails you will get an error
+-----------------------+--------+
| name | status |
+-----------------------+--------+
| plugin-w3-total-cache | error |
+-----------------------+--------+
You can do something similar for finding out if somebody turned the blog_public option off and get alerted via email.
Sources
Customize Doctor Config
wp cron event command
Find duplicate entries in text file