The go-to resource for learning PHP, Laravel, Symfony, and your dependencies.

Monitoring Performance After PHP Upgrades


Upgrading your PHP version typically brings enhanced security, new language features, and performance improvements. But we’ve all seen upgrades that didn’t go as planned—even carefully tested deployments can introduce unexpected performance bottlenecks or regressions. That’s why a systematic monitoring strategy becomes crucial during the post-upgrade period. In this post, we’ll walk through how to effectively monitor your application’s performance after a PHP upgrade, giving you the data you need to confirm success or catch problems early.

Why Monitor? The Risks of Flying Blind

You might wonder: can’t we just test thoroughly before upgrading and call it a day? That’s a reasonable question. Production environments have complexities that often don’t appear in staging—real traffic patterns, actual data volumes, concurrency from dozens or hundreds of simultaneous users. One may wonder: aren’t automated tests and staging environments sufficient? The answer is that both are necessary but not sufficient on their own. Skipping post-upgrade monitoring means operating blind to the actual performance characteristics of your application under real load.

Without systematic monitoring, several classes of issues can slip through:

  • Performance regressions: A function or library may behave differently in the new PHP version under specific conditions, leading to slower response times that only emerge with real production data.
  • Increased resource usage: Changes in memory management or opcode caching can cause higher CPU or memory consumption—costs that directly impact your infrastructure budget.
  • New errors: Deprecated functions or subtle breaking changes might trigger warnings or errors that your test suite never exercised. Of course, even with excellent test coverage, production represents the final validation.

Proactive monitoring catches these issues early, often before users notice them. This gives you time to investigate and remediate while maintaining application stability and a positive user experience.

Step 1: Establish a Performance Baseline

We cannot know if performance has changed if we don’t know what it was. Before touching the upgrade process, we must collect baseline metrics from our production environment. Think of this as your single source of truth—a reference point against which every post-upgrade measurement will be compared.

Key baseline metrics to record, with practical collection methods:

  • Average response time: The typical time your server needs to process a request. For a standard Laravel or Symfony application, we might see averages between 100ms and 500ms under moderate load. Use application_performance_monitoring tools or nginx logs: awk '$7 ~ /200/ {sum+=$NF; count++} END {print "Avg:", sum/count "ms"}' access.log.

  • Peak response time (95th and 99th percentile): These highlight worst-case performance—the experience of your slowest users. Averages can hide problems; the 99th percentile might reveal that 1% of requests take 3-5 seconds even when the average is 200ms. Most APM tools provide these percentiles automatically; otherwise, consider using hdr-histogram libraries or manually sorting response times.

  • CPU and memory usage: Average resource consumption under typical load. Track these at the system level with vmstat 1 or sar, and at the process level with ps aux --sort=-%mem. For PHP-FPM, monitor pm.status URLs if available: curl http://localhost/status?json. Establish your normal ranges—perhaps 40-60% CPU average with occasional spikes to 80%. Memory leaks often show as gradually increasing RSS values in ps output.

  • Application throughput (requests per minute): How many requests your application handles. This contextualizes everything else: if throughput drops while response times increase, you have a capacity problem. Calculate from logs: grep ' 200 ' access.log | wc -l divided by minutes in your collection period. Your baseline might be 5,000 RPM on a Tuesday morning.

  • Error rate: Frequency of application errors and HTTP 5xx status codes. Even a 0.1% error rate might be thousands of users. Monitor both application exceptions and web server error logs: grep -c ' 500 ' access.log and tail -f /var/log/php-fpm/error.log.

Collect this data over a representative period—typically a full business cycle of 5-7 days—to capture normal traffic variations. A weekend baseline differs from a weekday. Document the collection methodology so you can replicate it after the upgrade. One may wonder: what if we don’t have historical monitoring? Start now. Even a few days of baseline data is better than none.

Step 2: Key Metrics to Monitor Post-Upgrade

After deploying the PHP upgrade, we compare the new performance data against our baseline. Let’s examine what to watch and what it might indicate.

One may wonder: how much change is significant? That depends on your application, but we’ll provide concrete thresholds. A 10% improvement or degradation in response time usually warrants investigation. Larger changes—20% or more—are immediate red flags.

Response time: Is the average faster, slower, or unchanged? We might expect modest improvements from a newer PHP version due to opcache optimizations and JIT compilation (in PHP 8.0+). But beware: a 5% improvement might be statistical noise; a 30% slowdown signals a regression. Pay special attention to the 95th and 99th percentiles—these often reveal problems hidden by averages. For example, if your 99th percentile jumps from 800ms to 2,500ms, some users are experiencing severe slowness even if the average looks fine.

