Command Line Tips to Improve WordPress Performance

Written: 11.2.2020 Updated: 11.2.2020

There are plenty of WordPress speed optimization articles online, but most of them recommend installing various plugins. Installing more plugins might, unfortunately, make things worse as the plugins themselves slow down WordPress. The plugins might also require learning about a lot of options and settings, and the plugins need to be updated and maintained.

Luckily a lot can also be done from the command-line (e.g. over an SSH connection). Working in an SSH session has many benefits: it is fast to execute, the results stay visible in the terminal screen for easy review of what happened, and the commands can be copy-pasted and scripted to save time for those who work with many sites.

Measure. Make a change. Validate.

Before doing any performance optimizations, it is important to measure the baseline. After doing changes to the site one must measure again and validate that the changes were indeed an improvement. Blindly following some guides and making changes to your site just because somebody said so might put the site worse off, since there are actually changes that on some sites speed them up, but on other sites slows them down. Knowing how to measure the baseline and remembering to validate the improvements is absolutely principal to actually gaining results.

Our favorite command-line tool for this is cURL. As an example in cURL you can initiate an HTTP request, view the response headers from the server and output the time it took with this command:

$ curl -I -w "%{time_total}\n" https://seravo.com/
HTTP/2 200 
server: nginx
content-type: text/html; charset=UTF-8
vary: Accept-Encoding
referrer-policy: strict-origin-when-cross-origin
x-xss-protection: 1; mode=block
x-powered-by: Seravo

0.081511

Note that the $ sign is not part of the command, it just represents the command-line prompt. The curl argument -I instructs it to show the HTTP response headers, which among others show that the server responded with HTTP/2 200, which basically means everything was OK. The -w argument tells curl to output the total time of the request and response, telling how fast the server is and how much latency there is between the machine that executed the command and the server. In this example, the response was loaded in 81 milliseconds.

Some times the result varies, so it makes sense to do the measurement a couple of times. Also, if the server has HTTP caching (for example a front proxy), one can send the Pragma: no-cache header in the request to tell the server what caches should be bypassed and that a response is wanted all the way from the PHP running WordPress. This is the same header a browser sends to the server when you press Ctrl+F5 or Cmd+R to reload a page.

Combining these into a bash for loop that runs 5 iterations looks like this:

$ for i in {1..5}; do LC_NUMERIC=C curl -IL -w "%{time_total}\n" -H Pragma:no-cache -o /dev/null -s "https://seravo.com"; done
0.247952
0.300009
0.265494
0.257871
0.258129

With 5 measurements in a row, you can see what kind of variation there is. The extra LC_NUMERIC=C ensures time format uses dot instead of a comma. The -L tells curl to follow redirects, if there are any, and -s and -o /dev/null are used to suppress all other output so that only the total load time is printed nicely in one simple list.

Curl is something anybody can run anywhere. Customers at Seravo have in addition access to a custom command wp-speed-test that basically does fancier version of the curl loop above. The command can also take as an argument --cache if one specifically wants to measure the speed of a page loaded from cache, as the default is to test WordPress speed uncached. Optionally a URL can also be given as an argument. If omitted, the command will just test the WordPress front page.

Example output:

$ wp-speed-test https://seravo.com/features/
Testing speed URL https://seravo.com/features/...

For an explanation of the different times, please see docs at https://curl.haxx.se/docs/manpage.html

URL                             TOTAL NAMELOOKUP CONNECT APPCONNECT PRETRANSFER STARTTRANSFER    = AVG
https://seravo.com/features/    0.111      0.004   0.005      0.013       0.013         0.111    0.111
https://seravo.com/features/    0.078      0.000   0.000      0.000       0.000         0.078    0.095
https://seravo.com/features/    0.101      0.000   0.000      0.000       0.000         0.101    0.097
https://seravo.com/features/    0.079      0.000   0.000      0.000       0.000         0.079    0.092
https://seravo.com/features/    0.084      0.000   0.000      0.000       0.000         0.084    0.091
https://seravo.com/features/    0.121      0.000   0.000      0.000       0.000         0.121    0.096
https://seravo.com/features/    0.113      0.000   0.000      0.000       0.000         0.113    0.098
https://seravo.com/features/    0.076      0.000   0.000      0.000       0.000         0.076    0.095
https://seravo.com/features/    0.084      0.000   0.000      0.000       0.000         0.084    0.094
https://seravo.com/features/    0.158      0.000   0.000      0.000       0.000         0.158    0.101
https://seravo.com/features/    0.083      0.000   0.000      0.000       0.000         0.083    0.099
https://seravo.com/features/    0.112      0.000   0.000      0.000       0.000         0.112    0.100
https://seravo.com/features/    0.076      0.000   0.000      0.000       0.000         0.075    0.098
https://seravo.com/features/    0.095      0.000   0.000      0.000       0.000         0.095    0.098
https://seravo.com/features/    0.072      0.000   0.000      0.000       0.000         0.072    0.096
https://seravo.com/features/    0.120      0.000   0.000      0.000       0.000         0.119    0.098
https://seravo.com/features/    0.091      0.000   0.000      0.000       0.000         0.091    0.097
https://seravo.com/features/    0.105      0.000   0.000      0.000       0.000         0.105    0.098
https://seravo.com/features/    0.131      0.000   0.000      0.000       0.000         0.131    0.100
https://seravo.com/features/    0.135      0.000   0.000      0.000       0.000         0.135    0.101


