Development Tips

PHP Performance Tuning: 7 Fixes That Actually Move the Needle

By Ken Key Mar 28, 2026 5 min read Updated Oct 16, 2026

PHP Performance Tuning: 7 Fixes That Actually Move the Needle
The short answer

The fixes that matter most are enabling and tuning OPcache, sizing PHP-FPM workers to your actual memory, upgrading to PHP 8.3 or newer, and caching expensive queries. Most PHP slowness I see on Long Island sites comes from stock configs nobody ever touched.

Part of the complete web development guide.

On this page

Most PHP performance advice you find online is either five years out of date or written by someone who never had to keep a site alive during a traffic spike. I’ve been tuning PHP apps for small businesses and agencies around Commack and the rest of Long Island for years, and the same handful of problems show up again and again. Here are the seven fixes I reach for first, in the order I usually apply them.

1. Turn on OPcache and actually configure it

If OPcache isn’t enabled, nothing else you do matters much. It caches compiled bytecode so PHP doesn’t re-parse every file on every request. It ships with PHP and is usually on by default in modern builds, but the stock settings are conservative. Check with php -i | grep opcache or a quick phpinfo() dump.

Reasonable production settings for a typical site:

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.save_comments=1
opcache.jit=off

Two things trip people up here. First, validate_timestamps=0 means PHP stops checking whether files changed, so you must clear the cache on every deploy (opcache_reset() or reload PHP-FPM). Second, leave the JIT off for web requests. It helps CPU-bound number crunching, not typical request/response work, and it can hurt you if you tune it blindly.

2. Tune PHP-FPM, don’t just install it

The default pm=dynamic config with a low pm.max_children is a common cause of “the site is slow” that’s really “requests are queuing.” Figure out how much RAM each worker actually uses under load, then set the process manager to match your box.

pm = dynamic
pm.max_children = 25
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 500

If each worker peaks around 60MB and you have 2GB to spare for PHP, 25 children is a sane ceiling. Set max_children too high and you’ll swap. Too low and requests back up behind a full queue while your CPU sits idle. Watch pm.max_children reached warnings in the FPM status page if you enable it.

3. Profile before you optimize anything

Guessing is how people spend a weekend “optimizing” a function that runs in 3ms while the actual bottleneck is a missing index. Profile first. Xdebug’s profiler is fine for local work, and Blackfire or SPX are good for staging.

// xdebug.mode=profile in php.ini, then run a request
// output lands in /tmp/cachegrind.out.*
// open it with KCachegrind, QCachegrind, or Webgrind

Look at inclusive time, not just the top of the list. The real cost is usually a database call or a loop that fires a query per iteration. Once you know where the time goes, the fix is usually obvious.

4. Kill N+1 queries

This is the single most common performance bug in PHP apps I look at. It hides behind ORMs and lazy loading. A page that lists 50 orders and loads each customer individually is 51 queries where 2 would do.

// Before: one query per order
$orders = Order::all();
foreach ($orders as $order) {
 echo $order->customer->name; // fires a query each time
}

// After: eager load the relation
$orders = Order::with('customer')->get();
foreach ($orders as $order) {
 echo $order->customer->name; // already loaded
}

If you’re on raw PDO or mysqli, the same idea applies: fetch the related rows in one WHERE ... IN (...) query and map them in PHP. This one change routinely cuts page load times in half on data-heavy pages.

5. Cache the expensive stuff, and know what you’re caching

Not every page needs to be dynamic. If a homepage or category page only changes when content is published, cache the rendered output and invalidate on write. Redis or Memcached handles the object cache; a reverse proxy or a simple file cache handles full pages.

$key = 'homepage_html_' . $version;
$html = $redis->get($key);

if ($html === null) {
 $html = render_homepage();
 $redis->setex($key, 3600, $html);
}

echo $html;

The trap is caching something that should be per-user (cart contents, logged-in dashboards) and serving it to everyone. Cache public, expensive, rarely-changing data. Leave personalized output alone.

6. Use the right PHP version and keep it current

Every major PHP release brings real speed gains and lower memory use. If you’re still on 7.4, which stopped getting security fixes back in 2022, you’re leaving performance and safety on the table. PHP 8.3 and 8.4 are both solid for production in 2026, and 8.4’s property hooks and asymmetric visibility can simplify code that used to need boilerplate getters.

Test on a staging copy before you jump. The breaking changes between 7.x and 8.x are mostly around removed functions and stricter type handling, and static analysis tools like PHPStan or Psalm will surface most of them before you deploy.

7. Stop shipping the same assets on every request

PHP performance isn’t only about PHP. If every page load pulls down 40 unminified CSS and JS files with no cache headers, your server is doing work it shouldn’t and users are waiting. Bundle and minify, set long cache lifetimes with hashed filenames, and let the browser do the work.

Cache-Control: public, max-age=31536000, immutable

Combine that with a content delivery network and you take a big chunk of static traffic off your origin entirely. The app server only handles what genuinely needs PHP.

Where this fits together

If you run a small business site, you probably don’t need all seven. Start with OPcache and PHP-FPM, profile once to confirm where time goes, then fix the worst offender. If you’d rather hand it off, that’s the kind of work I do through custom web development for local businesses, and it pairs well with Long Island SEO work, since a faster site tends to rank and convert better on its own.

FAQ

Do I need the PHP JIT for a normal website?

No. For typical request/response workloads, OPcache with a proper max_accelerated_files setting does more. The JIT helps CPU-heavy computation, not database-bound page rendering.

How do I know if my PHP-FPM pool is undersized?

Watch for “max_children reached” warnings and check whether requests queue while CPU sits low. That combination almost always means you need more workers, not a faster server.

Is upgrading PHP worth the risk?

Usually yes. The performance and security gains are real, and static analysis plus a staging environment catches most breakage before it hits production.

Have a project like this?

I do custom web development for Long Island businesses. If you need this done right, let’s talk.