Profile    Mohammed Shiroz Status   Loading  
Logo
Share This
Back to blog
Filter by:
Tags
//Article title

9 Performance Quick Wins for PHP Sites That Take Minutes, Not Rewrites

About Post

Most slow PHP sites aren't slow because of PHP. They're slow because of a handful of boring settings nobody switched on, an image that weighs more than the rest of the page combined, and one query doing a full table scan on every request.

The good news: boring problems have boring fixes. None of the wins below needs a rewrite, a new framework or a bigger server. Most take minutes. Together they can change how a site feels.

First, measure for five minutes

Before touching anything, find out where the time goes. There are only two places:

  • Server time: how long until the first byte arrives (TTFB). Open DevTools, Network tab, click the HTML request, look at "Waiting for server response".
  • Browser time: everything after that. Images, scripts, fonts, layout. Lighthouse in Chrome DevTools gives you a decent summary.

A slow TTFB means look at PHP, the database and the server. A fast TTFB with a slow page means look at the front end. Fixing the wrong half is how people spend a week and gain nothing.

Server-side wins

1. Make sure OPcache is on, and tuned

Without OPcache, PHP reads and compiles every file on every request. With it, compiled code stays in memory. It's bundled with PHP, but I still find it disabled or starved of memory more often than you'd expect. Check php -i | grep opcache (or phpinfo() on the web server, then remove it) and aim for something like:

opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0

The gotcha is the last line. With validate_timestamps=0, PHP stops checking whether files changed, which is faster, but it also means your deploy must reload PHP-FPM (or Apache) or the old code keeps running. Put the reload in your deploy script.

2. Cache the framework's own work

Laravel spends time on every request loading config files, registering routes and discovering events. In production, cache all of it in one go:

composer install --no-dev --optimize-autoloader
php artisan optimize   # config, routes, events and views

One trap: after config:cache, the .env file isn't read any more, so any env() call outside your config/ files returns null. Read env values in config files only, and use config() everywhere else.

3. Cache the expensive answers

The dashboard numbers that take a heavy query but only change a few times an hour don't need to be computed for every visitor:

$stats = Cache::remember('dashboard.stats', now()->addMinutes(10), function () {
    return [
        'open_requests' => MaintenanceRequest::where('status', 'open')->count(),
        'overdue_invoices' => Invoice::overdue()->count(),
    ];
});

Use Redis or Memcached as the cache store in production rather than files when you can. And decide upfront how stale each value is allowed to be. That's a product question, not a technical one.

Database wins

4. Index what you filter and sort by

The single most common cause of a slow page I see is a query with a WHERE or ORDER BY on a column with no index. Run EXPLAIN on your slowest queries. If you see type: ALL on a big table, that's a full scan. A composite index matching the query (for example (status, created_at)) often turns seconds into milliseconds.

Turn on MySQL's slow query log in production so you find these from evidence instead of guesses.

5. Kill the N+1 queries

A list page that runs one query for the list and then one more per row is fine with 10 rows and painful with 500. Eager load with with(), and in development add Model::preventLazyLoading(! app()->isProduction()); in a service provider so Laravel throws an exception the moment it happens.

Don't do slow work inside the request

6. Queue it

Sending email, generating a PDF, resizing an upload, calling a third-party API: the user doesn't need to wait for any of these. Push them to a queue and respond immediately.

Mail::to($tenant)->queue(new ReceiptMail($payment));

GenerateContractPdf::dispatch($contract);

This is often the biggest perceived speed-up of the whole list, because the slowest part of a request is usually waiting on someone else's server.

Browser-side wins

7. Fix the images

Images are usually the heaviest thing on the page. Three fixes cover most of it:

  • Serve modern formats (WebP or AVIF) at the size they're actually displayed. A 4000-pixel photo shown in a 400-pixel card is pure waste.
  • Add loading="lazy" to images below the fold. Not to the hero image: lazy-loading the main image makes the page feel slower.
  • Always set width and height attributes so the browser reserves space and the layout doesn't jump.

8. Let the browser cache your assets properly

If your build tool (Vite, for example) puts a content hash in file names like app-3f9a1c.js, those files can be cached for a year, because a new version gets a new name. In Apache, an .htaccess in the build folder can do it:

<IfModule mod_headers.c>
    Header set Cache-Control "public, max-age=31536000, immutable"
</IfModule>

Only do this for fingerprinted files. Your HTML and anything without a hash in its name should not be cached like this, or users will be stuck on old versions.

9. Compress text responses

HTML, CSS, JS and JSON compress extremely well. Make sure gzip or Brotli is enabled on the web server. It's a config line, and it shrinks every text response.

The order I'd do them in: measure, then OPcache and php artisan optimize, then the slow query log and indexes, then queues, then images and caching headers. The first three are server settings you can do in an afternoon.

The quick-wins checklist

  • OPcache on, with enough memory, and PHP reloaded on deploy.
  • composer install --no-dev -o and php artisan optimize in the deploy script.
  • Expensive, rarely-changing values cached with a clear expiry.
  • Slow query log on, indexes for real query patterns, no N+1.
  • Email, PDFs and third-party calls on a queue.
  • Images resized, modern formats, lazy below the fold, dimensions set.
  • Long cache headers for hashed assets, compression for text.

Which of these did you find switched off on a server you inherited? For me, it's OPcache surprisingly often.

Comments (0)
Leave your review

Thanks for your valuable comments. Your comments has been updated and appreciate your getting in touch...

01. About Shiroz

Mohammed Shiroz

Hi, I'm Mohammed Shiroz, a software engineer and AI enthusiast from Sri Lanka who turns ideas into intelligent, real-world solutions. With over 9 years of hands-on experience, I currently lead real estate ERP development at Kate Group, a...

03.My Projects

04. Categories

Ready To order Your Project ?

Get in Touch
Close