PHP OPcache JIT Was Crashing PHP-FPM Workers and Causing Random 502s on a WordPress Server

· 11 min read

A client's WordPress site started throwing 502 Bad Gateway errors at random. Not a flood — maybe four or five per hour, scattered across different pages. No pattern to which URLs were affected. The site would work fine for ten minutes, then a request would fail, then everything was normal again.

The nginx error log showed the cause immediately:

2026-06-28 14:23:17 [error] 4821#0: *38291 recv() failed (104: Connection reset by peer) while reading response header from upstream

PHP-FPM was dropping connections mid-request. The PHP-FPM log confirmed it:

[28-Jun-2026 14:23:17] WARNING: [pool www] child 19284 exited on signal 11 (SIGSEGV) after 847.22 seconds from start
[28-Jun-2026 14:23:17] NOTICE: [pool www] child 19301 started

Signal 11. SIGSEGV. A segmentation fault — PHP-FPM workers were crashing hard, and the process manager was spawning replacements. Each crash killed whatever request was in flight, producing a 502 for that visitor.

Ruling out the usual suspects

SIGSEGV on a PHP-FPM worker has a short list of common causes. I ran through them:

OOM killer misreported as SIGSEGV. The kernel's out-of-memory killer terminates processes and PHP-FPM sometimes reports the signal as SIGSEGV. I checked:

dmesg | grep -i "out of memory"
dmesg | grep -i "oom"

Clean. No OOM events. The server had 8GB of RAM with 3GB free. This was a genuine segfault, not a memory pressure kill.

Buggy PHP extension. A misbehaving extension can corrupt shared memory and crash workers. I checked which extensions were loaded:

php -m | sort

Nothing unusual — the standard WordPress stack. No imagick compiled from source, no exotic PECL modules. All extensions were from the distribution's official PHP packages.

Corrupted OPcache shared memory. If the OPcache memory region gets corrupted, every worker that reads from it segfaults. A restart would fix it temporarily. I restarted PHP-FPM and the crashes stopped — for about twenty minutes. Then they came back.

That ruled out a one-off corruption event. Something was actively triggering the fault.

Finding the actual cause

I checked the PHP configuration more carefully:

php -i | grep -i jit
opcache.jit => tracing => tracing
opcache.jit_buffer_size => 128M => 128M
opcache.jit_bisect_limit => 0 => 0
opcache.jit_debug => 0 => 0
opcache.jit_hot_func => 127 => 127
opcache.jit_hot_loop => 64 => 64
opcache.jit_hot_return => 8 => 8
opcache.jit_hot_side_exit => 8 => 8
opcache.jit_max_exit_counters => 8192 => 8192
opcache.jit_max_root_traces => 1024 => 1024
opcache.jit_max_side_traces => 128 => 128

JIT was enabled in tracing mode with a 128MB buffer. The server was running PHP 8.4.

This was the problem.

Why JIT crashes WordPress servers

PHP's JIT (Just-In-Time) compiler translates PHP bytecode into native machine code at runtime. In theory, this makes hot code paths faster by skipping the bytecode interpreter entirely. In practice, the tracing JIT compiler has a history of segfault bugs — particularly in PHP 8.1 through 8.4.

The tracing JIT works by monitoring which code paths execute frequently, compiling those paths to native code, and then executing the native code on subsequent requests. When the native code generator hits certain PHP opcode patterns, it can produce invalid machine instructions or corrupt its own trace buffer. The result is a SIGSEGV that kills the PHP-FPM worker.

WordPress makes this worse. A typical WordPress request loads hundreds of files across core, theme, and plugins. The code paths are highly varied — a WooCommerce checkout request executes completely different code from a blog post view. The tracing JIT builds traces for all of these, and the more diverse the code paths, the more opportunities for the JIT compiler to hit a bug.

The PHP bug tracker has a long trail of JIT-specific segfaults. GH#10626 is a clear example — a tracing JIT crash triggered by specific opcode patterns during trace compilation. There are dozens more across the PHP 8.x lifecycle, all stemming from the same fundamental problem: the tracing JIT generating bad native code under specific conditions. (Not every OPcache segfault is JIT-related — some involve the file cache or observer API independently — but JIT is by far the most common trigger on WordPress servers.)

PHP 8.5.7 (released June 2026) patched three separate JIT crashes related to VM interrupts during user function calls and tailcall operations. But each PHP minor release seems to fix some JIT segfaults while the overall pattern continues.

The fix

I disabled JIT while keeping OPcache enabled. These are two separate things — OPcache's bytecode caching is rock-solid and gives WordPress a 30-50% response time improvement. JIT is an additional layer on top that compiles bytecode to native machine code, and it's the part that crashes.

In the PHP configuration (on this server, /etc/php/8.4/fpm/conf.d/10-opcache.ini):

; OPcache bytecode caching — keep this enabled
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.revalidate_freq=60
opcache.validate_timestamps=1

; JIT — disable it
opcache.jit=disable
opcache.jit_buffer_size=0