CPU load: Newer PHP versions typically bring efficiency gains, so we might reasonably expect a slight CPU decrease—perhaps 5-15%—assuming the same workload. A sudden spike, especially if correlated with specific endpoints, indicates a process consuming excess resources. One common cause: opcache misconfiguration after an upgrade. Check if you see CPU rise steadily over hours without recovery—this suggests a memory leak forcing more garbage collection.

Memory consumption: PHP 7.x dramatically reduced memory usage compared to PHP 5.x, but regressions can still occur. Monitor for memory that grows continuously without leveling off—this suggests a leak. Tools like memory_get_peak_usage() in development or top/htop in production help. Of course, some increase is normal as caches warm up, but a steady climb over days is concerning.

Error logs: Scrutinize error logs for new warnings, notices, or fatal errors. PHP upgrades often introduce deprecation notices for functions that will be removed. For instance, upgrading from PHP 7.4 to 8.0 typically surfaces htmlspecialchars() charset parameter deprecations. We’ll want to address these proactively before they become fatal in future versions. Use tail -f /var/log/php-fpm/error.log or centralized logging with grep -i deprecation.

Database performance: Though not PHP-specific, PHP upgrades can affect database driver behavior. Watch query execution times. A change in mysqli or PDO behavior might affect prepared statements or connection pooling.

Opcode cache (opcache) metrics: If you use opcache—and you should—monitor its hit rate after upgrading. A drop below 95% suggests misconfiguration or incompatibility. Check via opcache_get_status() or your APM tool.

Step 3: Essential Monitoring Tools

No single tool gives us the complete picture. A combination provides comprehensive visibility into our application’s health. Let’s examine the categories, their trade-offs, and practical examples.

Application Performance Monitoring (APM)

APM tools offer deep insights: transaction tracing, database query analysis, and error aggregation. They typically instrument your code via PHP extensions or agents, capturing timing data, call stacks, and context.

New Relic is a mature commercial solution. Its PHP agent integrates seamlessly, providing detailed transaction traces down to the database query level. For example, we can see that a particular controller action spends 300ms in database queries and 150ms in external API calls. The trade-off: cost. New Relic pricing starts around $150/month per host for Pro, though they offer free tiers for small applications. If you already use New Relic for other services, integrating PHP is straightforward.

Datadog provides full-stack observability, unifying APM, infrastructure metrics, logs, and synthetic tests. Their PHP tracing library (DD Trace) works with OpenTelemetry standards. The advantage: correlation across systems. If your PHP application calls a Python microservice, Datadog traces span both. The learning curve is steeper than point solutions.

Tideways (formerly XHProf) focuses on PHP performance profiling with lower overhead than some competitors. It’s popular in the PHP community for its targeted approach. Their hosted service simplifies setup. Tideways tends to be more affordable than New Relic for smaller teams.

Sentry excels at error tracking with performance monitoring (they call it “Performance Issues”). While not as feature-rich as full APM platforms for deep drilling, Sentry’s strength is surfacing exceptions and slow transactions with stack traces. If your primary concern is catching new errors after upgrades, Sentry alone might suffice. Pricing is usage-based.

OpenTelemetry PHP represents an open standard gaining traction. You can instrument your application once and send data to multiple backends (Jaeger, Prometheus, commercial APMs). The trade-off: more setup complexity. We typically recommend OpenTelemetry for teams committed to vendor neutrality or those already using it in other services.

Which should we choose? If budget allows and you need deep traces, New Relic or Datadog are comprehensive. For PHP-focused profiling, Tideways is excellent. To catch errors with performance context, Sentry works well. For teams wanting flexibility, OpenTelemetry avoids lock-in. Of course, we can also combine tools—Sentry for errors plus Prometheus for metrics.

Server and Log Monitoring

APM covers application-level details, but we also need system-level visibility.

Prometheus & Grafana form a powerful open-source combo. We scrape metrics from PHP-FPM’s status page, collect system metrics with node_exporter, and visualize in Grafana. For example, we can create a dashboard showing request rate, response time, and PHP-FPM queue length in one view. The learning curve: Prometheus uses its own query language (PromQL), and setup requires configuring scrapes and alert rules. But the cost is just infrastructure time.

ELK Stack (Elasticsearch, Logstash, Kibana) aggregates logs from multiple sources. We can ship PHP-FPM logs, Nginx access logs, and application logs into a searchable index. After an upgrade, we might query for all “deprecated” warnings across a time range: message:"Deprecated" AND @timestamp:[now-24h TO now]. The complexity: ELK requires significant resources and maintenance. Many teams now prefer lighter alternatives like Loki or structured logging to cloud services.

