- Targeted Upgrade: Record installed package versions and upgrade only active PHP packages with
apt-get install --only-upgradeto prevent unintended system-wide package changes. - Graceful Service Reload: Reload PHP-FPM (
systemctl reload php8.4-fpm) to spawn workers with the new binary while existing workers finish active requests. - Process and Cache Verification: A command run in the terminal cannot clear the web server's OPcache; reload PHP-FPM instead. Verify the active master process in memory and gracefully restart queue workers.
| Metadata | Details |
|---|---|
| Who It Is For | Developers, DevOps engineers, and system administrators maintaining PHP applications on Linux servers. |
| Prerequisites | Sudo access on an Ubuntu/Debian server running Nginx with PHP-FPM, plus Composer installed. |
| Estimated Time | 20–25 minutes reading; 15 minutes in staging, 10-minute maintenance window in production. |
| Difficulty | Intermediate (includes copy-pasteable runbook for first-time upgraders). |
Glossary of Key Terms
Before performing upgrades, familiarize yourself with these core server and PHP runtime concepts:
- CLI (Command-Line Interface): The standalone terminal executable (
/usr/bin/php) used for cron jobs, Artisan commands, and queue workers. - FPM (FastCGI Process Manager): The daemon service (
/usr/sbin/php-fpm8.4) managing worker processes that execute PHP code for web requests forwarded by Nginx. - OPcache: A shared-memory cache that stores precompiled PHP script bytecode so PHP scripts do not need to be loaded and parsed on every HTTP request.
- Graceful Reload: Sending
SIGUSR2to the master process (systemctl reload) to spawn new workers on the updated binary while allowing existing workers to complete in-flight requests without dropping connections. - Package Hold: Locking package versions with
apt-mark holdto prevent automated package managers from upgrading them unexpectedly. - Version List: A recorded baseline file (
/backup/php-versions-before-*.txt) listing exact installed package versions to enable precise, safe rollbacks. - Dry Run: A simulation mode (
--dry-run) where the package manager calculates dependency changes without modifying files or installing packages on disk. - EOL (End of Life): The official date after which a PHP release branch no longer receives bug fixes or security patches from the PHP development team.
- RCE (Remote Code Execution): A critical vulnerability that allows an unauthorized attacker to execute arbitrary code or shell commands on the server.
- Socket: A Unix domain socket (
/run/php/php8.4-fpm.sock), an inter-process communication endpoint on the local filesystem connecting Nginx to PHP-FPM.
Quick Copy-Paste Patch Runbook (Targeted Upgrade)
For developers needing to apply the October 2026 security patch immediately on an Ubuntu 24.04 server running PHP 8.4, execute these commands in sequence (Ubuntu 24.04 production runbook):
# 1. Create secure backup directory and record pre-upgrade version list for rollback safety
sudo install -d -m 700 -o "$USER" -g "$USER" /backup
dpkg-query -W -f='${db:Status-Abbrev} ${Package}=${Version}\n' 'php8.4*' | awk '/^ii/{print $2}' > /backup/php-versions-before-$(date +%F-%H%M).txt
# 2. Update repository package lists and inspect available PHP updates
sudo apt-get update
apt list --upgradable 'php8.4*'
# 3. Perform a dry-run check of the exact targeted upgrade command
sudo apt-get install --only-upgrade --dry-run $(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.4*' | awk '/^ii/{print $2}')
# 4. Apply targeted upgrade strictly to installed PHP packages (avoids broad system changes)
sudo apt-get install --only-upgrade $(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.4*' | awk '/^ii/{print $2}')
# 5. Test PHP-FPM configuration syntax and gracefully reload workers (minimal downtime)
sudo php-fpm8.4 -t && sudo systemctl reload php8.4-fpm
# 6. Verify the active master process is executing the updated binary (no "(deleted)" file handle)
sudo ls -l /proc/$(systemctl show -p MainPID --value php8.4-fpm)/exe
# 7. Gracefully restart background queue workers as the web user (avoids root-owned cache files)
cd /var/www/your-app && sudo -u www-data php artisan queue:restart
# Or if using a named Supervisor worker pool:
# sudo supervisorctl restart laravel-worker:*
# 8. Clear and rebuild application caches once as the web user (avoids root-owned cache files)
cd /var/www/your-app && sudo -u www-data php artisan optimize:clear && sudo -u www-data php artisan optimize
# For Symfony applications:
# cd /var/www/your-app && sudo -u www-data php bin/console cache:clear --env=prod
# 9. Smoke test CLI database connection and web homepage response
cd /var/www/your-app && sudo -u www-data php artisan db:show
curl -s -o /dev/null -w "HTTP Status: %{http_code}\n" https://yourdomain.com/
For comprehensive explanations, diagnostic reasoning, and rollback procedures, continue reading the detailed guide below.
Pre-Upgrade Assessment
Before modifying packages on a server, document your existing configuration. This establishes a baseline to verify against after the upgrade and provides an exact rollback target.
1. Confirm Installed CLI and FPM Versions
What it does: Displays the active version for both CLI and FPM binaries to verify they are currently aligned.
php -v
php-fpm8.4 -v
Expected output (example, your versions and timestamps will differ):
PHP 8.4.23 (cli) ...
PHP 8.4.23 (fpm-fcgi) ...
What to do if output differs: If CLI and FPM show different minor versions before upgrading, determine which version your Nginx configuration uses before proceeding.
2. Identify Configured PHP-FPM Socket in Nginx
What it does: Searches Nginx server configurations to identify the exact FastCGI socket in use. A Unix domain socket is an inter-process communication endpoint on the local filesystem that connects Nginx to PHP-FPM.
grep -r "fastcgi_pass" /etc/nginx/sites-enabled/
Expected output (example, your versions and timestamps will differ):
/etc/nginx/sites-enabled/default: fastcgi_pass unix:/run/php/php8.4-fpm.sock;
What to do if output differs: If the socket path points to an older version (e.g., php8.3-fpm.sock), note that a minor upgrade requires updating this directive, whereas a patch upgrade (e.g. 8.4.23 to 8.4.27) keeps the same socket path.
3. Inventory Installed PHP Packages Accurately
What it does: Uses dpkg-query to output only currently installed (ii) packages, filtering out residual configuration files from previously uninstalled packages (rc). (See the section "Inventory and Upgrade the Required Packages").
dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.4*' | awk '/^ii/{print $2}'
Expected output (example, your versions and timestamps will differ):
php8.4-bcmath
php8.4-cli
php8.4-common
php8.4-curl
php8.4-fpm
php8.4-mbstring
php8.4-mysql
php8.4-opcache
php8.4-xml
php8.4-zip
What to do if output differs: Save this list. This command provides the exact package list used in targeted upgrades and rollback scripts.
4. Inspect Composer Dependencies and Platform Requirements
What it does: Validates that the active PHP runtime and extensions satisfy every dependency constraint defined in composer.lock.
cd /var/www/your-app && composer check-platform-reqs
Expected output (example, your versions and timestamps will differ):
Checking platform requirements for packages in the vendor dir
ext-curl 8.4.23 success
ext-mbstring 8.4.23 success
php 8.4.23 success
What to do if output differs: If any requirement fails, resolve the dependency constraint or missing extension before upgrading. Reference: Composer CLI Documentation.
composer check-platform-reqs, php -m, and CLI database/Redis commands execute strictly within the CLI environment (/usr/bin/php), not the PHP-FPM daemon (/usr/sbin/php-fpm8.4). Passing checks in the CLI confirm your terminal tools and queue workers will function, but they do not guarantee PHP-FPM has loaded the same extensions or pool directives. Always inspect FPM modules directly with sudo php-fpm8.4 -m.Staging-First Workflow
Production should never be the first environment where a PHP upgrade is deployed without prior validation. Follow this disciplined 10-step staging-first sequence:
- Verify Backups: Ensure database dumps and configuration archives exist before beginning.
- Mirror Production Environment: Staging must use the same Ubuntu version, same PHP minor version, and identical
composer.lock. - Verify Candidate Versions: Inspect candidate versions via
apt-cache policy(see the section "Safe Ubuntu and PHP Repository Handling"). - Execute Upgrade in Staging: Apply the targeted upgrade in staging first.
- Validate Runtimes: Confirm both CLI and FPM versions match the target patch level.
- Run Automated Test Suites: Execute unit and integration tests (
php artisan testor./vendor/bin/phpunit). - Test User Journeys: Manually test login, file uploads, external API integrations, and email dispatch.
- Inspect Error Logs: Review
/var/log/php8.4-fpm.logand/var/log/nginx/error.logfor warnings or stack traces. - Obtain Production Approval: Schedule the production maintenance window only after staging tests pass.
- Prepare Rollback Artifacts: Have your pre-upgrade version list file ready.
Safe Ubuntu and PHP Repository Handling
Understanding where your PHP packages come from is critical to maintaining system stability on Ubuntu.
Ondřej Surý PPA vs. Ubuntu Default Repositories
On Ubuntu 24.04 LTS (Noble Numbat), the stock operating system repository provides PHP 8.3. Stock Ubuntu packages follow version formatting such as 8.3.6-0ubuntu0.24.04.1 (check the exact version string on your system with apt-cache policy php8.3). Canonical maintains stock packages through backported security patches; the upstream minor version remains fixed, but security fixes are incorporated directly into the Ubuntu package release. You can inspect backported CVE fixes using:
apt changelog php8.3-cli | grep -i "CVE"
If you are running PHP 8.4 on Ubuntu 24.04, your server uses Ondřej Surý’s PPA (ppa:ondrej/php), a widely used third-party PPA. This PPA tracks upstream PHP releases directly, updating minor and patch versions as php.net releases them.
Inspect Candidate Versions via apt-cache policy
What it does: Displays the installed version, candidate version, and package source repository for your PHP packages. (This is the primary explanation; other sections link back here).
apt-cache policy php8.4-cli php8.4-fpm
Expected output (example, your versions and timestamps will differ):
php8.4-cli:
Installed: 8.4.23-1+ubuntu24.04.1+deb.sury.org+1
Candidate: 8.4.27-1+ubuntu24.04.1+deb.sury.org+1
Version table:
8.4.27-1+ubuntu24.04.1+deb.sury.org+1 500
500 https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble/main amd64 Packages
*** 8.4.23-1+ubuntu24.04.1+deb.sury.org+1 100
100 /var/lib/dpkg/status
What to do if output differs: If the Candidate version does not show the expected patch level (e.g. 8.4.27), run sudo apt-get update to refresh package indexes.
Reviewing the Transaction with a Dry Run
A dry run is a simulation mode that calculates package dependencies and displays proposed changes without altering the system.
What it does: Simulates the exact targeted upgrade command to verify which packages will be upgraded and ensure no unintended system changes occur.
sudo apt-get install --only-upgrade --dry-run $(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.4*' | awk '/^ii/{print $2}')
Expected output (example from local test environment, your versions and timestamps will differ):
Reading package lists... Done
Building dependency tree... Done
The following packages will be upgraded:
php8.4-cli php8.4-common php8.4-curl php8.4-fpm php8.4-mbstring php8.4-opcache
6 upgraded, 0 newly installed, 0 to remove.
Inst php8.4-cli [8.4.23-1+ubuntu24.04.1+deb.sury.org+1] (8.4.27-1+ubuntu24.04.1+deb.sury.org+1 ...)
Inst php8.4-common [8.4.23-1+ubuntu24.04.1+deb.sury.org+1] (8.4.27-1+ubuntu24.04.1+deb.sury.org+1 ...)
...
Conf php8.4-cli (...)
Conf php8.4-common (...)
What to do if output differs: Verify that only the intended php8.4-* packages are listed under upgraded packages, and that 0 to remove is confirmed. If any other packages are proposed for modification, review your package sources before proceeding.
Inventory and Upgrade the Required Packages
Accurate Package Inventory
The traditional command dpkg -l | grep php includes packages with rc status (packages removed whose configuration files remain). To get an exact list of active packages:
dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.4*' | awk '/^ii/{print $2}'
This ensures only active, installed packages are included in your upgrade transaction.
Updating vs. Upgrading
apt-get update: Downloads updated package lists from repositories. Does not install or change any software.apt-get install --only-upgrade <packages>: Upgrades only the specified packages if they are already installed. This is the recommended production default.apt-get upgrade: Upgrades all pending packages on the server. This is riskier on production because it can touch database services, system libraries, or kernel packages.
Backups and Rollback Planning
An upgrade without a verified rollback procedure is a gamble. Create these backups before performing any package updates.
1. Database Backup and Test Restore
Create a dedicated backup directory with restricted ownership and permissions:
sudo install -d -m 700 -o "$USER" -g "$USER" /backup
Define the backup target once and take a compressed database dump without --databases:
BACKUP=/backup/db-$(date +%Y%m%d-%H%M%S).sql.gz
sudo mysqldump --single-transaction --routines --triggers your_database_name | gzip > "$BACKUP"
sudo mysqldump works when MySQL's root account uses socket authentication (default auth_socket on Ubuntu with MySQL; unix_socket on MariaDB); otherwise use --defaults-extra-file with a chmod 600 file. Never provide database passwords directly on the command line.Verify that the backup is restorable by importing it into a temporary scratch database:
sudo mysql -e "CREATE DATABASE IF NOT EXISTS restore_test_db;"
gunzip -c "$BACKUP" | sudo mysql restore_test_db
sudo mysql -e "DROP DATABASE restore_test_db;"
--databases: a dump made with --databases contains CREATE DATABASE and USE statements that will switch execution back to your original database during a test restore, overwriting live production data.2. Application and Server Configuration Backup
When backing up .env files containing production secrets and database passwords, enforce strict filesystem permissions using umask 077:
# Enforce private file permissions for archives containing secrets
(umask 077 && tar czf /backup/app-config-$(date +%Y%m%d-%H%M%S).tar.gz \
-C /var/www/your-app .env composer.json composer.lock)
# Backup complete server configuration trees
sudo tar czf /backup/nginx-$(date +%F).tar.gz -C /etc nginx
sudo tar czf /backup/php8.4-$(date +%F).tar.gz -C /etc/php 8.4
/etc/nginx instead of copying sites-enabled: The /etc/nginx/sites-enabled/ directory contains symbolic links pointing to configuration files located in /etc/nginx/sites-available/. Copying symbolic links directly only duplicates symlinks (or fails to dereference them properly) without capturing the referenced virtual host files or the parent nginx.conf and conf.d/ snippets. Creating a tarball of /etc/nginx captures the complete configuration tree.3. Verify Backup Checksums
Before modifying system packages, calculate SHA-256 checksums of your backup artifacts on the server to ensure file integrity. Run the checksum inside /backup and scope it strictly to named files, excluding SHA256SUMS so reruns do not fail:
cd /backup
sha256sum "$(basename "$BACKUP")" app-config-*.tar.gz nginx-*.tar.gz php8.4-*.tar.gz > SHA256SUMS
sha256sum -c SHA256SUMS
Do not upload production database dumps or .env credential files to external web tools or remote services.
4. Record Pre-Upgrade Version List
Record the exact package versions currently active (filtered strictly to installed packages):
dpkg-query -W -f='${db:Status-Abbrev} ${Package}=${Version}\n' 'php8.4*' | awk '/^ii/{print $2}' > /backup/php-versions-before-$(date +%F-%H%M).txt
Rollback Limitations and Planning
Before upgrading, recognize the inherent limitations of package-level rollbacks:
- Third-Party Repository Retention: Older package versions may be removed or pruned from the third-party PPA over time as new upstream releases occur. If an older
.debrelease is no longer hosted on the remote mirror, APT cannot download it without a local package cache or mirror archive. - Database and Code Scope: Rolling back the PHP runtime packages reverts system binaries, but it does not roll back application database schema migrations, file storage uploads, or userland code deployed during the maintenance window.
- Infrastructure Snapshots: For complete, reliable disaster recovery, always take a full virtual machine (VM) snapshot or LVM storage snapshot prior to the maintenance window.
Rollback Procedure (If Required)
If you must revert to your pre-upgrade packages using the recorded version list file generated by dpkg-query, reference the specific version list file directly without shell globbing:
# 1. Target the exact pre-upgrade version list file (use the exact file you created before the upgrade)
VERSIONS=/backup/php-versions-before-2026-10-21-1300.txt
# 2. Downgrade packages to the recorded version list using --allow-downgrades
sudo apt-get install --allow-downgrades $(cat "$VERSIONS")
# 3. Hold packages via apt-mark to prevent automated apt upgrades from reinstalling the update
sudo apt-mark hold $(cut -d= -f1 "$VERSIONS")
# 4. Gracefully reload PHP-FPM to load the downgraded binary
sudo systemctl reload php8.4-fpm
When you have resolved the application incompatibility and are ready to apply the patch again, remove the package hold using the matching apt-mark unhold:
sudo apt-mark unhold $(cut -d= -f1 "$VERSIONS")
Perform the Update
In production environments, execute the targeted upgrade sequence:
Step 1: Update Repository Indexes
sudo apt-get update
Step 2: Execute Targeted Upgrade
What it does: Upgrades only the installed PHP 8.4 packages identified from our inventory command.
sudo apt-get install --only-upgrade $(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.4*' | awk '/^ii/{print $2}')
Expected output (example, your versions and timestamps will differ):
Reading package lists... Done
Building dependency tree... Done
The following packages will be upgraded:
php8.4-cli php8.4-common php8.4-curl php8.4-fpm php8.4-mbstring php8.4-opcache ...
X upgraded, 0 newly installed, 0 to remove.
Step 3: Validate PHP-FPM Configuration Syntax
What it does: Verifies that FPM configuration files and pool directives are syntactically valid before restarting the daemon.
sudo php-fpm8.4 -t
Expected output (example, your versions and timestamps will differ):
[21-Oct-2026 13:02:10] NOTICE: configuration file /etc/php/8.4/fpm/php-fpm.conf test is successful
Step 4: Gracefully Reload PHP-FPM (Minimal Downtime)
What it does: Sends SIGUSR2 to the PHP-FPM master process, instructing it to spawn new workers running the updated binary while allowing existing workers to complete in-flight HTTP requests.
sudo systemctl reload php8.4-fpm
Note on restart vs. reload: systemctl reload replaces worker processes gracefully without terminating existing TCP connections. If you modify socket parameters or core daemon directives, run sudo systemctl restart php8.4-fpm.
Post-Upgrade Validation
1. Verify Installed Binary Versions
php -v
php-fpm8.4 -v
Both should report the target version (e.g. 8.4.27).
2. Verify the Running Master Process Binary
What it does: Inspects /proc for the running PHP-FPM master PID to confirm that the active process in memory matches the updated on-disk executable.
sudo ls -l /proc/$(systemctl show -p MainPID --value php8.4-fpm)/exe
Expected output (example, your versions and timestamps will differ):
lrwxrwxrwx 1 root root 0 Oct 21 13:05 /proc/12345/exe -> /usr/sbin/php-fpm8.4
What to do if output differs: If the path ends with (deleted), the master process is still executing the old binary from unlinked memory. Execute sudo systemctl restart php8.4-fpm to complete the transition.
Note on service restarts: On Ubuntu systems, the needrestart package may automatically prompt or restart services during apt upgrades. Always perform this /proc check to verify that the active running master PID is executing the new binary.
3. Gracefully Restart Queue Workers
Background queue workers (such as Laravel queue workers or Symfony messenger daemons) hold the PHP binary in memory. They do not automatically pick up a new PHP binary when FPM reloads.
-
For Laravel Applications: Execute the restart command as the web user from your application directory:
cd /var/www/your-app && sudo -u www-data php artisan queue:restart # If using Laravel Horizon: # cd /var/www/your-app && sudo -u www-data php artisan horizon:terminateRunning
artisanaswww-dataavoids generating root-owned files or logs instorage/.queue:restartinstructs workers to exit gracefully after completing their current job. Supervisor will then restart them on the new PHP binary. -
For Supervisor Worker Pools: Avoid running
supervisorctl restart all, which kills non-PHP processes and terminates in-flight jobs. Instead, target the specific worker group:sudo supervisorctl restart laravel-worker:*Verify worker logs using the
stdout_logfilepath from your Supervisor config (for example/var/log/supervisor/laravel-worker.log). Check active worker processes using:pgrep -af 'artisan queue'
4. Verify Database Connectivity and Rebuild Application Caches
Never put database passwords on the command line in PDO snippets; passwords entered in command-line arguments are visible in process tables (ps aux) and saved in shell history.
Use your framework's native environment test and rebuild application caches once:
# Laravel database inspection and cache rebuild (run as www-data to avoid root-owned cache files)
cd /var/www/your-app && sudo -u www-data php artisan db:show
cd /var/www/your-app && sudo -u www-data php artisan optimize:clear && sudo -u www-data php artisan optimize
# For Symfony applications:
cd /var/www/your-app && sudo -u www-data php bin/console cache:clear --env=prod
Diagnose Post-Upgrade Errors
When an unexpected error arises after upgrading PHP or reloading services, use this diagnostic reference table to identify the cause, check the system state, and apply the correct remediation:
| Symptom | Likely Cause | Check | Fix |
|---|---|---|---|
| HTTP 500 Internal Server Error | Fatal PHP error, syntax error, or unhandled exception in application code following upgrade. | Inspect Nginx and application error logs: sudo tail -n 50 /var/log/nginx/error.log and sudo tail -n 50 /var/www/your-app/storage/logs/laravel.log. |
Fix the unhandled exception, syntax incompatibility, or missing class in application code; clear stale framework caches. |
| HTTP 502 Bad Gateway (Wrong Socket) | FastCGI socket path mismatch between Nginx fastcgi_pass and PHP-FPM pool listen directive, or PHP-FPM service is not active. |
Check active sockets and Nginx directive: ls -la /run/php/*.sock and grep -r "fastcgi_pass" /etc/nginx/sites-enabled/. |
Align fastcgi_pass with the active socket path in /etc/nginx/sites-available/your-site, test with sudo nginx -t, and reload Nginx: sudo systemctl reload nginx. |
| Composer Platform Failure | Active PHP runtime or installed extensions do not satisfy version constraints in composer.lock. |
Run Composer platform requirements check: cd /var/www/your-app && composer check-platform-reqs. |
Install the missing extension package (e.g. sudo apt-get install php8.4-mbstring php8.4-xml), reload FPM, or update dependency constraints. |
Permission Denied in storage/ or bootstrap/cache/ |
Web server user (www-data) lacks write permissions to runtime directories, or maintenance commands run as root generated root-owned cache files. |
Inspect directory permissions and ownership: ls -ld /var/www/your-app/storage /var/www/your-app/bootstrap/cache. |
Correct permissions strictly on runtime-writable folders: cd /var/www/your-app && sudo chown -R www-data:www-data storage bootstrap/cache && sudo chmod -R 775 storage bootstrap/cache. (See Least-Privilege Note below). |
| php.ini Differences (CLI vs. FPM) | CLI and FPM load separate configuration files (/etc/php/8.4/cli/php.ini vs. /etc/php/8.4/fpm/php.ini), resulting in mismatched memory_limit, upload_max_filesize, or loaded modules. |
Compare loaded INI files: run php --ini (for CLI); for FPM run sudo php-fpm8.4 -i and look for 'Loaded Configuration File'. |
Apply matching configuration parameters to the respective php.ini or FPM pool configuration (/etc/php/8.4/fpm/pool.d/www.conf) and reload FPM. |
| SOAP/TLS Integration Failures | Missing php8.4-soap / php8.4-curl package, or updated TLS library defaults rejecting legacy remote API server ciphers. |
Check loaded extensions (php --ri soap, php --ri curl) and test handshake: curl -Iv https://remote-api-endpoint.com. |
Install required extensions (sudo apt-get install php8.4-soap php8.4-curl), reload FPM, or adjust client TLS cipher configurations if communicating with legacy endpoints. |
| "Table 'cache' doesn't exist" | Database cache table has not been created or migrations have not been run. This issue is not caused by a PHP upgrade. | Check database tables: sudo mysql -e "SHOW TABLES IN your_db LIKE 'cache';" or inspect cache.php driver setting. |
Do not run blanket migrations blindly in production. Check migration status with php artisan migrate:status first; create the cache table only (e.g. php artisan make:cache-table followed by migrating that specific migration file); note that production requires --force (cd /var/www/your-app && sudo -u www-data php artisan migrate --path=database/migrations/xxxx_create_cache_table.php --force). |
| Worker Execution on Stale Runtime | Queue workers or background processes hold the unlinked PHP binary in memory after FPM reloads. | Inspect worker process start times and commands: pgrep -af 'artisan queue'. |
Instruct workers to exit and respawn on the new binary: cd /var/www/your-app && sudo -u www-data php artisan queue:restart or sudo supervisorctl restart laravel-worker:*. |
chown -R www-data:www-data /var/www/your-app:
storage/: Stores application logs, user uploads, framework sessions, and compiled Blade templates generated by PHP-FPM at runtime.bootstrap/cache/: Stores the compiled service container, route cache, and package discovery manifests (packages.php,services.php).- A blanket
chown -R www-data:www-dataacross the entire application root makes sensitive code files,.envsecret configurations, and.gitmetadata writable by the web server process. If an attacker discovers an upload flaw or Local File Inclusion (LFI) vulnerability, write access to application code immediately escalates the defect into complete Remote Code Execution (RCE). Confining write privileges strictly tostorage/andbootstrap/cache/adheres to the principle of least privilege.
Application Cache and Redis Considerations
Cache management frequently intersects with PHP maintenance windows. Keep these architectural principles in mind:
Cache Layers Explained
| Cache Layer | What It Caches | Storage Mechanism | How to Reset Safely |
|---|---|---|---|
| OPcache | Precompiled PHP script bytecode | Shared system memory (shm) |
sudo systemctl reload php8.4-fpm or cachetool via Unix socket. |
| Framework Cache | Configuration, routes, views, container | Local filesystem (storage/framework/) |
cd /var/www/your-app && sudo -u www-data php artisan optimize:clear |
| Data Cache | Application objects, sessions, queries | Database or Redis | Managed via application cache API (Cache::flush()). |
OPcache Reset Mechanics
opcache_reset() called from the CLI only resets the memory pool of that single CLI process; it does not clear the OPcache of the PHP-FPM daemon.
To reset PHP-FPM's OPcache without downtime:
- Gracefully reload the service:
sudo systemctl reload php8.4-fpm - Or use Cachetool (gordalina/cachetool) communicating directly over the Unix domain socket (the socket must be accessible to your running user):
cachetool opcache:reset --fcgi=/run/php/php8.4-fpm.sock
Redis Testing (No Stray Keys)
If your application uses Redis, test the connection via CLI using a temporary key with a short time-to-live (TTL) and clean it up immediately:
php -r "
if (!extension_loaded('redis')) {
echo 'Redis extension missing' . PHP_EOL;
exit(1);
}
\$r = new Redis();
try {
\$r->connect('127.0.0.1', 6379, 1.5);
\$r->setex('health_check', 10, 'ok');
echo 'Redis check: ' . \$r->get('health_check') . PHP_EOL;
\$r->del('health_check');
} catch (Exception \$e) {
echo 'Redis error: ' . \$e->getMessage() . PHP_EOL;
}
"
The Redis Maintenance Rule
Installing the php8.4-redis extension during a PHP maintenance window is safe and standard practice. However, migrating your application's active cache driver (e.g. switching from database caching to Redis) should always be conducted as a separate maintenance window. If an issue emerges, isolating changes ensures you know immediately whether the runtime upgrade or the cache driver configuration is responsible.
Production Release Checklist
Pre-Upgrade
- Baseline recorded:
dpkg-query -W -f='${db:Status-Abbrev} ${Package}=${Version}\n' 'php8.4*' | awk '/^ii/{print $2}' > /backup/php-versions-before-$(date +%F-%H%M).txt - Database backup taken without
--databasesand restorable into scratch database (gunzip -c "$BACKUP" | sudo mysql restore_test_db) - Application configuration backed up with
umask 077and server configs backed up withtar czf - Candidate versions inspected with
apt-cache policy - Composer platform requirements validated (
cd /var/www/your-app && composer check-platform-reqs) - Staging upgrade validated successfully
Execution
- Package index updated (
sudo apt-get update) - Dry run verified with exact targeted upgrade command (
sudo apt-get install --only-upgrade --dry-run ...) - Targeted upgrade applied (
sudo apt-get install --only-upgrade $(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.4*' | awk '/^ii/{print $2}')) - PHP-FPM configuration verified (
sudo php-fpm8.4 -t) - PHP-FPM gracefully reloaded (
sudo systemctl reload php8.4-fpm)
Post-Upgrade
- CLI and FPM binary versions match target (
php -v,php-fpm8.4 -v) - Running process verified in
/proc(no(deleted)binary) - FPM and Nginx reporting
active(sudo systemctl is-active php8.4-fpm nginx) - Background workers restarted as web user (
cd /var/www/your-app && sudo -u www-data php artisan queue:restart) - Application caches cleared and rebuilt once (
cd /var/www/your-app && sudo -u www-data php artisan optimize:clear && sudo -u www-data php artisan optimize) - HTTPS web endpoints returning HTTP 200
- Error logs monitored (
sudo tail -f /var/log/php8.4-fpm.log)
Frequently Asked Questions (FAQ)
Should PHP be updated directly in production?
No. Production should never be the first environment where a runtime upgrade is attempted. Always execute and validate the upgrade on a staging environment that mirrors production hardware, operating system, and extension inventory. Apply to production only after automated tests pass and rollback procedures are verified.
Is restarting PHP-FPM enough after an upgrade?
Restarting or gracefully reloading PHP-FPM (sudo systemctl reload php8.4-fpm) ensures the web server uses the new PHP binary for incoming HTTP requests. However, it does not update background processes. You must also gracefully restart queue workers (cd /var/www/your-app && sudo -u www-data php artisan queue:restart), scheduled job runners, and long-running daemons; otherwise, they will continue executing on the old, unpatched in-memory binary until killed.
Why can the CLI and website use different PHP versions?
On Ubuntu, the PHP CLI (/usr/bin/php) and PHP-FPM (/usr/sbin/php-fpm8.4) are independent packages with separate binaries and configuration directories. The system php command may point to a default version configured by Debian alternatives, while Nginx forwards requests to a dedicated FPM pool socket. Always check both runtimes independently (php -v and php-fpm8.4 -v).
How do I roll back safely?
If an unexpected incompatibility occurs, revert to the pre-recorded version list file using APT's --allow-downgrades flag, lock the packages with apt-mark hold, and gracefully reload PHP-FPM:
# Target the exact pre-upgrade version list file (use the exact file you created before the upgrade)
VERSIONS=/backup/php-versions-before-2026-10-21-1300.txt
sudo apt-get install --allow-downgrades $(cat "$VERSIONS")
sudo apt-mark hold $(cut -d= -f1 "$VERSIONS")
sudo systemctl reload php8.4-fpm
Important Security Note: Rolling back below the fixed patch version re-opens both CVE-2026-109618 and CVE-2026-109619. The interim stopgaps (such as disabling session upload progress and enforcing memory limits) are partial and do not eliminate the underlying engine flaws. Always prefer fixing forward.
Will running composer update patch PHP runtime security vulnerabilities?
No. Running composer update or composer install only updates userland PHP packages inside your application's vendor/ directory (such as frameworks, ORMs, and third-party libraries). It has zero effect on the system-level PHP binary (/usr/bin/php or /usr/sbin/php-fpm8.4).
Engine-level security flaws — such as the October 2026 critical RCE vulnerabilities (CVE-2026-109618 and CVE-2026-109619, CVSS 9.2) affecting memory management and persistent worker states — reside in the compiled C runtime itself. This class of vulnerability is fully fixed only by upgrading the operating system PHP packages (apt-get install --only-upgrade php8.4-fpm php8.4-cli) and reloading PHP-FPM.
Should Redis be configured during a PHP upgrade?
No. Cache backend changes and PHP runtime upgrades are independent operations. Performing both in the same maintenance window introduces diagnostic ambiguity — if a failure occurs, you cannot easily determine whether it stems from the runtime update or cache store misconfiguration. Upgrade and verify PHP stability first; schedule cache backend migrations as a separate change window.
How long should production be monitored after deployment?
A minimum of 24 hours to capture issues triggered across peak traffic periods, hourly cron jobs, and nightly batch processing. For applications with weekly scheduled routines or billing cycles, 48 hours of close log monitoring is strongly recommended.
How can I prevent future PHP compatibility incidents?
- Subscribe to PHP security announcements via php.net/mailing-lists.php.
- Automate version monitoring to track installed packages against supported branches.
- Integrate
composer check-platform-reqsinto CI/CD pipelines to block incompatibilities before deployment. - Maintain a staging environment that faithfully mirrors production operating system and extension versions.
- Document your full extension inventory using automated
dpkg-queryreporting. - Plan proactive upgrades during active support windows rather than waiting for End of Life.
- Separate infrastructure runtime updates from application deployments into dedicated maintenance windows.
Conclusion
A PHP runtime upgrade does not need to be stressful. By taking pre-upgrade package version lists, performing targeted APT installations, testing configurations before reloading, and verifying running processes, you can keep your servers secure against critical vulnerabilities with minimal downtime.
For deeper architectural context on PHP runtime diagnostics, CLI vs FPM differences, and security incident analysis, read our companion guide: Understanding PHP Version Issues: Diagnosis, Risks, and What Developers Need to Know.
Sources and Official References
- PHP 8.4.27 Release Announcement: https://www.php.net/releases/8_4_27.php
- PHP 8.5.12 Release Announcement: https://www.php.net/releases/8_5_12.php
- PHP Core Security Advisories: https://github.com/php/php-src/security/advisories
- Tenable Security Analysis (Pre-Disclosure Exploitation FAQ): https://www.tenable.com/blog/php2shell-cve-2026-109618-cve-2026-109619-frequently-asked-questions-about-critical-php-remote
- The Hacker News Emergency Patches & Interim Mitigation Report: https://thehackernews.com/2026/10/php-issues-emergency-patches-for-two.html
- Composer check-platform-reqs CLI Reference: https://getcomposer.org/doc/03-cli.md#check-platform-reqs
- Ubuntu Launchpad Ondřej Surý PHP PPA: https://launchpad.net/~ondrej/+archive/ubuntu/php