Test completed. If the values seems too high, please profile your PHP code to find potential bottle necks.
Note that this test tells how fast your site is in the sense of how long it takes for PHP to generate the HTML output. To test how much load the site can handle, run wp-load-test.

Quickly find out which plugin slows down the site

In addition to cURL, another tool we use all the time is WP-CLI which allows you to control the WordPress installation from the command line. As an example, you could install the Native Lazyload plugin by Google and activate it in one go by running wp plugin install --activate native-lazyload. It is extremely handy, so we recommend learning to use it. Command-line help can be viewed with wp --help. Note that while cURL can be used from anywhere to test any site, since it uses external HTTP requests, the wp command needs to be run from the server itself where WordPress lives since it needs direct file and database access to the site.

Combining cURL and WP-CLI allows us to neatly deactivate each plugin one by one, and measuring what effect it had on the general WordPress load time. The following one-liner makes five measurements, disables a plugin, makes the measurements again, and then re-enables the plugin. This is repeated for each plugin. If there is a very slow plugin, you would see a major difference in the load speed.

for p in $(wp plugin list --fields=name --status=active)
do
  echo $p
  wp plugin deactivate $p
  for i in {1..5}
  do
    curl -o /dev/null -w "%{time_total}\n" \
    -H "Pragma: no-cache" -s http://localhost/
  done
  wp plugin activate $p 
done

Below is a screenshot of how it looks like when there is one plugin that is very slow, and the page load times drop significantly when that plugin is deactivated.

Example output from for-loop that combines wp-cli and curl to find out which plugin makes the WordPress site slow.

Using the WP-CLI profile package

In addition the built-in commands in WP-CLI, the tool can also be extended by installing so-called packages. One example is the profile-command package. It offers a quick way to analyze what stage or hook is slow on the site. This alone does not point out what plugin or theme code is responsible, but it can be very valuable to a developer who can dig deeper into the code once the stage or hook is known. To use it, it needs to be first installed with wp package install wp-cli/profile-command.

Below are examples of the profiling commands and what they might output.

$ wp profile stage --all --spotlight
+--------------------------+----------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| hook                     | callback_count | time    | query_time | query_count | cache_ratio | cache_hits | cache_misses | request_time | request_count |
+--------------------------+----------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| muplugins_loaded:before  |                | 0.2045s | 0s         | 0           | 91.67%      | 11         | 1            | 0s           | 0             |
| plugins_loaded:before    |                | 0.858s  | 0.0015s    | 2           | 100%        | 231        | 0            | 0s           | 0             |
| plugins_loaded           | 62             | 0.3323s | 0s         | 0           | 100%        | 192        | 0            | 0s           | 0             |
| after_setup_theme:before |                | 0.0769s | 0s         | 0           | 100%        | 438        | 0            | 0s           | 0             |
| after_setup_theme        | 19             | 0.0857s | 0s         | 0           | 96.72%      | 59         | 2            | 0s           | 0             |
| init                     | 183            | 0.4485s | 0.0007s    | 1           | 99.52%      | 1453       | 7            | 0s           | 0             |
| wp_loaded                | 31             | 0.0772s | 0.0249s    | 6           | 99.17%      | 240        | 2            | 0s           | 0             |
| parse_request:before     |                | 0.6226s | 0s         | 0           | 100%        | 2392       | 0            | 0s           | 0             |
| template_redirect        | 39             | 0.0142s | 0s         | 0           | 98.61%      | 142        | 2            | 0s           | 0             |
| wp_head                  | 41             | 0.108s  | 0.0009s    | 1           | 99.26%      | 945        | 7            | 0s           | 0             |
| loop_start:before        |                | 1.3029s | 0.0312s    | 44          | 95%         | 15292      | 804          | 0s           | 0             |
| loop_end:before          |                | 0.1547s | 0s         | 0           | 96.68%      | 1396       | 48           | 0s           | 0             |
| wp_footer:before         |                | 0.2821s | 0.0062s    | 4           | 97.33%      | 5176       | 142          | 0s           | 0             |
| wp_footer                | 26             | 0.0074s | 0.0008s    | 1           | 100%        | 83         | 0            | 0s           | 0             |
+--------------------------+----------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| total (14)               | 401            | 4.5751s | 0.0661s    | 59          | 98.14%      | 28050      | 1015         | 0s           | 0             |
+--------------------------+----------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+

