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.

Post-upgrade symptoms, possible causes and safe next steps
SymptomPossible causeDiagnostic evidenceNext action
HTTP 500 or blank responseFatal application error or PHP startup failureMatching PHP error with timestamp, file and error typeReproduce in staging; address the named error before changing unrelated settings.
WordPress critical errorIncompatible plugin, theme, custom code or runtime dependencyRecovery Mode details or a private fatal-error entry naming the failing componentCheck that component’s supported PHP range and test its update or replacement in staging.
Undefined function or classMissing extension, dependency or autoloaderExact symbol in the fatal error; extension inventory and vendor/autoload.php availabilityIdentify the symbol’s provider; restore the compatible dependency in the serving environment.
Composer platform errorPHP or extension requirements do not match the deploymentcheck-platform-reqs output for the relevant CLI binary and lock fileBuild a compatible dependency/runtime combination; verify the web runtime separately.
Nginx 502 after an FPM changeBackend stopped, wrong address/socket or socket permission problemNginx error log plus the FPM pool’s listen setting and service stateMatch fastcgi_pass to the intended running pool; resolve the specific connection error.
CLI works; website or jobs failDifferent binaries, INI files, extensions or long-running workersDomain handler/pool mapping compared with CLI and worker configurationVerify 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.

Read-only CLI inventory — not proof of the web-serving runtime
php -v
php --ini
php -m
php --ri mysqli

Find 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.

Staging-only wp-config.php example — private path must be provisioned first
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 2 — trusted project with installed production dependencies
composer --no-plugins --no-scripts validate --no-check-publish
composer --no-plugins --no-scripts check-platform-reqs --no-dev
Alternative requirement check when vendor/ is absent
composer --no-plugins --no-scripts check-platform-reqs --lock --no-dev

Trace 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.

Read-only diagnostics — Linux systemd example, verified unit name required
systemctl status php8.4-fpm --no-pager
journalctl -u php8.4-fpm --since "30 minutes ago" --no-pager
sudo nginx -t

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.

Lab 1 file: index.php
<?php
echo bcadd('1', '2', 0), "\n";
Lab 1 file: Dockerfile
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"]
Lab 1 file: compose.yaml
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"]
Lab 1 commands — local fixture directory only
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 down

Lab 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.

Lab 2 file: index.php
<?php
echo 'recovered ', PHP_SAPI, "\n";
Lab 2 file: compose.yaml
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]
Lab 2 file: nginx/default.conf
server {
    listen 80;
    location = /index.php {
        include /etc/nginx/fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /var/www/html/index.php;
        fastcgi_pass php:9001;
    }
}
Lab 2 file: fixed.conf
server {
    listen 80;
    location = /index.php {
        include /etc/nginx/fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /var/www/html/index.php;
        fastcgi_pass php:9000;
    }
}
Lab 2 commands — separate local fixture directory only
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 down

Verify 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.

Image digests used for local verification on 24 October 2026
php:8.4-cli
sha256:debadc2e36b857657846e2e952cbaf52702e55482dd3c2c43806f68d8c534a70

php:8.4-fpm
sha256:cc7cdfe4da7d46f6e69ac653e3d3396590bf51d6d6731854d01449d0b32b704c

nginx:stable-alpine
sha256:47928bf67eb62a1863cb08d9ecb52d3e704304ba614eaee17884204677702951

Need 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.