Simple utilities: Don’t overlook htop, vmstat 1, iostat 1, and netstat. These give immediate real-time views. For quick checks during an upgrade, htop shows CPU and memory per PHP-FPM process. vmstat identifies I/O bottlenecks that might correlate with PHP’s increased disk activity for opcache.

Benchmarking Tools

To create controlled load for comparison—especially in staging before production—we need load testing tools.

ApacheBench (ab) is simple but limited. It’s installed on most systems via apt-get install apache2-utils or yum install httpd-tools. Example: ab -n 1000 -c 50 http://localhost/. However, ab lacks modern HTTP features and can’t model complex user journeys.

wrk is a modern, high-performance tool written in C with Lua scripting. It can generate substantial load on a single machine. A typical command: wrk -t12 -c400 -d30s --latency http://localhost/. The -t12 uses 12 threads, -c400 maintains 400 concurrent connections, and --latency reports latency distribution. wrk scripts allow custom request generation—for instance, logging in, adding to cart, checking out. We can compare PHP 8.1 results to PHP 8.2 directly.

Siege offers a middle ground: easy to use with some configuration options. Configure a URL list in $HOME/.siegerc for realistic scenarios.

Locust (Python) and k6 (Go) provide programmable load testing with scenarios and thresholds. These are better for simulating real user behavior with think times and varied endpoints. For example, k6 scripts can model 70% reads and 30% writes with specific endpoints.

Gatling (Scala) generates beautiful reports but requires writing scenarios in Scala or Kotlin.

For PHP upgrade testing, we recommend wrk for raw throughput numbers and k6 or Locust for realistic multi-step scenarios. Run the same benchmark before and after the upgrade, ideally on identical staging environments. If you don’t have staging parity, use production-like environments as close as possible.

Practical Setup Examples

Let’s walk through concrete configurations for common monitoring scenarios. These examples show actual commands and configurations you can adapt.

Example 1: PHP-FPM Status Page Setup

First, we’ll enable the PHP-FPM status page to monitor queue length and active processes. Edit your PHP-FPM pool configuration (typically /etc/php/8.2/fpm/pool.d/www.conf):

pm.status_path = /status
ping.path = /ping

Then in your web server configuration (Nginx example):

location ~ ^/(status|ping)$ {
    access_log off;
    allow 127.0.0.1;
    deny all;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
}

After reloading PHP-FPM (systemctl reload php8.2-fpm), query the status:

$ curl http://localhost/status?json
{"pool":"www","process manager":"dynamic","start time":1702727681,"start since":86670,"accepted conn":1024309,"listen queue":0,"max listen queue":0,"listen queue len":0,"idle processes":5,"active processes":8,"total processes":13,"max active processes":257,"active children":8,"total children":13,"slow requests":0}

Key fields: active processes shows currently executing requests; listen queue indicates requests waiting for a worker; slow requests is incremented if request_slowlog_timeout is set. A persistently non-zero queue suggests you need more PHP-FPM workers.

Example 2: Setting Up a Basic Grafana Dashboard with Prometheus

We’ll scrape PHP-FPM metrics and system metrics into Prometheus, then visualize in Grafana.

Prometheus configuration (/etc/prometheus/prometheus.yml):

scrape_configs:
  - job_name: 'php-fpm'
    static_configs:
      - targets: ['localhost:9253']
  - job_name: 'node'
    static_configs:
      - targets: ['localhost:9100']

Exporter setup: Install php-fpm_exporter (Go binary) and run it pointing to your status URL:

$ ./php-fpm_exporter --phpfpm.status-url=http://127.0.0.1/status?json

Grafana panel query example to show request rate derived from PHP-FPM metrics:

sum(rate(phpfpm_processes_active[5m]))

For response time, you’ll typically pull from your APM or application logs.

Example 3: Using New Relic’s PHP Agent

Install the agent:

$ sudo apt-get install newrelic-php5
$ sudo newrelic-install install
$ sudo systemctl restart php8.2-fpm

Configuration goes in /etc/newrelic.ini. Important settings:

newrelic.enabled = true
newrelic.appname = "My Application"
newrelic.distributed_tracing_enabled = true
newrelic.transaction_tracer.enabled = true
newrelic.transaction_tracer.record_sql = "obfuscated"

After deployment, check the New Relic UI for transaction traces. One may wonder: does this add overhead? Typically 1-3% CPU overhead, which is acceptable for production monitoring.

Example 4: Quick Command Cheatsheet

Here are commands we run post-upgrade to check key indicators:

