TL;DR
Benchmarking health check endpoints is essential for validating application performance baselines, load balancer configuration, and Kubernetes readiness/liveness probe reliability. PestPHP’s built-in stress testing feature provides a developer-friendly way to run concurrent HTTP load tests without external tools like Apache Bench or k6. This guide walks you through identifying the symptoms, understanding the root cause, and implementing a production-tested fix with real code examples.
Symptoms
If you’re experiencing this issue, you’ll likely notice one or more of these signs in your application:
- Health endpoints returning 503 under moderate load
- Load balancer incorrectly marking healthy instances as unhealthy
- Kubernetes pods restarting due to slow liveness probe responses
- No baseline performance data for capacity planning
- Unclear if application can handle expected concurrent user load
- Performance regressions going undetected until production incidents occur
- Auto-scaling thresholds set based on guesses rather than measured data
If any of these symptoms look familiar, you’re dealing with pestphp stress testing for health endpoint benchmarking. Read on to understand why it happens and how to fix it properly.
Root Cause
Benchmarking health check endpoints is essential for validating application performance baselines, load balancer configuration, and Kubernetes readiness/liveness probe reliability. PestPHP’s built-in stress testing feature provides a developer-friendly way to run concurrent HTTP load tests without external tools like Apache Bench or k6. Understanding how to write stress tests, interpret results (TTFB, throughput, success rate), and set meaningful thresholds ensures your health endpoints are production-ready. Without benchmarks, you deploy blind — unable to predict how your application will behave under real user load, unable to set accurate auto-scaling thresholds, and unable to detect performance regressions introduced by code changes or dependency updates.
Understanding the root cause is critical before applying a fix — treating only the symptoms often leads to the problem resurfacing in a different form, especially under production load.
How to Fix
Follow these steps in order. Each step builds on the previous one, and skipping steps may result in an incomplete fix.
Step 1: Install PestPHP if not already present: composer require pestphp/pest —dev
Install PestPHP if not already present: composer require pestphp/pest —dev — the stress testing feature is included in the core PestPHP package starting from v2.0
Step 2: Create a stress test file in tests/Stress/ directory
Create a stress test file in tests/Stress/ directory — organize stress tests separately from unit and feature tests so they can be run independently in CI pipelines
Step 3: Define endpoint targets and concurrency levels matching your expected production load
Define endpoint targets and concurrency levels matching your expected production load — start with your health check endpoint at 10 concurrent connections, then scale up to match your expected peak traffic
Step 4: Run the stress test: ./vendor/bin/pest stress http://localhost:8000/up —concurrency=10 —duration=10 and capture the baseline results before making any changes
Run the stress test: ./vendor/bin/pest stress http://localhost:8000/up —concurrency=10 —duration=10 and capture the baseline results before making any changes
Step 5: Analyze the results carefully: TTFB P95 should be under 100ms for health endpoints, success rate must be 100%, and throughput should match your expected requests per second
Analyze the results carefully: TTFB P95 should be under 100ms for health endpoints, success rate must be 100%, and throughput should match your expected requests per second
Step 6: Set up CI pipeline stress tests to catch performance regressions before deploy
Set up CI pipeline stress tests to catch performance regressions before deploy — add the stress test command to your GitHub Actions or GitLab CI workflow with threshold assertions
Step 7: Compare results across different frameworks, configurations, and infrastructure setups to make informed architecture decisions backed by real data rather than assumptions
Compare results across different frameworks, configurations, and infrastructure setups to make informed architecture decisions backed by real data rather than assumptions
Code Example
The following before/after comparison shows the core pattern you need to change. Pay attention to the comments explaining why each change matters.
Before (Broken)
// No benchmark — deploying without performance data
// routes/web.php
Route::get('/up', function () {
return response('OK', 200);
});
After (Fixed)
// Stress test with PestPHP
// tests/Stress/HealthEndpointTest.php
use function Pest\Stressless\stress;
it('handles health check endpoint under load', function () {
$result = stress('http://localhost:8000/up')
->concurrently(10)
->for(10)->seconds();
expect($result->requests()->successRate())->toBe(100.0);
expect($result->requests()->duration()->med())->toBeLessThan(50); // median < 50ms
expect($result->requests()->duration()->p95())->toBeLessThan(100); // P95 < 100ms
});
it('handles database health check under load', function () {
$result = stress('http://localhost:8000/up/database')
->concurrently(5)
->for(10)->seconds();
expect($result->requests()->successRate())->toBe(100.0);
expect($result->requests()->duration()->p95())->toBeLessThan(200);
});
Key Takeaways
- Identify before you fix: Use the symptoms checklist above to confirm you’re dealing with this specific issue, not a different problem with similar error messages
- Test under load: Many of these issues only surface under concurrent access — always verify your fix with load testing before deploying to production
- Monitor after deployment: Set up alerting for the symptoms listed above so you catch regressions early
Difficulty Level
Intermediate
This guide assumes familiarity with the framework and its core tooling. You should understand basic database concepts, configuration patterns, and be comfortable reading framework source code when needed. Prior experience with similar issues will help but is not required.
Frequently Asked Questions
How does PestPHP stress testing compare to Apache Bench or k6?
PestPHP stress tests integrate directly into your test suite and use PHP’s familiar assertion syntax. They’re ideal for CI/CD regression checks and developer workflows. For production load testing at scale (10,000+ concurrent users), use dedicated tools like k6, Locust, or Gatling. PestPHP is best for baseline benchmarks and automated regression detection.
What is a good TTFB threshold for health endpoints?
For health check endpoints: P50 under 10ms, P95 under 50ms, P99 under 100ms. If your health endpoint queries the database, add 20-50ms to each threshold. Load balancers typically have health check timeouts of 5-30 seconds, so anything under 1 second is functional — but faster is better for accurate load distribution.