$ wp profile hook --all --spotlight
+-------------------------------------------------+-------------------------------------------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| callback                                        | location                                        | time    | query_time | query_count | cache_ratio | cache_hits | cache_misses | request_time | request_count |
+-------------------------------------------------+-------------------------------------------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| SimpleHistory->filter_gettext()                 | simple-history/inc/SimpleHistory.php:796        | 0.011s  | 0s         | 0           |             | 0          | 0            | 0s           | 0             |
| GFForms::loaded()                               | gravityforms/gravityforms.php:239               | 0.01s   | 0s         | 0           |             | 0          | 0            | 0s           | 0             |
| wpseo_init()                                    | wordpress-seo/wp-seo-main.php:293               | 0.0159s | 0s         | 0           | 100%        | 54         | 0            | 0s           | 0             |
| SkyVerge\WooCommerce\COG\Utilities\Previous_Ord | woocommerce-cost-of-goods/vendor/skyverge/wc-pl | 0.011s  | 0s         | 0           |             | 0          | 0            | 0s           | 0             |
| SimpleHistory->load_loggers()                   | simple-history/inc/SimpleHistory.php:979        | 0.0162s | 0s         | 0           | 100%        | 4          | 0            | 0s           | 0             |
| WooCommerce->init()                             | woocommerce/includes/class-woocommerce.php:533  | 0.1144s | 0s         | 0           | 100%        | 657        | 0            | 0s           | 0             |
| wp_widgets_init()                               | wp-includes/widgets.php:1601                    | 0.03s   | 0s         | 0           | 100%        | 180        | 0            | 0s           | 0             |
| ACF->init()                                     | advanced-custom-fields-pro/acf.php:218          | 0.0396s | 0s         | 0           |             | 0          | 0            | 0s           | 0             |
| WC_Post_Types::register_taxonomies()            | woocommerce/includes/class-wc-post-types.php:36 | 0.0196s | 0s         | 0           | 100%        | 61         | 0            | 0s           | 0             |
| wpDiscuzForm->initPersonalDataExporter()        | wpdiscuz/forms/wpDiscuzForm.php:63              | 0.0037s | 0.0006s    | 1           | 100%        | 10         | 0            | 0s           | 0             |
| WP_Rewrite->flush_rules()                       | wp-includes/class-wp-rewrite.php:1744           | 0.0835s | 0.042s     | 4           | 99.54%      | 217        | 1            | 0s           | 0             |
| WC_API->rest_api_includes()                     | woocommerce/includes/class-wc-api.php:126       | 0.0493s | 0s         | 0           |             | 0          | 0            | 0s           | 0             |
| WC_API->register_rest_routes()                  | woocommerce/includes/class-wc-api.php:251       | 0.2017s | 0s         | 0           | 100%        | 324        | 0            | 0s           | 0             |
| create_initial_rest_routes()                    | wp-includes/rest-api.php:178                    | 0.0506s | 0s         | 0           | 100%        | 249        | 0            | 0s           | 0             |
| wp_enqueue_scripts()                            | wp-includes/script-loader.php:1441              | 0.0216s | 0s         | 0           | 100%        | 216        | 0            | 0s           | 0             |
| WPSEO_Frontend->head()                          | wordpress-seo/frontend/class-frontend.php:653   | 0.0436s | 0.0013s    | 1           | 98.99%      | 391        | 4            | 0s           | 0             |
| wp_resource_hints()                             | wp-includes/general-template.php:2893           | 0.0008s | 0s         | 0           | 50%         | 1          | 1            | 0s           | 0             |
| wp_print_head_scripts()                         | wp-includes/script-loader.php:1395              | 0.006s  | 0s         | 0           | 97.7%       | 85         | 2            | 0s           | 0             |
| wptexturize()                                   | wp-includes/formatting.php:51                   | 0.0217s | 0s         | 0           |             | 0          | 0            | 0s           | 0             |
| Sensei_Quiz::single_quiz_title()                | sensei-lms/includes/class-sensei-quiz.php:1124  | 0.0255s | 0s         | 0           | 100%        | 1          | 0            | 0s           | 0             |
| Groups_Post_Access::wp_get_nav_menu_items()     | groups/lib/access/class-groups-post-access.php: | 0.0105s | 0s         | 0           | 100%        | 118        | 0            | 0s           | 0             |
| wp_trim_excerpt()                               | wp-includes/formatting.php:3311                 | 0.0586s | 0s         | 0           | 99.17%      | 120        | 1            | 0s           | 0             |
| OMAPI_Output->load_optinmonster()               | optinmonster/OMAPI/Output.php:311               | 0.0034s | 0.0008s    | 1           | 100%        | 43         | 0            | 0s           | 0             |
+-------------------------------------------------+-------------------------------------------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+
| total (66)                                      |                                                 | 2.0908s | 0.079s     | 51          | 81.77%      | 5319       | 418          | 0s           | 0             |
+-------------------------------------------------+-------------------------------------------------+---------+------------+-------------+-------------+------------+--------------+--------------+---------------+

