This section will cover the basics on configuring services for performance with WordPress.
WordPress has a lot of dynamic functionality, but this comes at a cost. Tasks such as processing PHP, querying the database and collecting information from external APIs all take resources and time.
Caching saves time for potentially heavy tasks by reusing previously computed results, rather than calculating them for every page view.
Caches typically expire after a certain amount of time and are regenerated so the most recent content is displayed. When items are served from cache they have a faster response time, often coming from memory, and take load off the server.
In a typical page load, various caches might be checked in the following order:
- Local Browser cache / Local Storage / Web App Manifest
- Content Delivery Network (CDN)
- Full Page Cache (Reverse Proxy; RAM or SSD with Nginx+)
- Full Page Cache (Static; PHP)
- Opcode Cache
- Object Cache (wp_options, transient API)
- Fragment Cache (Database, static files, transient API)
- Transients API (Object Cache or Database Cache)
- Database Cache
- File Storage Cache (Static Files, SSD or spinning disk)
Each caching instance may be a different domain, server, or compute instance, either routed by the local machine, the remote server cluster, or any intermediary computer along the request chain.
Each cache may have any configuration of: Data storage: RAM, SSD, or spinning hard drives. Physical connection, or data over network. Input/Output latency: Connection from local or remote server motherboard to RAM, Data I/O, or network I/O.
For any given page load, speed and user experience will result from the combined latency of all services, and the order they are processed as users interact with a web application.
Content Distribution Network (CDN) Cache
Content distribution networks are designed to optimize the network latency between servers and visitors from different geographical locations. Data is distributed amongst endpoints and then visitors get served from the endpoint which is closest to them.
In addition to optimizing networking latency, CDNs can act as another layer of static and/or full-page caching running on all those endpoints.
It’s important to make sure that CDNs are working well with all your other caching systems and that it purges caches on all endpoints when you request that from your main server. Otherwise, people in certain areas may get old results which is generally an issue that’s difficult to troubleshoot.
Full Page Cache
In order to display your content, WordPress does a lot of work under the hood, and all those calculations require server resources and time to complete. For starters, the PHP service on the server has to process the request, load WordPress core, your theme PHP files and all PHP scripts coming from your plugins. The majority of those PHP files make requests to your database, too, which adds to the overall resource footprint of your site.
The best way to cache these requests is to use a reverse proxy like NGINX or Varnish which stores the output directly into the server memory. That saves a lot of processing power because cached content is served straight out of the reverse proxy without hitting your web server, the PHP service, or your database service at all. If a reverse proxy is not available on your current server setup, you can fallback to storing cached content into your file system. It's slower than reverse proxies and a hit reaches your web server and your PHP service at least once, so it can direct the request to the proper cached file but still - it's much faster than doing all the computing for every request.
Full Page Caching stores the HTML output of a request, but all the CSS, JS, images and font files will have to be loaded separately too. They are handled separately and optimizing them is worth investing the time and effort. Static caching can have great effect on those resources. You can often use the same reverse proxy to static resources in the server memory - CSS, JS, Fonts, Images and serve them directly.
It's important to have the ability to expire caches when necessary to avoid serving visitors old data. When available, selective caching is preferred over purging the entire cache, to avoid the cost of WordPress regenerating every page for the site. Furthermore, it's good practice to exclude certain types of pages from your full page caching completely because they are different for each user. For example, if you have an online store, it's imperative that your cart, checkout and profile pages are completely dynamic. In general, it’s a good idea to exclude all logged in users from the cache because they are supposed to see personalized content. Another important aspect is the default caching period, which can be different for each website depending on how often data is changed.
In 2005, WordPress introduced its internal object cache — a way of automatically storing any data from the database (not just objects) in PHP memory to prevent unnecessary queries. However, out of the box, WordPress will discard all of those objects at the end of the request, requiring them to be rebuilt from scratch for the next page load.
What does this mean? Think of a standard WordPress homepage displaying the most recent posts. Each of these posts has quite a bit of information associated with it WordPress must look up such as the author, categories, tags, and excerpt.
Support for a persistent object cache gives WordPress, plugins, and themes, a place to store that data for reuse. While these items are cached, PHP execution time is improved while lessening the load on the database. It's particularly helpful in situations where much of the page is difficult to cache from the front-end, like for authenticated traffic or e-commerce applications.
For these reasons, persistent object caching support is commonly offered with managed WordPress hosting.
Transients are inherently sped up by caching plugins, where normal Options are not. A memcached plugin, for example, would make WordPress store transient values in fast memory instead of in the database. For this reason, transients should be used to store any data that is expected to expire, or which can expire at any time. Transients should also never be assumed to be in the database, since they may not be stored there at all.
The web server must read, compile, and run each PHP script. An opcode cache stores a compiled copy of each PHP script in memory or on disk. When the web server starts processing PHP scripts for WordPress, the web server checks the opcode cache for a cached copy of the PHP script. If there is a cached copy, the web server can skip straight to running the PHP script using the cached copy instead of having to read and compile the script again. Skipping this reading and compiling PHP scripts can greatly improve the web server's resource usage and enable WordPress to serve many more requests than it might have been able to otherwise. It's particularly helpul for dynamic content and authenticated traffic, where full page caching isn't as effective.
As with any cache, opcode caches can keep changes from taking effect until the cache expires or is purged. With opcode cache specifically, this means older versions of the compiled PHP code will be loaded. When updating plugins, themes, or WordPress core, the appropriate files should be purged from the cache to avoid continuing to load the older versions.
This caching method allows saving sections of otherwise non-cacheable dynamic website content. It can help especially for sites where the majority of the page is static, but has certain dynamic elements, like a shopping cart, or for membership sites.
In the WordPress context, developers often store parts of the page using the WordPress transients/object cache API. In these cases, providing a persistent object cache will allow that caching to happen outside of the database.
Storing these fragments separately in a front end cache is not natively supported by WordPress, and means both manually configuring the sections of the page to be cached, and configuring your front-end cache, whether it be Nginx, Varnish, or otherwise, to support fragment caching. This is usually an advanced technique, and reserved for sites or hosting platforms with very high dynamic traffic needs.
Purging / Busting / Clearing Caches
Purging caches is as important as storing them. You have to make sure that all layers of caching are cleared when necessary.
Fragment caching is the temporary storage of expensive or long-running server-side operations to avoid taxing web servers and delayed delivery to visitors. It's become a common practice for operations such as generating Menu markup, Widget markup and slow MySQL or HTTP responses. Core currently uses transients to cache HTTP calls to WordPress.org APIs for updates and events.
Fragment caching is particularly beneficial when appropriately paired with full-page caching. Perhaps there's uniform
<footer> markup displayed on every page that can be temporarily stored. When the server needs to rebuild static cache files and a fragment is found, it saves the server from running Menu/Widget queries to generate the footer markup on every page.
Significant caution should be exercised blanket caching Core resources. If a site Menu relies on dynamic
.current-menu-item classes, storing the menu markup in a fragment will "burn" that class in, no longer highlighting the correct page as a user navigates. Any caching of WordPress Core resources should be opt-in and integrate an appropriate flushing mechanism for when users modify the resource.
The Transients API should always be used for fragment caching instead of directly using
wp_cache_* functions. In environments without a persistent Object Cache,
set_transient() will store cache values in the database in the
wp_options table. However, when Object Cache is enabled,
set_transient() will wrap
As PHP is an interpreted language, its version and configuration has a large impact on how well and whether WordPress will run.
When possible, PHP 7.x should be used to run WordPress. As of the writing of this document, PHP 7 is the only major version of PHP still receiving active development and support. The PHP group regularly retires support for older versions of PHP, and older versions are not guaranteed to be updated for security concerns. At the same time, newer versions of PHP contain both security and performance improvements, while being accompanied by new features and bug fixes, which are not guaranteed to be backwards compatible. However, extreme care must be taken when upgrading the version of PHP. While WordPress is compatible with the latest releases of PHP, sites built to use older versions of PHP may not be compatible due to their included plugins and themes.
If upgrading to PHP 7 is not immediately possible, upgrading to PHP 5.6 should be done as soon as possible. PHP 5.6 is the oldest version of PHP 5 that is still receiving security patches. Older versions of PHP are not having their bugs and vulnerabilities patched and should be considered insecure and avoided.
More information about the support versions of PHP can always be found on PHP's supported versions page.
When upgrading PHP, it's a good practice to test sites for compatibility before upgrading. If you offer multiple environments, such as a staging and a production environment, PHP version should be configurable separately for each environments. This will allow users to test newer version of PHP in their non-production environment and resolve any issues before upgrading PHP version in the production environment.
There's a useful WP-CLI command for performing a general compatibility check, but be aware that it is not 100% accurate.
PHP is primarily configured using a configuration file,
php.ini, from which PHP reads all of its settings and configuration at runtime. This usually happens through CGI/FastCGI, or a process manager like PHP-FPM.
You can see detailed information about each of these directives in the official PHP documentation.
There are several timeout settings on a system that limit different aspects of a request. When configuring your timeouts, it's important to select values that work well together. For example, it doesn't make sense to have a very high script execution timeout on your PHP service, if the Apache timeout is lower than that - in such case, if the request takes longer, it will be killed by the web server no matter your PHP timeout setting is.
Note that processes take different amount of time, depending on the server load, and those limitations are placed to ensure that your server functions properly. If you have high server load, processes may take longer to complete thus causing a cascade effect leading to even more server load. That's why it's a matter of balance between giving enough time for your scripts to be compiled and ensuring that you're within normal server loads.
The maximum time allowed for data transfer from the web server to PHP is specified with the
php.ini directive. It is usually used to limit the amount of time allowed to upload files. It's important to note that the amount of time is separate from
max_execution_time, and defines the amount of time between when the web server calls PHP and execution starts.
The maximum amount of memory that PHP is allowed to use per page render is specified with the
In addition to setting memory limits within PHP, WordPress has two memory configuration constants that can be changed in the wp-config.php file. WordPress will raise the PHP
memory_limit to these values if it has permission to do so, but if the
php.ini specifies higher amounts, WordPress will not lower the amount allowed.
This option declares the amount of memory WordPress should request for rendering the front end of the website.
define( 'WP_MAX_MEMORY_LIMIT', '256M' );
Since the WordPress backend usually requires more memory, there's a separate setting for the amount, that can be set for logged in users. This is mainly required for image uploads. You can have it set higher than the front end limit to ensure your backend has all the resources it needs.
File Upload Sizes
When uploading media files and other content to WordPress using the WordPress admin dashboard, WordPress uses PHP to process the uploads. PHP's configuration includes limits on the size of files that can be uploaded through PHP and on the size of requests that can be sent to the web server for processing. These will need to align with the server's timeouts, discussed above.
The limit on the size of individual file uploads can be configured using the
The limit on the entire size of a request that can be sent from the web server to PHP for processing can be configured using the
php.ini directive. The value for
post_max_size must be greater than or equal to the value for
upload_max_filesize. PHP will not process requests larger in size than the value for
post_max_size applies to every PHP request and not only uploads, so it may become important to address separately if a site processes a large amount of other data included with the request.
Replacing WordPress' Cron Triggers
wp-cron.php script is responsible for causing certain tasks to be scheduled and executed automatically. Every time someone visits your website,
wp-cron.php checks whether it is time to execute a job or not. Even though these checks are small and fast they consume time and produce load. For this reason, it's worth considering setting the
DISABLE_WP_CRON constant and using an alternative method to trigger WordPress' cron system.