Load Testing
Load testing answers a different question than Lighthouse: what happens when many users arrive at once?
What You Learn
- Whether caching behaves as expected under concurrency.
- Which layer fails first (CDN, web server, PHP workers, database).
- What "safe" capacity looks like for your real routes.
Test Types
| Type | What it is | When to run it |
|---|---|---|
| Smoke | small load to validate tooling | before every real run |
| Load | expected concurrency for 10+ minutes | after major optimizations |
| Stress | ramp until you see failure modes | when sizing infrastructure |
| Spike | sudden burst | when you expect campaign traffic |
| Soak | steady load for a long time | when you suspect leaks or degradation |
Tooling
Good defaults:
- k6 for scripted scenarios.
- ab for quick sanity checks.
- A paid external provider when you need geographically distributed load.
Run a Basic Test
caution
Do not run uncached stress tests against production unless you fully understand the risk. Use staging for origin-capacity tests.
Quick cached homepage test (ab)
ab -n 500 -c 50 https://example.com/
k6-sample-scenario.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 10 },
{ duration: '3m', target: 25 },
{ duration: '1m', target: 0 },
],
};
export default function () {
const pages = [
'https://example.com/',
'https://example.com/some-post/',
'https://example.com/?s=test',
];
const url = pages[Math.floor(Math.random() * pages.length)];
const res = http.get(url);
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}
Before You Test
- Make sure your test traffic will not trigger WAF/rate limits (or whitelist the test source).
- Monitor the origin during the run (CPU/RAM/disk, PHP workers, DB).
- Test from outside the origin network so latency is realistic.
Test Scenarios for WordPress
| Scenario | What to Test | Configuring the Test |
|---|---|---|
| Homepage (cached) | CDN + server cache effectiveness | Simple GET to homepage |
| Blog post (cached) | Content page delivery | GET to a published post |
| Search | Dynamic (uncacheable) request | GET /?s=test |
| WooCommerce product | Dynamic with cart/session | GET product page |
| Contact form submission | POST handling under load | POST to form endpoint |
| WP Admin login | Authentication under load | POST to /wp-login.php |
Interpreting Results
Focus on these signals:
- Tail latency grows fast while median stays OK: you are saturating something.
- Errors appear (429/5xx): you hit rate limits or worker limits.
- Throughput plateaus while latency climbs: the bottleneck is reached.
Common bottleneck hints:
| Symptom | Likely bottleneck | Next step |
|---|---|---|
| response time rises on uncached URLs | PHP/DB work | profile slow routes; reduce dynamic work |
| 5xx under concurrency | worker saturation | review PHP-FPM/LSAPI workers and queues |
| cache HIT collapses under test | cache bypass/misconfig | verify cookies, query strings, and cache rules |
Common Mistakes
| Mistake | What Happens | How to Fix |
|---|---|---|
| Testing from the same server | Network latency is 0, results are unrealistic | Test from a different machine or cloud |
| Testing only the homepage | Miss slow dynamic pages | Include search, login, and product pages |
| Running too few requests | Statistical noise dominates results | Run at least 500 requests per scenario |
| Ignoring server-side metrics | Cannot identify the bottleneck | Monitor CPU, RAM, and PHP workers during test |
| Testing without cache | Unrealistic results (production uses cache) | Test with cache enabled for real-world data |
| Ignoring error rate | High throughput but 10% errors looks "fast" | Always check error rate alongside speed |
Checklist
- Scenarios include at least one cached page and one uncached/dynamic route.
- Origin monitoring runs during the test.
- Test source is not blocked or rate-limited.
- Results and the exact test configuration are saved.