On 21 October 2026, two critical PHP vulnerabilities, CVE-2026-109618 and CVE-2026-109619, prompted a review of the production infrastructure behind SwiftVecto, a web-based platform offering browser utilities and file-processing tools.
The following morning, I carried out security maintenance on the Ubuntu server hosting the application.
What initially appeared to be a straightforward PHP upgrade turned into a useful reminder about distribution-maintained packages, security backports, backups, and the difference between a successful package installation and a healthy production application.
This article documents the actual maintenance process, including the commands used, the results obtained, and the checks performed before returning the server to normal operation.
For security reasons, application-specific directory paths, database names and service identifiers have been replaced with generic examples.
1. Understanding the vulnerability and the available fix
The two PHP vulnerabilities were addressed through security updates released by the PHP project and Linux distribution maintainers.
The important question was not simply whether the server was running the latest upstream PHP release.
It was whether the installed packages contained the security fixes required to address the vulnerabilities.
This distinction matters because Ubuntu, like other Linux distributions, can backport security fixes into existing package versions.
For Ubuntu 24.04 LTS, security notice USN-8948-1 identified the corrected PHP 8.3 package revision as:
8.3.6-0ubuntu0.24.04.13
That was the version I needed to verify against the production environment.
Rather than replacing Ubuntu's PHP installation with packages from an alternative repository, the preferred approach was to use the distribution's maintained security packages.
2. Establishing the production environment
The environment consisted of:
| Component | Configuration |
|---|---|
| Hosting | DigitalOcean Droplet |
| Operating system | Ubuntu 24.04.4 LTS |
| Application framework | Laravel |
| Web server | Nginx |
| PHP runtime | PHP 8.3-FPM |
| Database | MySQL |
| Background processing | Supervisor-managed Laravel queue worker |
I started by checking the operating system and PHP version:
cat /etc/os-release
php -v
The PHP command reported:
PHP 8.3.6 (cli)
At first glance, that looked concerning.
The upstream PHP project had released newer versions containing the security fixes, while the server was still reporting PHP 8.3.6.
However, the upstream version number alone was not sufficient to determine whether the Ubuntu installation was patched.
There were two reasons.
First, php -v reports the command-line runtime, which is not necessarily the runtime serving web requests.
Second, Ubuntu may retain the same upstream PHP version while incorporating backported security fixes.
The next step was to inspect the actual installed packages.
3. Checking the installed PHP package revisions
I queried the PHP packages using:
dpkg-query -W \
-f='${Package} ${Version}\n' \
'php8.3*' 2>/dev/null
The relevant installed packages were at:
8.3.6-0ubuntu0.24.04.12
This included:
php8.3-cliphp8.3-fpmphp8.3-commonphp8.3-opcache
Ubuntu's security notice identified .13 as the corrected revision.
The server was running .12.
That established that the relevant security update had not yet been installed.
Why the package revision matters
Consider these two versions:
8.3.6-0ubuntu0.24.04.12
8.3.6-0ubuntu0.24.04.13
Both contain the upstream version number 8.3.6.
However, they are different Ubuntu package revisions.
The final revision incorporates distribution-specific changes, which can include security backports.
For administrators maintaining Ubuntu systems, this is why checking only php -v can be misleading.
The exact package revision must be compared against the vendor's security advisory.
4. Refreshing the package indexes
I checked the versions available through APT:
apt-cache policy php8.3-cli php8.3-fpm
Initially, the installed and candidate versions were identical:
Installed: 8.3.6-0ubuntu0.24.04.12
Candidate: 8.3.6-0ubuntu0.24.04.12
The patched revision was not yet visible in the local package indexes.
I refreshed the indexes:
sudo apt update
Running the policy check again showed:
Installed: 8.3.6-0ubuntu0.24.04.12
Candidate: 8.3.6-0ubuntu0.24.04.13
The patched version was now available through the configured Ubuntu repositories.
There was no need to introduce a third-party repository or migrate to another PHP release branch.
This kept the maintenance focused on the relevant security update.
5. Simulating the upgrade before changing production
Before installing anything, I ran an APT simulation:
sudo apt-get -s install --only-upgrade \
php8.3-cli \
php8.3-fpm \
php8.3-common \
php8.3-opcache
APT reported:
14 upgraded, 0 newly installed,
0 to remove and 69 not upgraded.
The proposed changes included the four explicitly requested packages and their related PHP extensions.
These included components used for MySQL connectivity, XML processing, ZIP handling, image processing and other application functionality.
All 14 packages were scheduled to move from revision .12 to .13.
The simulation provided two useful assurances:
- The update could be resolved without installing additional packages or removing existing ones.
- The proposed changes were limited to the expected PHP packages and extensions.
The 69 other packages were outside the scope of this targeted PHP update and would require separate review.
For production maintenance, I prefer to understand exactly what the package manager intends to change before approving an installation.
6. Creating backups before applying the patches
Before proceeding, I created a DigitalOcean Droplet snapshot.
That provided a server-level recovery option if the maintenance introduced an unexpected infrastructure problem.
However, I also wanted a separate logical database backup.
The application uses MySQL on the same server, so relying exclusively on a Droplet snapshot would not have been my preferred approach.
I first verified the database service:
systemctl is-active mysql
The result was:
active
I then created a restricted backup directory.
The path below is illustrative rather than the actual production location:
sudo install -d -m 700 /root/app-backups
A compressed logical database backup can be created using:
set -o pipefail
BACKUP="/root/app-backups/database-$(date +%Y%m%d-%H%M%S).sql.gz"
mysqldump \
--single-transaction \
--quick \
--no-tablespaces \
--routines \
--triggers \
--events \
app_database | gzip > "$BACKUP"
Here, app_database is a placeholder for the actual database name.
The --single-transaction option is useful for producing a consistent logical backup of InnoDB tables without locking them for the entire dump, provided concurrent schema changes are avoided.
The pipefail setting ensures that a failure in mysqldump is not concealed by a successful exit from gzip.
After creating the backup, I checked the compressed file:
gzip -t "$BACKUP"
I also inspected the end of the SQL dump:
zcat "$BACKUP" | tail -n 8
The actual backup contained the MySQL completion marker:
-- Dump completed on 2026-10-22 4:28:45
The compressed backup was approximately 866 KB.
Restricting access to database backups is also important:
chmod 600 "$BACKUP"
The backup passed the compression integrity check, and the completion marker was present.
These checks do not replace a proper restoration test, but they provide useful evidence that the backup completed successfully.
With the Droplet snapshot and database backup available, the update could proceed with a more reasonable recovery position.
7. Applying the PHP security update
I ran the targeted installation:
sudo apt-get install --only-upgrade \
php8.3-cli \
php8.3-fpm \
php8.3-common \
php8.3-opcache
APT upgraded the expected 14 PHP packages.
These included:
- PHP CLI and PHP-FPM
- PHP common components and OPcache
- MySQL and SQLite extensions
- cURL, GD and internationalisation extensions
- Multibyte string, XML and ZIP extensions
- Other installed PHP 8.3 components
The installation completed successfully, and PHP-FPM restarted during the update.
I then checked the installed package revisions:
dpkg-query -W \
-f='${Package} ${Version}\n' \
php8.3-cli \
php8.3-fpm \
php8.3-common \
php8.3-opcache
All four packages reported:
8.3.6-0ubuntu0.24.04.13
This matched the corrected revision identified in Ubuntu's security notice.
The PHP vulnerabilities had been addressed through Ubuntu's security-maintained packages without changing the application's PHP major or minor version.
8. Verifying PHP-FPM and background processing
Installing the packages was only part of the maintenance.
The next step was to verify that the services required by the application were still functioning.
I checked PHP-FPM:
systemctl status php8.3-fpm --no-pager
The service reported:
Active: active (running)
I reviewed its recent journal entries:
journalctl -u php8.3-fpm \
--since "15 minutes ago" \
--no-pager -n 40
The inspected output showed normal service restarts during the package installation, with no startup errors.
The application also uses Laravel queues for background processing.
These workers are managed by Supervisor, so I checked their status:
sudo supervisorctl status
The production worker was running.
This is an important check for Laravel applications.
A website may respond successfully to HTTP requests while queued jobs remain unprocessed because a background worker has stopped.
Checking the homepage alone would not establish that the application was functioning as expected.
9. Checking the website and application logs
I tested the public HTTPS endpoint:
curl -sS -o /dev/null \
-w 'HTTP status: %{http_code}\n' \
https://swiftvecto.com/
The response was:
HTTP status: 200
That confirmed the homepage was responding successfully.
I also reviewed the Laravel application logs for evidence of new errors following the update.
For readers adapting this procedure, the application log can be inspected using a command such as:
tail -n 50 /var/www/example-app/storage/logs/laravel.log
The path above is an example and should be replaced with the relevant application directory.
When reviewing logs after maintenance, I find it useful to distinguish between historical errors and errors that began during or after the change window.
An existing error should not automatically be attributed to the latest update simply because it appears in the log.
In this case, the inspected log contained no newly recorded production errors attributable to the PHP maintenance.
That was reassuring, but it was not a substitute for testing individual application features.
An HTTP 200 response verifies that a particular endpoint responded successfully. It does not prove that every controller, database operation, queued job or file-processing function works correctly.
Infrastructure verification and application regression testing are separate responsibilities.
10. Identifying a pending kernel update
During the maintenance, Ubuntu also reported that the running kernel was older than the installed kernel.
The running kernel was:
6.8.0-134-generic
The newer installed kernel was:
6.8.0-147-generic
This was separate from the PHP vulnerability remediation.
However, because the server was already undergoing controlled maintenance, I decided to complete the pending kernel activation.
Before rebooting, I checked that the essential services were configured to start automatically:
systemctl is-enabled \
nginx php8.3-fpm mysql supervisor
All four returned:
enabled
I checked their current running states:
systemctl is-active \
nginx php8.3-fpm mysql supervisor
All four were active.
I also verified the installed kernel package:
dpkg-query -W \
-f='${Package} ${Status} ${Version}\n' \
linux-image-6.8.0-147-generic
The result confirmed:
linux-image-6.8.0-147-generic install ok installed 6.8.0-147.147
Finally, I checked for failed systemd units:
systemctl --failed --no-pager
The result was:
0 loaded units listed.
With the pre-reboot checks completed, I initiated a controlled reboot:
sudo systemctl reboot
11. Post-reboot verification
Once the server became available again, I reconnected and verified the active kernel:
uname -r
The output was:
6.8.0-147-generic
I checked the essential services:
systemctl is-active \
nginx php8.3-fpm mysql supervisor
All four returned:
active
The Supervisor-managed Laravel queue worker had restarted successfully.
I repeated the public website check:
curl -sS -o /dev/null \
-w 'HTTP status: %{http_code}\n' \
https://swiftvecto.com/
The response remained:
HTTP status: 200
Finally:
systemctl --failed --no-pager
Again returned:
0 loaded units listed.
The new kernel was active, the essential services had recovered, and the public application remained accessible.
12. What this maintenance exercise reinforced
There are several lessons worth taking from this process.
The upstream version number is not the whole story
PHP 8.3.6 may appear outdated when compared with a newer upstream release.
On Ubuntu, however, security fixes can be backported into distribution-maintained packages.
The correct approach is to compare the exact installed package revision against the relevant Ubuntu security notice.
Simulate package changes before applying them
APT's simulation mode provides an opportunity to inspect dependency changes, package removals and the overall upgrade scope.
It is a simple step that can prevent avoidable surprises.
Maintain more than one recovery option
A Droplet snapshot and a logical MySQL backup serve different purposes.
Having both available before modifying production infrastructure improves recovery options if something goes wrong.
Neither should be considered fully validated without an appropriate restoration test.
Check background services as well as HTTP responses
Laravel applications may depend on queue workers, scheduled tasks and other processes that are not exercised by a homepage request.
Checking Nginx and PHP-FPM is necessary, but it may not be sufficient.
Keep security maintenance focused
A critical security update is not the ideal time to introduce unrelated application changes.
The PHP update was handled as a targeted maintenance operation, with unrelated work left for separate review.
A successful update is not a complete regression test
A successful package installation, active services, healthy queue workers and an HTTP 200 response are all useful indicators.
They do not prove that every application feature is functioning correctly.
That distinction should remain clear when documenting production maintenance.
Conclusion
The PHP security update on SwiftVecto's production infrastructure was completed successfully.
The server moved from Ubuntu's PHP package revision:
8.3.6-0ubuntu0.24.04.12
To:
8.3.6-0ubuntu0.24.04.13
This addressed the vulnerabilities identified in Ubuntu's security notice.
Fourteen PHP packages were upgraded, PHP-FPM restarted successfully, and the Laravel queue worker remained operational.
A pending kernel update was also activated through a controlled reboot.
Following the reboot, Nginx, PHP-FPM, MySQL and Supervisor were all active, the website returned HTTP 200, and systemd reported no failed services.
The maintenance did not constitute a complete application regression test or a penetration test, but it provided clear evidence that the required PHP security packages had been installed and that the essential production infrastructure was functioning afterwards.
For me, the main takeaway is that security patching is not simply about installing a newer software version. It is about establishing what is affected, verifying the vendor's fix, preparing a recovery path, controlling the change, and checking the system afterwards.
Top comments (0)