---
title: "PHP-FPM and OPcache tuning for WordPress"
id: "561"
type: "post"
slug: "php-fpm-opcache-wordpress"
published_at: "2026-10-06T20:00:48+00:00"
modified_at: "2026-10-06T20:00:48+00:00"
url: "https://danielpazwp.com/php-fpm-opcache-wordpress/"
markdown_url: "https://danielpazwp.com/php-fpm-opcache-wordpress.md"
excerpt: "How to size OPcache and PHP-FPM workers for your own WordPress server, read opcache_get_status and the FPM status page, and find slow requests."
taxonomy_category:
  - "Performance"
---

[Performance](https://danielpazwp.com/category/performance/)
9 min read

# PHP-FPM and OPcache tuning for WordPress

How to size OPcache and PHP-FPM workers for your own WordPress server, read opcache_get_status and the FPM status page, and find slow requests.

Published **06/10/2026**9 min read

[Written byDaniel PazDaniel Paz has worked on the web for more than 14 years and has focused on WordPress since 2016. He is CEO and founder of WebOption, creator of WP Alta Performance (Brazil’s reference channel on WordPress optimization) and has shipped WordPress for Globo, Shell, Endeavor, Estratégia Concursos and Brazil’s Federal Government. He speaks at WordCamp Brazil, WordCamp Canada and WordCamp US, TDC and PHP Conference Brazil.](https://danielpazwp.com/author/daniel-paz/)

OPcache tuning for WordPress means giving PHP enough shared memory and file slots to keep the compiled code of core, your theme and every plugin in memory, so no request has to recompile it. PHP-FPM tuning means running as many PHP workers as the server’s RAM and CPU can actually sustain, so requests don’t queue. Both show up as server response time on uncached requests. The right values depend on your codebase and traffic: count your PHP files, check `opcache_get_status()`, measure real worker memory and watch the FPM status page, instead of copying numbers from a blog post.

When a WordPress site has a slow server response and the page cache is fine, the PHP layer is one of the first places I look. The usual findings are an OPcache that’s full and restarting, a pool with five workers on a server with 16 GB of RAM, or the opposite: a pool so large it pushes the server into swap at peak. This page goes through the directives that matter and how to choose values for your own server. It belongs to my guide on [how to speed up WordPress](https://danielpazwp.com/speed-up-wordpress/)
, and it’s the server-side follow-up to [reducing TTFB in WordPress](https://danielpazwp.com/ttfb-wordpress/)
.

## What do OPcache and PHP-FPM do for WordPress?

Every WordPress request loads hundreds of PHP files: core, the theme and all active plugins, whether or not the page uses them. Without OPcache, PHP reads, parses and compiles each of those files into bytecode on every single request. OPcache keeps the compiled bytecode in shared memory, and every worker reuses it.

PHP-FPM is the process manager that runs those workers. Each worker handles one request at a time. If ten requests arrive and the pool has eight workers, two wait in the listen queue, and that wait is added to their TTFB. It doesn’t show up in any WordPress profiler, because WordPress hasn’t started yet.

Neither one helps a page served from page cache, since PHP never runs. They matter for cache misses, logged-in users, wp-admin, WooCommerce checkout, the REST API, admin-ajax and cron. Those are also the requests a [Redis object cache](https://danielpazwp.com/wordpress-redis-object-cache/)
 helps with, so the two pieces of work usually go together.

## Which directives matter for OPcache tuning in WordPress?

These are the `php.ini` settings I review on every server. The defaults listed are PHP’s documented defaults. Your host or distribution may ship different ones, so check `php -i | grep opcache` or `phpinfo()` from a web request, since CLI and FPM can load different ini files.

| Directive | Default | What it controls | How to choose a value |
| --- | --- | --- | --- |
| opcache.enable | 1 | OPcache for web requests | Must be on. Some hosts still ship it off |
| opcache.memory_consumption | 128 (MB) | Shared memory for compiled scripts | Enough that free_memory stays comfortably above zero after a full day of traffic |
| opcache.interned_strings_buffer | 8 (MB) | Memory for repeated strings such as class and function names | Raise it if the status shows the interned strings buffer full |
| opcache.max_accelerated_files | 10000 | Maximum number of cached scripts | Above the number of PHP files on the server, with room for updates |
| opcache.validate_timestamps | 1 | Whether PHP checks files for changes | Leave on if WordPress updates itself; see below |
| opcache.revalidate_freq | 2 (seconds) | How often those checks happen | A higher value means fewer checks and slower pickup of changed files |
| opcache.save_comments | 1 | Keeps docblocks in the cache | Leave on. Some libraries read annotations at runtime |

To size `max_accelerated_files`, count the PHP files of every site that shares the same PHP-FPM master, since they share one OPcache:

```
find /var/www -type f -name "*.php" | wc -l
```

PHP rounds the value up to the next number in an internal list of primes, so the effective limit is a bit higher than what you set. A WooCommerce site with a page builder and forty plugins can easily pass the default of 10,000 files.

## How do you check whether OPcache is big enough?

Read `opcache_get_status()` from a web request, not the CLI, because the CLI has its own separate cache. Put a small script behind authentication or an IP restriction, open it, then delete it:

```
<?php
$s = opcache_get_status( false );
printf( "Free memory: %.1f MB\n", $s['memory_usage']['free_memory'] / 1048576 );
printf( "Wasted memory: %.1f MB\n", $s['memory_usage']['wasted_memory'] / 1048576 );
printf( "Cached keys: %d of %d\n", $s['opcache_statistics']['num_cached_keys'], $s['opcache_statistics']['max_cached_keys'] );
printf( "Hit rate: %.2f%%\n", $s['opcache_statistics']['opcache_hit_rate'] );
printf( "OOM restarts: %d\n", $s['opcache_statistics']['oom_restarts'] );
printf( "Hash restarts: %d\n", $s['opcache_statistics']['hash_restarts'] );
```

The counters that matter are the restarts. `oom_restarts` above zero means OPcache ran out of memory and threw everything away, so the next requests recompiled the whole codebase. `hash_restarts` above zero means it ran out of file slots. Either one is a reason to raise the matching directive. A high hit rate on its own doesn’t prove much, because it’s computed since the last restart.

## Should you turn off validate_timestamps on WordPress?

With `opcache.validate_timestamps=0`, PHP never checks whether a file changed. That saves a little work per request, and it’s common advice. On WordPress it has a catch: core, plugin and theme updates from wp-admin or auto-updates write new files to disk, and PHP keeps running the old bytecode until the cache is reset. You can end up with half-updated code in memory.

I only turn it off when deployments go through a pipeline that resets OPcache afterwards, either by reloading PHP-FPM or by calling `opcache_reset()` from a web request, and when updates from wp-admin are disabled. Otherwise I leave it on and, if needed, raise `revalidate_freq`.

JIT, available since PHP 8.0, is a separate question. It speeds up CPU-heavy code, and a typical WordPress request spends much of its time waiting on the database and on I/O. If you want to try it, measure TTFB on uncached requests with and without it before you keep it.

## How many PHP-FPM workers does WordPress need?

There’s no universal number. The ceiling comes from memory, and the useful value comes from traffic and CPU. The pool settings live in the pool file, often `/etc/php/<version>/fpm/pool.d/www.conf` on Debian and Ubuntu:

- `pm` picks the mode. `static` keeps a fixed number of workers. `dynamic` keeps a range between the spare server limits. `ondemand` starts workers only when requests arrive and stops them after `pm.process_idle_timeout`, which saves memory on quiet sites and adds startup time on the first request.
- `pm.max_children` is the hard limit on simultaneous workers, and so on simultaneous PHP requests.
- `pm.start_servers`, `pm.min_spare_servers` and `pm.max_spare_servers` shape the `dynamic` mode.
- `pm.max_requests` recycles a worker after that many requests. The default of 0 never recycles. A finite value contains slow memory leaks from plugins.

To set `pm.max_children`, take the RAM you can give PHP after the database, Redis, the web server and the OS, and divide it by the real average memory of a worker under normal traffic. The process name changes between distributions and PHP versions:

```
ps -C php-fpm8.3 -o rss= | awk '{ s += $1; n++ } END { printf "%d workers, %.0f MB average\n", n, s / n / 1024 }'
```

Use the measured average, not `memory_limit`. The limit is a cap for one runaway request; most workers stay well below it. Leave headroom for peaks, and check that the result is sane for your CPU too. When requests are CPU-bound, workers far beyond the number of cores just wait for each other.

## How do you see whether PHP-FPM is the bottleneck?

PHP-FPM tells you, if you ask. Two signals are enough for most diagnoses:

- The FPM log line `server reached pm.max_children setting`. If it appears at peak times, requests queued.
- The status page, enabled with `pm.status_path`. The fields `listen queue`, `max listen queue`, `max children reached` and `slow requests` tell you whether requests waited and how often.

The slow log is the most useful setting most servers don’t have. It writes a PHP backtrace for every request that runs longer than a threshold, so you can see which plugin function was executing:

```
; pool file, for example www.conf
pm.status_path = /fpm-status
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/www-slow.log
pm.max_requests = 500
```

Those values are examples to adjust, not recommendations. Pick a slowlog threshold that catches the requests you care about without filling the disk, and restrict the status path to localhost or your monitoring. Reload PHP-FPM after changes (for example `systemctl reload php8.3-fpm`, adjusting the service name).

If the slow log keeps pointing at database calls, the PHP layer is fine and the work moves to [wp_options autoload and database optimization](https://danielpazwp.com/wordpress-database-optimization/)
. If it points at HTTP calls to external APIs, cache those responses or move them to cron.

## What about the PHP version?

Run a PHP version that still receives security support and that your plugins support. Newer major versions have generally made WordPress faster, and old ones stop getting fixes. Check compatibility on staging first, since old plugins are what usually block an upgrade. Site Health warns you when your PHP version is outdated.

For where PHP sits next to page cache, object cache and the CDN, see [which cache layer you actually need](https://danielpazwp.com/page-cache-object-cache-edge-cache-which-one-you-actually-need/)
. Tuning PHP makes cache misses cheaper. It doesn’t replace caching.

## Frequently asked questions

### Can I tune OPcache on shared hosting?

Usually not. OPcache memory is set for the whole server, and many shared hosts don’t let you change it. You can still check that it’s enabled with `phpinfo()` or in the host’s PHP settings. If it’s off, that’s a reason to change hosts.

### Why is my site slow only under traffic?

That pattern points at the worker pool or the database, not at OPcache. Check the FPM log for the `pm.max_children` warning and the status page for a listen queue during the slow period.

### Should I just set pm to static with a high max_children?

Only if the memory math supports it. Too many workers on too little RAM pushes the server into swap, and everything gets slower at once. Measure worker memory first.

If your server response time is high and you want someone to trace it through PHP, OPcache, the database and the cache layers with real measurements, start with my [WordPress performance audit](https://danielpazwp.com/wordpress-performance-audit/)
.

[Work with Daniel](https://danielpazwp.com/#book)
[More in Performance](https://danielpazwp.com/category/performance/)

[About the authorDaniel Paz has worked on the web for more than 14 years and has focused on WordPress since 2016. He is CEO and founder of WebOption, creator of WP Alta Performance (Brazil’s reference channel on WordPress optimization) and has shipped WordPress for Globo, Shell, Endeavor, Estratégia Concursos and Brazil’s Federal Government. He speaks at WordCamp Brazil, WordCamp Canada and WordCamp US, TDC and PHP Conference Brazil.Author page](https://danielpazwp.com/author/daniel-paz/)

## Related articles

[All articles](https://danielpazwp.com/blog/)

[Performance Oct 2026FlyingPress vs WP Rocket: features, trade-offs and how to choose](https://danielpazwp.com/flyingpress-vs-wp-rocket/)
[Performance Oct 2026What are Core Web Vitals?](https://danielpazwp.com/what-are-core-web-vitals/)
[Performance Oct 2026NitroPack vs WP Rocket: SaaS optimizer or plugin on your server](https://danielpazwp.com/nitropack-vs-wp-rocket/)
