I run several production servers. Most of the traffic going through my hosting lands on WordPress sites. But not all of it. When a client asks me for a bespoke management system, a backend for an app, or a small e-commerce store with non-standard rules, a CMS is no longer enough. That’s where PHP frameworks come in. For Laravel security updates I’ve put together a check that I actually run, not just one I talk about in theory.

How many sites are CMS-based, and how many are custom-built
W3Techs figures say that roughly 68% of sites with a recognised CMS run WordPress, Shopify, or a similar platform. The rest, a bit more than a third of the publicly monitored web, uses none of the CMS platforms those statistics track. In there you’ll find static sites, but also plenty of custom applications. It’s not a perfect figure. “No CMS detected” doesn’t exactly equal “custom application”. But the direction is clear: hand-built web apps remain a huge slice of the internet.
Among the PHP frameworks used for these applications, Laravel is today the most widespread. JetBrains’ State of PHP 2025 survey, covering over 1,700 developers, reports 64% usage for Laravel. Symfony follows, but well behind. On my hosting the situation matches this trend. The custom applications I manage for clients, when they’re not WordPress, are almost always Laravel projects. Some clients arrive with an existing project to maintain. Others ask for one from scratch, knowing it’s the most common choice on the market. Absolute site counts, from sources like BuiltWith, should be taken with caution: Laravel bundles several Symfony components internally, which muddies statistics based purely on installed packages. But the overall picture doesn’t change: for anyone writing bespoke PHP applications, Laravel today is the most common starting point, not the exception.
How I monitor Laravel security updates
A Laravel project in production is never “finished and settled”. The framework, its components, and the dozens of Composer packages it brings with it all receive patches when a flaw is found. If nobody checks, a site can stay vulnerable for months. The client often doesn’t even notice. It’s the same principle I applied when I had to handle an attack in progress on a server during a PHP security emergency: better to prevent with a regular check than to chase the problem after it has already broken out.
That’s why I wrote a simple bash script. I run it via cron on every Laravel project I manage. I also plug it into deploy pipelines, when the client has one. It doesn’t do anything magic: it reads Composer’s lock file and checks it against databases of known vulnerabilities. Here it is, exactly as I use it, unchanged.
The audit script
#!/usr/bin/env bash
set -euo pipefail
PROJECT_DIR="${1:-.}"
cd "$PROJECT_DIR"
echo "==> Project: $(pwd)"
if [[ ! -f composer.lock ]]; then
echo "SKIP: composer.lock non trovato (non sembra un progetto composer installato)."
exit 0
fi
echo ""
echo "==> Composer audit (vulnerabilità note)"
# --no-interaction utile in CI; fallisce (exit code != 0) se trova advisory
composer audit --no-interaction
echo ""
echo "==> Pacchetti outdated (info)"
composer outdated --direct || true
echo ""
echo "==> Laravel version (se presente)"
php -r '
$lock=json_decode(file_get_contents("composer.lock"), true);
$pkgs=array_merge($lock["packages"] ?? [], $lock["packages-dev"] ?? []);
$laravel=null;
foreach ($pkgs as $p) { if (($p["name"] ?? "")==="laravel/framework") { $laravel=$p["version"] ?? null; break; } }
echo $laravel ? "laravel/framework: $laravel\n" : "laravel/framework non trovato nel lock\n";
'
Disclaimer: the script is provided as-is, for informational purposes. You use it entirely at your own risk: I accept no responsibility for data loss, downtime, or any error caused by incorrect use or by running it in a context different from the one I use it in myself. Always keep an independent backup before running it in production.
composer audit is a command built into Composer since version 2.4. It reads the exact package versions inside composer.lock. It checks them against a database of published security advisories. The database comes through Packagist, which in turn relies on the FriendsOfPHP/security-advisories repository and the GitHub Advisory Database. If it finds a match, it lists the package, the vulnerable version, and the linked advisory. Then it exits with a non-zero status code. That’s the detail I care about most. A non-zero exit code is what lets a cron job or a pipeline work out, on its own, that something needs reviewing. I don’t have to read every log line by hand.
On production projects I run it once a day via cron, with the output emailed to me if the command fails. On a few more structured clients I’ve also wired it into the deploy pipeline. If composer audit finds a blocking advisory, the deploy stops before it reaches production. Better to stop there than find out afterwards, with the site already live. This daily check, repeated on every project, is the practical core of the Laravel security updates I actually apply, not just the ones I remember to do by hand.
The second line of the script, composer outdated --direct, shows only the dependencies declared by hand in composer.json. It doesn’t show the entire tree of transitive dependencies. An average Laravel project carries hundreds of indirect packages. If I listed all of them, the output would be unreadable. I’d end up ignoring it, like with too many automated alerts. Looking only at the direct dependencies, I see at a glance what I actually chose. I can decide calmly what to update and when to do it.
The last part of the script prints the version of laravel/framework found in the lock file. I do this because the Laravel version lifecycle has precise deadlines. I want to know them without opening every project by hand, one by one. According to the official Laravel support table, security patches for version 11 ended on 12 March 2026. Version 12 receives them until 24 February 2027. Since the first quarter of 2026 the current version is Laravel 13. Planning Laravel security updates ahead of time also means this: knowing which version every project is stuck on, before its support runs out.
The honest limits of this check
composer audit is not an antivirus. It doesn’t read the application’s code. It only flags known vulnerabilities that have already been published in an advisory. Against a zero-day flaw, discovered but not yet disclosed, it’s useless. That’s why I treat it as a basic hygiene check, not an absolute guarantee. Regular package updates remain the most effective practice, even without an active advisory. Laravel’s official documentation recommends staying on the current version of the framework. It also recommends automating tests before every major upgrade. That turns upgrading into a habit, not a last-minute emergency.
The fact remains that a script of a few lines, run every day, would have saved me more than one awkward email to clients, back in the years when I didn’t yet use it. Today it’s part of the maintenance routine for every Laravel project I look after. I run it alongside monitoring server logs and access attempts. Laravel security updates don’t eliminate the risk. They cut it down a lot. And they cost very little, compared to a compromised site and a client who loses trust.
Leave a Reply