WordPress's reputation for being slow is well-deserved when it's badly hosted, but a properly tuned WordPress site can serve hundreds of thousands of pageviews per day from a €7-15/month VPS. The difference is configuration, not budget. This post walks through the optimization layers, in order of impact.
The hosting baseline
Before tuning, you need the right foundation. WordPress on shared hosting will be slow no matter what you do — you're sharing CPU and disk I/O with hundreds of other sites. Move to a VPS. For most WordPress sites:
- Up to 10K monthly pageviews: 1 vCPU / 1 GB RAM (Basic tier)
- 10K-100K monthly: 2 vCPU / 2 GB (Advanced)
- 100K-1M monthly: 2 vCPU / 4 GB (Professional)
- 1M+: scale horizontally with caching CDN
The stack: LEMP, not LAMP
Apache works, but Nginx + PHP-FPM (LEMP) consistently outperforms Apache + mod_php for WordPress. The architectural reason: Nginx is event-driven (single thread handles thousands of concurrent connections), while Apache traditionally spawns processes per connection. For static asset delivery and reverse proxying, Nginx is 3-5× faster at equivalent RAM.
PHP-FPM tuning
The default PHP-FPM config on most distros is too conservative. For a 2 GB VPS dedicated to WordPress, edit /etc/php/8.3/fpm/pool.d/www.conf:
pm = dynamic
pm.max_children = 25
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.max_requests = 500
Each child uses about 30-50 MB of RAM. max_children × per-child RAM should fit comfortably in available memory, leaving headroom for MySQL and Nginx. max_requests = 500 recycles workers periodically to prevent memory leaks from accumulating.
OPcache
OPcache caches compiled PHP bytecode in memory, eliminating the recompile-on-every-request cost. It's bundled with PHP but often misconfigured. In /etc/php/8.3/fpm/php.ini:
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
Setting validate_timestamps=0 means OPcache won't check whether files changed — fastest, but you must manually flush the cache after deploys. For development, set to 1 with revalidate_freq=2.
Object caching with Redis
WordPress makes hundreds of database queries per page load. Most are cacheable. Install Redis:
sudo apt install redis-server php8.3-redis
Install the Redis Object Cache plugin (free, in WP repo). Configure WordPress to use it via wp-config.php:
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE_KEY_SALT', 'yoursite.com');
Object caching alone typically reduces page generation time by 40-70%. It's the single biggest optimization for dynamic WordPress pages (logged-in users, e-commerce checkout) that bypass page caching.
Page caching
For anonymous visitors (the vast majority of traffic on most sites), generate the HTML once and serve it from disk on subsequent requests. Two good options:
- FastCGI cache (Nginx-level): highest performance, requires Nginx config changes. Bypasses PHP entirely for cached pages.
- WP Super Cache or W3 Total Cache: plugin-based, easier to configure, slightly less efficient.
For most sites, the plugin approach is fine. For traffic spikes, FastCGI cache lets you serve thousands of req/sec from a small VPS.
Database tuning
MariaDB (or MySQL) defaults are conservative. For a 2 GB VPS, edit /etc/mysql/mariadb.conf.d/50-server.cnf:
innodb_buffer_pool_size = 512M
innodb_log_file_size = 64M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
query_cache_type = 0
InnoDB buffer pool at 25% of available RAM caches hot tables in memory. flush_log_at_trx_commit=2 trades a 1-second write durability window for substantial write performance — acceptable for most WordPress workloads. Disable query cache (deprecated, hurts performance under concurrency).
CDN
Static assets (images, CSS, JS) should be served from a CDN, not your VPS. Cloudflare Free is the default choice — point your DNS through Cloudflare, enable "Cache Everything" with appropriate cache rules, and your VPS only sees uncached requests. Bunny.net is a good alternative with simpler pricing.
For sovereignty-sensitive deployments, KeyCDN and Bunny are EU-headquartered. Cloudflare is US-headquartered, which may matter for certain regulated workloads.
Image optimization
Images are usually the largest payload on a WordPress page. Use the WebP format with JPEG fallback. Plugins like ShortPixel or Imagify generate WebP automatically and serve them via the <picture> element. Result: 30-60% reduction in image bytes with no visible quality loss.
For new sites: enforce maximum image dimensions on upload. WordPress's default is to keep the original — meaning a 4000×3000 phone photo gets stored even if you only display 800×600.
Plugin discipline
Every active plugin is potential overhead. Audit quarterly:
- Disable plugins you don't actively use
- Replace 3 plugins doing related tasks with 1 plugin that does all (Rank Math vs. Yoast + redirection plugin + breadcrumbs plugin)
- Watch for plugins that load assets on every page (a popup plugin loading 50KB of JS on your homepage when popups only run on landing pages)
Measurement
You can't optimize what you don't measure. Use:
- Query Monitor plugin: shows slow queries, redundant queries, hooks fired per request
- Lighthouse in Chrome DevTools: gives you Core Web Vitals scores
- WebPageTest: real-world load timing from various locations
Target: TTFB under 200ms, LCP under 2.5s, CLS under 0.1. Sites hitting those numbers don't have a "WordPress is slow" problem.