# Check PHP-FPM process counts and memory
$ systemctl status php8.2-fpm
$ ps aux | grep php-fpm | awk '{sum+=$6} END {print "Total RSS (KB):", sum}'

# Monitor error logs for deprecations and fatal errors in real-time
$ tail -f /var/log/php8.2-fpm.log | grep -i -E "(deprecated|fatal|error)"

# Sample recent response times from Nginx logs (assuming $request_time in log format)
$ awk '$7 ~ /200/ {print $NF}' /var/log/nginx/access.log | sort -n | uniq -c | tail -20

# Check opcache hit rate via PHP CLI
$ php -r ' $status = opcache_get_status(); echo "Hit rate: " . ($status["opcache_statistics"]["hits"] / ($status["opcache_statistics"]["hits"] + $status["opcache_statistics"]["misses"]) * 100) . "%\n"; '

If the opcache hit rate is below 95%, investigate opcache.memory_consumption and opcache.max_accelerated_files settings.

Example 5: Benchmarking with wrk

We’ll create a simple benchmark script that mimics typical traffic. Write benchmark.lua:

request = function()
    return wrk.format("GET", "/")
end

response = function(status, headers, body)
    if status >= 400 then
        io.write("ERROR: ", status, "\n")
    end
end

Run the benchmark before upgrade and save output:

$ wrk -t4 -c100 -d30s --latency -s benchmark.lua http://localhost/ > baseline.txt

After upgrade, run the same command and compare:

$ wrk -t4 -c100 -d30s --latency -s benchmark.lua http://localhost/ > post_upgrade.txt

Compare the “Requests/sec” and latency percentiles. A 20% drop in requests per second generally indicates a meaningful regression.

Common Pitfalls and Troubleshooting

A few problems recur across PHP upgrades. Knowing these in advance helps us respond quickly.

Opcode cache confusion: After upgrading PHP, you must restart PHP-FPM to clear the old opcache. Old bytecode from PHP 8.1 will not run on PHP 8.2. The error might manifest as “Cannot redeclare class” or “Function not found” even though the code hasn’t changed. Solution: systemctl restart php8.2-fpm and clear any APC or OPcache shared memory.

Extension compatibility: A PHP extension compiled for PHP 8.1 will crash PHP 8.2. Check php -m output for missing modules. Reinstall extensions via PECL or your package manager. Errors might appear in the PHP-FPM log: PHP Warning: Unable to load dynamic library.

Configuration migration: php.ini settings change between versions. Some directives are removed or renamed. Compare your old php.ini with the new version’s sample (/etc/php/8.2/fpm/php.ini-production). The php -i command shows the active configuration; diff it between versions.

JIT compilation surprises (PHP 8.0+): The JIT can improve performance for CPU-bound code but might hurt I/O-bound web applications. If response times increase, try disabling JIT (opcache.jit=0) and retest. The JIT’s effect varies widely by application type.

Memory limit issues: PHP 8.x is generally more memory-efficient, but some applications see increases due to changed internal structures. If you encounter “Allowed memory size exhausted” errors, check if your script legitimately needs more memory or if a leak emerged. Temporarily increase memory_limit to confirm, then investigate.

Composer dependencies: Always run composer install or composer update with the new PHP version to ensure dependencies are compatible. Watch for platform checks in composer.json that might prevent upgrades. Remove config.platform.php overrides if they’re blocking.

One may wonder: what if we don’t catch a regression immediately? That’s where alerting comes in—set thresholds and get notified before users flood support. We’ll discuss alerting strategies in a future post, but briefly: configure alerts on response time increase >20%, error rate >0.5%, or CPU >85% sustained.

Conclusion

A successful PHP upgrade doesn’t end after deployment. Systematic performance monitoring is the crucial final step to validate the upgrade and safeguard your application’s stability.

By following these guidelines, we can ensure our PHP upgrade delivers the expected improvements without compromising user experience:

  1. Benchmark first: Always establish a performance baseline before upgrading.
  2. Monitor key metrics: Keep a close eye on response times, resource usage, and error rates.
  3. Use the right tools: Leverage APM, logging, and benchmarking tools for a complete picture.
  4. Know common pitfalls: Be ready for opcache issues, extension incompatibilities, and configuration changes.

A data-driven monitoring approach transforms a hopeful upgrade into a confident success. The effort pays off in reduced downtime, better performance insights, and the ability to roll back with data to justify the decision if needed.

Sponsored by Durable Programming

Need help with your PHP application? Durable Programming specializes in maintaining, upgrading, and securing PHP applications.

Hire Durable Programming