Exposure assessment ≠ compromise detection. This guide cannot determine whether a particular system was compromised. Verify the current advisory and your exact runtime.
Start safely: preserve evidence and narrow the change
This guide is for an existing PHP website that stopped working after a runtime change. It covers diagnosis and recovery; it does not identify a particular vulnerability, prove compromise or guarantee compatibility. An HTTP 500 or WordPress critical-error screen alone does not identify its cause.
Record the failed URL privately, the time and timezone, the previous and intended PHP builds, and the application release. Check which domains, replicas and background jobs changed. Reproduce on a staging copy with protected data before making another production change. Use a tested backup and maintenance or traffic-control plan when service is failing.
Do not publish phpinfo(), enable browser-visible stack traces, make logs public or paste credentials into a support form. If a rollback is necessary, review the security status of the previous runtime first. Restrict exposure while arranging a supported fix; a known vulnerable PHP build is not a safe public recovery target.
Match the symptom to evidence before changing configuration
The causes below are possibilities, not diagnoses. Correlate a fresh failed request with a private log entry and the exact deployment change. Status codes and screen messages alone are insufficient.
Scroll the table horizontally to compare the evidence and next actions.
| Symptom | Possible cause | Diagnostic evidence | Next action |
|---|---|---|---|
| HTTP 500 or blank response | Fatal application error or PHP startup failure | Matching PHP error with timestamp, file and error type | Reproduce in staging; address the named error before changing unrelated settings. |
| WordPress critical error | Incompatible plugin, theme, custom code or runtime dependency | Recovery Mode details or a private fatal-error entry naming the failing component | Check that component’s supported PHP range and test its update or replacement in staging. |
| Undefined function or class | Missing extension, dependency or autoloader | Exact symbol in the fatal error; extension inventory and vendor/autoload.php availability | Identify the symbol’s provider; restore the compatible dependency in the serving environment. |
| Composer platform error | PHP or extension requirements do not match the deployment | check-platform-reqs output for the relevant CLI binary and lock file | Build a compatible dependency/runtime combination; verify the web runtime separately. |
| Nginx 502 after an FPM change | Backend stopped, wrong address/socket or socket permission problem | Nginx error log plus the FPM pool’s listen setting and service state | Match fastcgi_pass to the intended running pool; resolve the specific connection error. |
| CLI works; website or jobs fail | Different binaries, INI files, extensions or long-running workers | Domain handler/pool mapping compared with CLI and worker configuration | Verify each execution path and replace or restart the affected workers through the normal release process. |
Identify the serving PHP runtime, not just the CLI version
Run these read-only commands only in an authorized shell or staging container. They describe the PHP executable selected by that shell. The mysqli inspection is an example: an absent module can produce a nonzero exit status; it is not a requirement for every PHP application.
Map the affected domain to its configured handler, FPM pool, container or Apache module. Compare that runtime’s build, configuration and extension list with the CLI. A successful php -v cannot establish what handles an HTTP request. Hosting panels may select PHP per domain, while cron and queue workers use another binary.
Use authenticated hosting controls or an existing protected diagnostic facility to verify the web runtime. In WordPress, Site Health → Info → Server can help when administration is accessible. Ask the host to confirm the assigned handler if it is not. Avoid creating a publicly reachable diagnostic script.
php -v
php --ini
php -m
php --ri mysqliFind private logs and investigate WordPress critical errors
Start with the host’s authenticated PHP/application logs and the web-server error log for the failed request. Their location depends on the provider, pool and logging configuration; there is no universal error.log path. Keep a timestamp, error type and minimal redacted excerpt. Logs may contain personal data, paths, tokens or database details, so keep originals private and retain them only as needed.
A WordPress Recovery Mode message may identify a failing plugin or theme and offer temporary administrative access. Keep its link private. A component named in a trace is a diagnostic lead, not proof that deleting it will safely restore the site. Check the first fatal error and the component’s documented PHP compatibility in a staging copy.
If existing logs are insufficient, the following WordPress configuration is for a controlled staging reproduction only. Edit existing constants rather than defining them twice, before the “stop editing” line in wp-config.php. Replace the example with an absolute path outside the public document root that the PHP worker can write to; do not make it world-writable. Confirm errors remain hidden, reproduce briefly, then restore the prior debugging settings and secure or remove the temporary log.
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', '/srv/private-logs/wordpress-upgrade.log');
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', '0');Resolve missing extensions and Composer platform mismatches
An undefined function can come from an absent extension; an undefined class can also mean a missing package or autoloader. Identify the exact symbol before installing anything. Compare the application’s requirements with extensions loaded by the serving runtime. Install or build the compatible extension for that PHP branch and deployment source, then replace or restart the appropriate workers. Do not copy an old extension binary into a new PHP installation.
For a trusted Composer project, run the following checks in its staging directory using the intended CLI runtime. They disable Composer plugins and scripts. validate checks the manifest and lock-file consistency; check-platform-reqs compares installed packages with the actual CLI platform, ignoring config.platform emulation. These checks do not verify the web handler, application behavior or security patch status.
If vendor packages are absent, use the separate --lock variant to inspect requirements from composer.lock. Use --no-dev only when evaluating a production dependency set. Do not bypass failures with --ignore-platform-reqs or run a blind composer update on production. Resolve constraints and build from the reviewed lock file through your normal release process, then test the application.
composer --no-plugins --no-scripts validate --no-check-publish
composer --no-plugins --no-scripts check-platform-reqs --no-devcomposer --no-plugins --no-scripts check-platform-reqs --lock --no-devTrace PHP-FPM service, socket and backend failures
For Nginx with PHP-FPM, match the active virtual host’s fastcgi_pass address or Unix socket to the intended FPM pool’s listen value. A connection-refused error suggests no accepting listener at that endpoint; “No such file” and “Permission denied” point to different socket checks. A 502 alone does not identify which problem occurred. Check the contemporaneous Nginx and FPM logs.
On a systemd-based Linux host, the example below applies only if a unit actually named php8.4-fpm is installed. Substitute the verified unit, or use your host’s or container platform’s service controls. Journal access may require authorized privileges. nginx -t checks configuration syntax and referenced files; it does not prove the backend is healthy. Inspect output privately.
For Unix sockets, check the parent directory and pool owner/group/mode or ACL against the web worker identity. Correct narrowly scoped permissions; never use chmod 777 as a repair. Keep FastCGI private: do not publish an FPM port to the internet. After a reviewed correction, use your platform’s tested restart or redeployment process and verify every instance.
systemctl status php8.4-fpm --no-pager
journalctl -u php8.4-fpm --since "30 minutes ago" --no-pager
sudo nginx -t- PHP manual: FPM listen, socket permissions and FastCGI security
- Nginx: FastCGI backend address and socket syntax
- systemd: journal filtering by unit and time
- systemd: service status reference
- Nginx: configuration test option
- Nginx: map the serving upstream and verify the update
- PHP-FPM: inventory pools and workers
- Ubuntu: check package source and the installed service
Lab demonstration 1: missing BCMath produces HTTP 500
This deliberately broken fixture demonstrates one missing-extension failure; it is not a customer case or a WordPress requirement. Both labs require Docker Engine 28 or later with the Compose plugin (version 2 or later) on a local machine using normal bridge networking. Older Docker releases have a documented localhost-publication isolation issue. Use a new empty directory reserved for this lab and create the three files below. Ports 18081 and 18082 must be free. The PHP development server is for this lab only, published on host loopback; never use it as a production server.
The “missing” service uses the base CLI image without BCMath. The corrected image builds that extension from PHP’s bundled source. Run the commands from the fixture directory and allow the services to start before requesting them. Inspect the private container logs for the fatal error rather than enabling error display.
Local verification on 24 October 2026 observed HTTP 500 and “Call to undefined function bcadd()” for the missing service, followed by HTTP 200 with body 3 for the corrected image. This demonstrates the dependency repair only; it does not establish compatibility or security of any real application. The down command removes this lab’s containers and network.
<?php
echo bcadd('1', '2', 0), "\n";
FROM php:8.4-cli@sha256:debadc2e36b857657846e2e952cbaf52702e55482dd3c2c43806f68d8c534a70
RUN docker-php-ext-install bcmath
WORKDIR /lab
COPY index.php .
CMD ["php", "-d", "display_errors=0", "-d", "log_errors=1", "-d", "error_log=/proc/self/fd/2", "-S", "0.0.0.0:8080", "-t", "/lab"]
services:
missing:
image: php:8.4-cli@sha256:debadc2e36b857657846e2e952cbaf52702e55482dd3c2c43806f68d8c534a70
ports: ["127.0.0.1:18081:8080"]
volumes: ["./index.php:/lab/index.php:ro"]
command: ["php", "-d", "display_errors=0", "-d", "log_errors=1", "-d", "error_log=/proc/self/fd/2", "-S", "0.0.0.0:8080", "-t", "/lab"]
fixed:
build: .
ports: ["127.0.0.1:18082:8080"]
docker compose -p php-upgrade-extension-lab up -d --build
curl -i http://127.0.0.1:18081/index.php
docker compose -p php-upgrade-extension-lab logs missing
curl -i http://127.0.0.1:18082/index.php
docker compose -p php-upgrade-extension-lab downLab demonstration 2: a wrong FPM backend port produces 502
Use a second new empty directory, separate from lab 1. Create the files below, including the nginx subdirectory. This Compose fixture publishes only Nginx on host loopback port 18083; FPM has no published host port. It intentionally points Nginx to port 9001 while this image’s FPM listener uses 9000. It is a synthetic connection failure, not an exploit demonstration.
Start the services and check that FPM is ready before requesting /index.php. The Nginx log should show a refused connection to port 9001. Copy the corrected configuration over the lab configuration, validate it and reload Nginx. Allow the reload to complete, then repeat the request. Do not copy these minimal fixtures over a production virtual host.
Local verification on 24 October 2026 observed HTTP 502 with a refused connection to port 9001, then HTTP 200 with body “recovered fpm-fcgi” after selecting port 9000. Other 502 causes need their own evidence. Stop the fixture with down when finished; do not expose FPM to make the test work.
<?php
echo 'recovered ', PHP_SAPI, "\n";
services:
php:
image: php:8.4-fpm@sha256:cc7cdfe4da7d46f6e69ac653e3d3396590bf51d6d6731854d01449d0b32b704c
volumes: ["./index.php:/var/www/html/index.php:ro"]
nginx:
image: nginx:stable-alpine@sha256:47928bf67eb62a1863cb08d9ecb52d3e704304ba614eaee17884204677702951
ports: ["127.0.0.1:18083:80"]
volumes:
- ./nginx:/etc/nginx/conf.d:ro
- ./index.php:/var/www/html/index.php:ro
depends_on: [php]
server {
listen 80;
location = /index.php {
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/index.php;
fastcgi_pass php:9001;
}
}
server {
listen 80;
location = /index.php {
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/index.php;
fastcgi_pass php:9000;
}
}
docker compose -p php-upgrade-backend-lab up -d
# Wait for PHP-FPM to report that it is ready to handle connections.
docker compose -p php-upgrade-backend-lab logs php
curl -i http://127.0.0.1:18083/index.php
docker compose -p php-upgrade-backend-lab logs nginx
cp fixed.conf nginx/default.conf
docker compose -p php-upgrade-backend-lab exec -T nginx nginx -t
docker compose -p php-upgrade-backend-lab exec -T nginx nginx -s reload
# Allow the reload to finish, then repeat the request.
curl -i http://127.0.0.1:18083/index.php
docker compose -p php-upgrade-backend-lab downVerify recovery across the application, not just the homepage
- Confirm the intended serving PHP build, required extensions and configuration on every domain, replica and pool. Check cron, queue workers and other long-running processes separately.
- Repeat the previously failing request and correlate it with fresh private logs. Check status, expected content and behavior; a cached HTTP 200 or an error page returning 200 is not sufficient evidence.
- Exercise login, sessions, forms, uploads, database-backed pages and scheduled work. Test checkout or other financial actions only through an approved sandbox or test flow; avoid duplicate customer actions.
- Monitor error rates, backend availability and failed jobs during the normal workload. Restore temporary debugging settings and remove diagnostic endpoints or files introduced during investigation.
- Record the resolved cause, changed build/configuration and verification evidence for the next release. Confirm security patch evidence separately. Successful recovery does not establish that a system has never been compromised.
When to request professional assistance
Ask for help when the serving runtime cannot be identified, an extension or dependency conflict blocks a supported PHP build, the backend failure persists, or you cannot safely test and roll back the change. Describe the symptom, hosting environment, change time and a short redacted error summary. Keep credentials, customer records and full logs out of the enquiry.
Request a scoped assessment or quote. Scope, availability, pricing and secure access are agreed before work begins. If you see evidence of an active intrusion, preserve evidence and use the separate emergency-help path rather than treating an upgrade rollback as incident recovery.
Lab test scope and reproducibility
Only the two synthetic Docker demonstrations above were executed for this guide. They do not test a real WordPress installation, hosting account, Composer project or customer website. The production diagnostic examples are documentation-verified examples, not commands run against your server. The fixtures pin the image digests used in local verification rather than relying on changing tags. Those digests are also recorded below. These identifiers are not a security endorsement or a current patch-status recommendation.
php:8.4-cli
sha256:debadc2e36b857657846e2e952cbaf52702e55482dd3c2c43806f68d8c534a70
php:8.4-fpm
sha256:cc7cdfe4da7d46f6e69ac653e3d3396590bf51d6d6731854d01449d0b32b704c
nginx:stable-alpine
sha256:47928bf67eb62a1863cb08d9ecb52d3e704304ba614eaee17884204677702951Need help recovering after a PHP upgrade?
Describe the failure, your hosting environment and the runtime change. Request help identifying the cause or planning a compatible migration. Keep credentials and confidential logs out of your enquiry.
Request a scoped assessment or quote; scope, availability, pricing and access are agreed separately before work begins.