Load Test Before Launch, Not After
09 Oct 2026The first time most people load test their app is during the launch, live, with real users as the test harness. Twenty minutes with k6 before that happens tells you where things break, at what traffic level, and with what error.
Install k6
brew install k6
# or, on Linux:
sudo apt-get install k6
No account, no cloud dependency required for the basic use — it runs entirely local against whatever URL you point it at.
Write a script
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 20 }, // ramp to 20 virtual users
{ duration: '1m', target: 20 }, // hold at 20
{ duration: '30s', target: 100 }, // ramp to 100
{ duration: '1m', target: 100 }, // hold at 100
{ duration: '30s', target: 0 }, // ramp down
],
};
export default function () {
const res = http.get('https://your-app.example.com/');
check(res, {
'status is 200': (r) => r.status === 200,
'responded under 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
Ramping instead of jumping straight to 100 users matters: a flat 100 from second one can trigger connection-pool warmup issues that have nothing to do with your actual capacity, and you’ll misread the result.
Run it
k6 run load-test.js
Reading the output
k6 prints a summary at the end. The numbers that matter:
http_req_duration..............: avg=142ms min=38ms med=98ms max=2.1s p(90)=310ms p(95)=480ms
http_req_failed.................: 2.31% ✗ 46 ✓ 1954
- p(95) — the number you should quote, not
avg. It means 95% of requests were faster than this. If p(95) is 480ms but avg is 142ms, a meaningful chunk of your users are having a much worse experience than the average suggests. - http_req_failed — your error rate under load. Anything above 0 at 20 concurrent users on a simple GET is worth investigating before you even get to the higher stage.
Watch where the failure rate climbs relative to the stage timeline — if errors start right as you hit the 100-VU stage, that’s your rough ceiling, not some abstract “it can handle traffic” claim.
What actually breaks, and where to look
| Symptom | Likely cause | Where to check |
|---|---|---|
| Errors start at a specific VU count | Connection pool or worker count maxed out | App server config (e.g. Gunicorn workers, Node cluster size) |
| Latency climbs steadily, no errors | Database connection pool exhausted, queries queuing | DB slow query log, connection count |
| 502/504 from the errors | Reverse proxy timeout shorter than backend response time | Nginx/Caddy proxy_read_timeout |
| Everything fine until disk fills mid-test | Logging or temp files from the test itself | Disk usage during the run, not just after |
Test against staging, not production, first
Run your first pass against a staging environment that mirrors production sizing. If you don’t have one, run the test during a low-traffic window and start with a lower target VU count than you think you need — a load test that takes down production because you skipped ramping is a worse outcome than not testing at all.
What “good” looks like
There’s no universal target — it depends on your actual expected traffic. But the exercise itself is the point: run this once before every launch or major infra change, and you’ll have last week’s numbers to compare against instead of finding out live whether this deploy made things worse.