VPS Tutorials
Slow WordPress on a VPS? (2026): PHP-FPM, OPcache, Redis, Nginx Cache, and Slow Query Optimization
A slow WordPress site on a VPS doesn't necessarily mean the machine is too weak. Common bottlenecks are usually PHP-FPM process counts, OPcache not enabled, Redis Object Cache not wired up properly, missing Nginx/FastCGI cache, heavy plugins, MySQL slow queries, and image/static asset handling. This article follows a real troubleshooting order: first quantify where it's slow, then optimize PHP, caching, the database, plugins, and front-end assets layer by layer, avoiding blind VPS upgrades.

When a WordPress site is slow, many people's first reaction is: the VPS specs aren't enough, should I upgrade from 1C1G to 2C4G?
Sometimes an upgrade really is needed, but more often it's not that the CPU is too weak — it's that PHP-FPM, OPcache, Redis, Nginx cache, MySQL, plugins, and images aren't configured well. A 2C2G VPS with a properly configured WordPress should be able to run a personal blog, a small business site, or a low-traffic WooCommerce store without "click the admin panel and wait ten seconds."
This article doesn't cover how to install WordPress. For installation, see:
This article solves only one problem: WordPress is already running, but it's slow on the VPS — in what order should you troubleshoot and optimize?
Don't start by installing a caching plugin. First figure out which part is slow.
There are four common kinds of slowness:
- Slow time to first byte (high TTFB): the page starts responding slowly — usually a PHP, database, or caching issue;
- Slow admin: wp-admin opens slowly — usually plugins, the database, external requests, or insufficient PHP processes;
- Slow front-end assets: HTML is fast, but images, CSS, and JS drag it down;
- Sudden slowness at peak times: normal most of the time, but CPU, memory, and MySQL spike as soon as traffic arrives.
First test time to first byte locally:
curl -o /dev/null -s -w 'time_namelookup: %{time_namelookup}\ntime_connect: %{time_connect}\ntime_starttransfer: %{time_starttransfer}\ntime_total: %{time_total}\n' https://example.com/
If time_starttransfer is high — say 1 second, 2 seconds or more — focus on PHP, the database, and caching.
Then check server resources:
top
free -h
df -h
iostat -xz 1
If CPU is stuck at 100% for a long time, see:
If memory is frequently exhausted, see:
Locate first, then optimize. Otherwise it's easy to end up with "five caching plugins installed and the site is even slower."
WordPress fundamentally runs PHP for a large share of requests. Nginx is fast, but if the PHP-FPM process pool is too small, the site will still be slow.
First find the PHP-FPM config file location. Depending on the system it may be:
/etc/php/8.2/fpm/pool.d/www.conf
/etc/php/8.3/fpm/pool.d/www.conf
/etc/php/8.4/fpm/pool.d/www.conf
The key parameters are:
pm = dynamic
pm.max_children = 8
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 500
pm.max_children is not better the larger it is. Each PHP-FPM child process consumes memory. First check roughly how much a single process uses:
ps -ylC php-fpm8.2 --sort:rss
Or:
ps aux | grep 'php-fpm' | grep -v grep
Rough estimate:
可给 PHP 的内存 / 单个 PHP-FPM 进程平均内存 = max_children
For example, on a 2GB VPS, after reserving some for the system, Nginx, MySQL, and Redis, maybe only 700MB is left for PHP. If one PHP process averages 80MB, setting pm.max_children to 8-10 is about right. Forcing it to 50 will only push the machine into swap.
After changing, check and reload:
php-fpm8.2 -t
sudo systemctl reload php8.2-fpm
If you've hit 502/504 before, you can also read:
OPcache is one of the most underrated items in WordPress performance optimization. It caches PHP's compiled bytecode so PHP files don't have to be re-parsed on every request.
Check whether it's enabled:
php -i | grep -i opcache.enable
You can also create a temporary PHP file to check phpinfo(), but delete it immediately after viewing — don't leave it exposed long-term.
Common config files:
/etc/php/8.2/fpm/conf.d/10-opcache.ini
A small WordPress site can start like this:
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.save_comments=1
A few notes:
memory_consumption=128is enough for most small and medium sites;- with many plugins and a heavy theme, you can raise it to 192 or 256;
max_accelerated_filesshouldn't be too small — WordPress has many plugin files;validate_timestamps=1is safer for ordinary sites, so old code isn't easily cached after plugin updates.
After changing, restart PHP-FPM:
sudo systemctl restart php8.2-fpm
Without OPcache, many caching plugins can't fix a slow admin either.
WordPress page caching makes the front end fast, but the admin, logged-in users, shopping carts, and dynamic pages still query the database frequently. That's where Redis Object Cache earns its keep.
Scenarios suited to Redis:
- The admin is slow;
- Many plugins;
- WooCommerce;
- Many posts and categories;
- Many database queries;
- MySQL CPU or I/O is often high.
Install Redis:
sudo apt update
sudo apt install redis-server
sudo systemctl enable --now redis-server
Check:
redis-cli ping
Returns:
PONG
On the WordPress side, you can install the Redis Object Cache plugin, then add to wp-config.php:
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE_KEY_SALT', 'example.com:');
If multiple WordPress sites share one Redis, WP_CACHE_KEY_SALT must be different, otherwise cache keys may pollute each other.
Don't expose Redis directly to the public internet. A previous article explained why database-type services shouldn't be opened to the public internet:
Check Redis listening:
ss -lntp | grep 6379
It should only listen on 127.0.0.1:6379 or an internal address — never run naked on the public internet.
If your site is mainly a blog, business site, or documentation site, and page content is basically the same for non-logged-in users, then Nginx FastCGI Cache is very effective.
Its job: cache the HTML generated by PHP at the Nginx layer. The next time an anonymous user visits the same page, Nginx returns it directly without going through PHP and MySQL.
Basic idea:
fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=WORDPRESS:100m inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
In the PHP location:
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-FastCGI-Cache $upstream_cache_status always;
Then set which requests should not be cached:
set $skip_cache 0;
if ($request_method = POST) {
set $skip_cache 1;
}
if ($query_string != "") {
set $skip_cache 1;
}
if ($request_uri ~* "/wp-admin/|/wp-login.php|/cart|/checkout|/my-account") {
set $skip_cache 1;
}
if ($http_cookie ~* "wordpress_logged_in_|comment_author|woocommerce_items_in_cart|wp_woocommerce_session") {
set $skip_cache 1;
}
Note: WooCommerce, membership sites, forums, and learning systems cannot be blindly cached site-wide. Shopping carts, checkout, and user centers must skip the cache.
Test whether the cache hits:
curl -I https://example.com/
Look at the response headers:
X-FastCGI-Cache: HIT
If it's always MISS or BYPASS, the skip rules, cookies, or cache path have a problem.
The WordPress plugin ecosystem is powerful, but it can easily mess up performance.
Common mistakes:
- Installing multiple page caching plugins at once;
- Plugin caching and Nginx FastCGI Cache caching the same layer at the same time;
- Enabling Redis Object Cache and also installing another object caching plugin;
- A caching plugin auto-generating complex
.htaccess, but you're using Nginx; - A page optimization plugin merging CSS/JS until it breaks.
Suggested principles:
- Page caching: Nginx FastCGI Cache or one mature caching plugin — pick one;
- Object caching: one Redis Object Cache is enough;
- Static asset optimization: image compression, lazy loading, critical CSS — don't over-merge;
- CDN: Cloudflare handles static assets and edge caching — don't let it fight with origin rules.
If you've already set up Cloudflare, you can also read:
A slow WordPress is often not slow PHP but a database dragged down by plugins.
Enable the MySQL slow query log:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = 'ON';
Check the slow query log location:
SHOW VARIABLES LIKE 'slow_query_log_file';
You can also persist it in the config file:
[mysqld]
slow_query_log=1
long_query_time=1
log_queries_not_using_indexes=0
Don't leave log_queries_not_using_indexes=1 on long-term — it can make logs balloon. Turn it on during troubleshooting and off after locating the issue.
Common sources of WordPress slow queries:
- Plugins stuffing large amounts of autoload data into
wp_options; - Page builders generating too many meta queries;
- Too much historical data in WooCommerce order tables;
- Search plugins, statistics plugins, and related-posts plugins doing large-range scans;
wp_postmetaqueries without suitable indexes;- The database and PHP competing for resources on the same small-memory VPS.
Check autoload size:
SELECT SUM(LENGTH(option_value)) / 1024 / 1024 AS autoload_mb
FROM wp_options
WHERE autoload = 'yes';
If autoload data is tens of MB, a slow admin is normal.
Check the largest autoload items:
SELECT option_name, LENGTH(option_value) AS size
FROM wp_options
WHERE autoload = 'yes'
ORDER BY size DESC
LIMIT 20;
Don't delete just because you see a large field. First confirm which plugin it comes from, back up, then handle it.
The biggest risk in MySQL tuning on a small VPS is copying 16GB/32GB server configs.
For a 1C2G or 2C4G WordPress site, focus on:
[mysqld]
innodb_buffer_pool_size=512M
innodb_log_file_size=128M
max_connections=50
table_open_cache=2000
tmp_table_size=64M
max_heap_table_size=64M
This is only a starting point, not a universal template.
innodb_buffer_pool_size is the core parameter. Too small, and the database reads from disk frequently; too large, and PHP and the system have no memory. On a single-machine WordPress setup, usually don't let MySQL eat until the system has no headroom.
If it's 1C1G:
- Don't be greedy with the MySQL buffer pool;
- PHP-FPM max_children should be conservative;
- Redis can be added, but limit its memory;
- Add ZRAM/Swap if necessary;
- Keep the number of plugins restrained.
If it's 2C4G:
- You can start MySQL at around 1G;
- PHP-FPM child processes can be more generous;
- Redis 128-256MB is enough for many sites;
- Once FastCGI Cache hits, pressure drops noticeably.
A slow WordPress is often not "WordPress is slow" but "some plugin is slow."
Common high-risk plugin types:
- Page builders;
- Statistics/analytics plugins;
- Related-posts plugins;
- Site search plugins;
- Security scanning plugins;
- Backup plugins;
- WooCommerce add-on plugins;
- Plugins that make external API requests.
Troubleshooting method:
- Back up first;
- Deactivate suspicious plugins one by one during off-peak hours;
- Watch TTFB, admin load time, and the slow query log;
- Use Query Monitor to inspect slow queries and hooks;
- If it can be solved at the server layer, don't rely entirely on plugins.
If speed recovers noticeably after deactivating a plugin, the bottleneck isn't the VPS but the application layer.
When WordPress has many images, local VPS disk, bandwidth, and PHP all come under pressure.
Suggestions:
- Compress images before uploading;
- Use WebP/AVIF;
- Enable lazy loading;
- Don't generate too many thumbnail sizes;
- Serve static assets via CDN;
- Consider object storage for large amounts of media files.
When to consider object storage, see:
For a personal blog, Cloudflare + local disk is usually enough. For image sites, download sites, and asset sites, keeping large numbers of files on the VPS system disk long-term makes expansion, backup, and migration troublesome later.
If your WordPress is already slow, do it in this order:
- Use
curlto measure TTFB and confirm whether it's backend-slow or static-asset-slow; - Use
top/free/iostatto check CPU, memory, and I/O; - Check whether PHP-FPM
pm.max_childrenis too small or too large; - Confirm OPcache is enabled;
- Install Redis Object Cache;
- Add Nginx FastCGI Cache or a mature caching plugin for anonymous pages;
- Enable the MySQL slow query log to find plugin and query issues;
- Clean up oversized autoload options;
- Reduce heavy plugins, slow plugins, and duplicate caching plugins;
- Image compression, CDN, and object storage if necessary;
- If resources are still consistently maxed out, then upgrade the VPS.
The benefit of this order: fix the cheapest, most common, and most certain problems first. Don't upgrade the machine right away, and don't install ten plugins right away.
| VPS spec | Suitable sites | Optimization focus |
|---|---|---|
| 1C1G | Personal blogs, low-traffic showcase sites | OPcache, page caching, few plugins, ZRAM |
| 1C2G | Ordinary blogs, small business sites | Redis, FastCGI Cache, conservative PHP-FPM |
| 2C4G | Multi-plugin WordPress, small WooCommerce | MySQL buffer pool, Redis, slow query optimization |
| 4C8G+ | WooCommerce, membership sites, content sites | Database indexes, object storage, CDN, monitoring and alerts |
If you're still choosing a machine, see:
Not necessarily. If OPcache is off, PHP-FPM is misconfigured, plugins cause slow queries, or caching is missing, switching machines only alleviates it, not cures it. First troubleshoot TTFB, PHP, the database, and caching, confirm resources really are insufficient long-term, then upgrade.
Redis Object Cache caches WordPress objects and database query results, mainly helping the admin, dynamic pages, and complex plugins. Page caching caches the final HTML, mainly helping anonymous users visiting front-end pages. They can be used together, but don't install multiple plugins of the same kind that fight each other.
Pure blogs, business sites, and documentation sites are better suited to Nginx FastCGI Cache — good performance, bypasses PHP. Ordinary users unfamiliar with Nginx are safer with a mature caching plugin. WooCommerce and membership sites need to be very careful with cache exclusion rules.
Yes, but with restraint. Few plugins, a light theme, OPcache enabled, page caching, image optimization, and ZRAM if necessary. Don't force heavy page builders, WooCommerce, a pile of statistics plugins, and local backup tasks onto 1C1G.
Check plugins, Redis Object Cache, PHP-FPM child processes, and MySQL slow queries first. Page caching usually can't help the admin, because admin requests can't be cached as static HTML.
A slow WordPress on a VPS usually isn't solved by one switch — it's the combined effect of PHP, caching, the database, plugins, and static assets.
The most effective route: first use TTFB and server metrics to confirm the bottleneck, then check PHP-FPM and OPcache, add Redis Object Cache, set up FastCGI Cache for anonymous pages, and finally use the slow query log and plugin troubleshooting to find what's really dragging the site down.
After all that, if CPU, memory, and disk I/O are still consistently maxed out, then upgrade the VPS. That way the money you spend solves a real bottleneck instead of paying for a misconfiguration.