k6 Per-Stage Thresholds to Abort Failing Load Tests Early
Use k6 0.55+ per-stage thresholds to stop load tests when latency or error limits are breached during a specific phase.
18 Oct 2025, 00:12 UTC

Load tests that run to completion hide failures until the end. If your API degrades after two minutes of a twenty-minute soak, you still pay for the remaining minutes and delay the CI signal. The useful takeaway is to enforce per-stage thresholds in k6: when a metric such as response time or error rate crosses a limit during a defined load phase, k6 stops the test and returns a non-zero exit code.
This note covers k6 version 0.55 or later, where thresholds can be scoped to tagged execution stages and are evaluated after each iteration. It assumes you are running HTTP load tests and can modify the test script. Verify the behavior in your environment before relying on it in CI.
Requirements
- k6 version 0.55 or later.
- Threshold metrics limited to
http_req_duration,http_req_rate, orhttp_req_status. - A mechanism to tag requests so thresholds apply to the correct stage.
Smallest Suitable Design: Tagged Stages
The smallest design maps each load stage to a tag and applies thresholds only to metrics carrying that tag. k6 stages do not automatically tag metrics, so the script must attach the stage tag to each request.
export const options = {
stages: [
{ duration: '1m', target: 20 },
{ duration: '5m', target: 100 },
{ duration: '1m', target: 0 },
],
thresholds: {
'http_req_duration{stage:steady-state}': ['p(95) < 500'],
'http_req_failed{stage:steady-state}': ['rate < 0.05'],
},
};
export default function () {
http.get('https://api.example.com/data', {
tags: { stage: __ENV.STAGE_TAG || 'steady-state' },
});
}
Pass STAGE_TAG=steady-state when executing the steady-state portion, or drive tagging from an external orchestrator. k6 does not copy stage names into metric tags automatically.
Trust and Data Boundaries
Thresholds run inside the k6 process and evaluate metrics after each iteration. The trust boundary is the test script: if a request omits the tag, the threshold rule does not apply to that iteration. The data boundary is the metric stream; thresholds inspect counters and timers collected by k6, not response bodies or external systems.
Operational Checks
- Create a script that calls an endpoint you control and adds
sleep(2)to force a slow response. - Set
'http_req_duration{stage:steady-state}': ['p(95) < 500']. - Run
k6 run script.jsfrom a terminal with read access to the script and outbound network access to the target host. - Confirm the process exits with a non-zero code after the first iteration in the tagged stage, rather than completing the full duration.
Risk: a deliberately slow endpoint holds virtual users for the sleep duration, so keep the diagnostic test short.
Failure Modes
- Premature termination: a threshold set too tightly aborts the test before the system reaches steady state, masking issues that appear only under sustained load.
- Metric overhead: evaluating thresholds after every iteration adds CPU cost. At very high request rates this can slightly skew the latency it is meant to measure.
- Silent misses: requests without the matching tag are excluded from the threshold, so a mis-tagged request can fail without triggering an abort.
- Unsupported metrics: custom metrics and non-HTTP timers require additional scripting; only
http_req_duration,http_req_rate, andhttp_req_statusare supported for threshold rules.
Conditions That Would Change the Design
- If you need thresholds on custom business metrics, add a custom metric and threshold rule, or move evaluation to a post-test analysis step.
- If you run k6 versions earlier than 0.50, per-iteration threshold evaluation is not available; use a global threshold and inspect results after completion.
- If the test runs at thousands of iterations per second, measure whether threshold evaluation affects response times before enabling per-stage aborts.
Practical Verification
After applying thresholds, run the test against a staging endpoint with known-good latency. Then introduce a 500 ms artificial delay on that endpoint and confirm the test exits early with a non-zero code. If it does not, review the tag names in the threshold and request objects for mismatches.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.