Then restart PHP-FPM:

sudo systemctl restart php8.4-fpm

The 502 errors stopped immediately. Over the following week, zero SIGSEGV entries in the PHP-FPM log.

Why the server had JIT enabled in the first place

This is worth understanding because it's a trap that catches people during PHP upgrades.

In PHP 8.0 through 8.3, the defaults were:

opcache.jit=tracing     # JIT mode was "tracing" by default
opcache.jit_buffer_size=0  # but buffer was 0, so JIT was effectively off

JIT was configured but dormant — the zero-byte buffer meant no native code was ever compiled. Someone could look at the config, see opcache.jit=tracing, and think JIT was already running. It wasn't.

Then in PHP 8.4, the defaults changed:

opcache.jit=disable        # JIT mode changed to "disable"
opcache.jit_buffer_size=64M  # buffer got a real default value

The intention was clearer defaults. But if you were upgrading from PHP 8.3 and had opcache.jit=tracing set explicitly in your config (even if JIT was never actually active because the buffer was 0), and you didn't also set opcache.jit_buffer_size=0, the new 64MB default buffer would silently activate JIT on upgrade.

That's exactly what happened on this server. The OPcache config file had been carried forward from PHP 8.2 with opcache.jit=tracing set explicitly. When PHP was upgraded to 8.4, the new jit_buffer_size=64M default kicked in and JIT went live without anyone intending it to.

Does JIT actually help WordPress?

No. WordPress workloads are I/O-bound, not CPU-bound. A typical WordPress request spends its time on:

  • Database queries (waiting for MariaDB/MySQL)
  • File reads (loading PHP files, reading uploads)
  • External HTTP requests (payment gateways, API calls, update checks)
  • Object cache reads (Redis/Memcached round trips)

JIT accelerates CPU-intensive computation — the kind of work where the same tight loop executes millions of times. That's not WordPress. Benchmarks consistently show JIT providing 1-5% improvement on WordPress page loads, which is within measurement noise.

For comparison, OPcache bytecode caching (without JIT) provides 30-50% improvement. Redis object caching provides another 20-40% on database-heavy pages. Those are the wins that matter.

How to check if JIT is running on your server

The CLI SAPI (php -r) has separate OPcache configuration from PHP-FPM and often has opcache.enable_cli=0 by default. Checking JIT via the command line can report a completely different state from what FPM is actually running. To get the real picture, check through FPM itself.

Drop a temporary diagnostic file into your web root:

<?php
// jit-check.php — delete after use
header('Content-Type: text/plain');
$status = opcache_get_status(false);
echo json_encode($status['jit'] ?? 'opcache not active', JSON_PRETTY_PRINT);

Then request it through the web server:

curl -s https://your-site.com/jit-check.php | python3 -m json.tool

Look for enabled and on. If both are true, JIT is active in FPM:

{
    "enabled": true,
    "on": true,
    "kind": 5,
    "opt_level": 4,
    "opt_flags": 6,
    "buffer_size": 134217728,
    "buffer_free": 131023408
}

Delete the file as soon as you've checked — don't leave diagnostic scripts on production servers. If you're running WordPress in production and both enabled and on show true, disable JIT unless you have specific profiling data showing it helps your workload.

Alternatively, if you can't expose a temporary file, check the FPM directive values directly:

php-fpm8.4 -i 2>/dev/null | grep -E 'opcache\.jit[^_]'

This reads the FPM binary's compiled-in and configured values without needing a web request.

Preventing this on future PHP upgrades

Add explicit JIT settings to your OPcache configuration. Don't rely on defaults — they've changed once and could change again:

; Always be explicit about JIT state
opcache.jit=disable
opcache.jit_buffer_size=0

If you manage multiple servers, check all of them after a PHP minor version upgrade. Use the FPM binary to check directive values across your fleet:

php-fpm8.4 -i 2>/dev/null | grep -E 'opcache\.jit[^_]'

Any server reporting opcache.jit => tracing with a non-zero buffer size needs attention.

The takeaway

OPcache bytecode caching is essential for WordPress performance. JIT compilation is not — and on PHP 8.1 through 8.4, it actively introduces crash risk with no meaningful performance benefit for WordPress workloads. The tracing JIT has been improving with each PHP release, but until the segfault history stabilises, the safe default for WordPress production servers is opcache.jit=disable.

If you're seeing intermittent 502 errors and your PHP-FPM logs show SIGSEGV, check your JIT configuration before diving into more complex debugging. It might be a two-line fix.


I manage 70+ WordPress sites and handle exactly this kind of server-level debugging. If your server is throwing intermittent 502s or you're planning a PHP upgrade and want it done safely, take a look at the maintenance plans. PHP upgrades, server tuning, and incident response are all included. For more on PHP-FPM configuration, see my post on PHP-FPM worker exhaustion and the PHP-FPM slow log guide.

Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans Get in Touch

Get in Touch to Discuss Your Needs