Check that HTTP front cache works

In curl the -I option can be used to view the server response headers as shown in the example earlier. One possible use for this is to enable a developer to see if there are any Cache-control or Expires headers or other similar ones emitted from the server that prevents the content from being cached.

The WordPress environment at Seravo contains an extra handy command to help to identify the cache status for sites specifically at Seravo:

$ wp-check-http-cache 
----------------------------------------
Seravo HTTP cache checker
----------------------------------------
Testing https://seravo.com/...
Request 1: MISS
Request 2: HIT
Request 3: HIT
----------------------------------------
SUCCESS: HTTP cache works for https://seravo.com/.
----------------------------------------
You can also test this yourself by running:
  curl -IL https://seravo.com/

In this example the caching works as it should. The first request is a MISS but after that, the requests HIT the cache.

On some sites, this command might warn that there is a PHPSESSION cookies set. A site should not set cookies to all visitors, because that prevents the cache from working. If you come across this issue, you can find out what code includes calls to the function session_start() that sets this cookie. To search for a string in the whole WordPress installation one can use the command-line tool grep, which is pre-installed in pretty much all Linux systems and other Unix-based systems. For sites at Seravo, one can also use our handy little helper wp-find-code. It only needs the search string as an argument and takes automatically care of searching in the correct paths. For example:

$ wp-find-code session_start
wp-content/plugins/mailpoet/mailpoet_initializer.php:    session_start();

Accessing the database from the command-line

The easiest way to access the database of a WordPress site is using wp db cli (assuming WP-CLI is installed). From there you can run any SQL commands that you like (and know!). Recalling SQL commands is sometimes hard, and therefore at Seravo we also offer a couple of helpers to view the database tables, their sizes, analyze the rows and contents a bit, and optimize the database.

All of these commands at Seravo can be found by running wp-db- and then pressing the tab key a couple of times:

$ wp-db-
wp-db-cleanup    wp-db-dump      wp-db-info     wp-db-load
wp-db-optimize   wp-db-size      wp-db-update

Yes, but how to access SSH in the first place?

If you would like to use these command-line tips, but you haven’t used SSH at all before, we recommend checking out our SSH tutorial on Youtube to get started.

Leave a comment

Otto Kekäläinen

CEO

Search Seravo.com

Categories

More reading

Spring Clean WordPress to Make It Secure and Faster

18.2.2020

Over time, even well-maintained WordPress sites tend to accumulate some cruft: excess files, unused plugins, unnecessary database contents, and logs […]

How HTTP/3 Will Make the Internet Faster

31.1.2020

When HTTP/2 was released in 2015 as an official IETF standard it was a major improvement over the old HTTP/1.1 […]

Technical SEO for WordPress

30.1.2020

For a lot of people, Search Engine Optimization simply means installing one of the popular SEO plugins in WordPress, while […]