Resource library

QA How-To

How to Use k6 to Load Test HTTP3 Endpoints (2026)

Learn to k6 load test HTTP3 endpoints safely, verify QUIC separately, add checks and thresholds, model traffic, and interpret protocol-aware results well.

22 min read | 2,825 words

TL;DR

Use k6 to apply realistic load to the HTTPS routes behind an HTTP/3-enabled service, and use an HTTP/3-capable client such as curl to verify QUIC negotiation. Stock k6 v2 does not originate HTTP/3, so its latency numbers describe the negotiated fallback protocol and backend behavior, not QUIC performance.

Key Takeaways

  • Stock k6 can test an HTTP/3-enabled origin, but its HTTP client does not generate or validate HTTP/3 traffic.
  • Use curl with --http3-only before and after a k6 run to prove that QUIC is reachable on UDP port 443.
  • Treat k6 results as application and fallback-path capacity data, not as QUIC transport benchmarks.
  • Tag stable endpoint names and enforce checks plus percentile thresholds so failures stop the run.
  • Separate warm connection measurements from cold connection experiments because their costs differ.
  • Correlate client results with CDN, load balancer, QUIC, and application telemetry before drawing conclusions.

To k6 load test http3 endpoints correctly in 2026, separate two questions: can the application sustain the expected workload, and can clients actually negotiate HTTP/3 over QUIC? Stock k6 is excellent for the first question, but its k6/http client does not currently originate HTTP/3 traffic. It normally reaches an HTTP/3-enabled HTTPS origin over HTTP/2 or HTTP/1.1.

That limitation does not make k6 useless. The HTTP/3 edge usually forwards requests into the same cache, gateway, service, and database that k6 can exercise. You can build a strong capacity test with k6, verify QUIC independently with an HTTP/3-capable curl build, and correlate both with server telemetry. What you must not do is label k6's http_req_duration as an HTTP/3 transport measurement.

This tutorial builds that honest two-part workflow. For broader foundations, review the complete k6 performance engineering guide and the k6 load testing tutorial.

What You Will Build

You will create a small, repeatable test kit that:

  • Probes an endpoint with curl --http3-only and fails if QUIC cannot be used.
  • Runs a smoke test against the same route with k6 checks.
  • Applies a staged, arrival-rate workload with endpoint-specific thresholds.
  • Records the protocol k6 actually negotiated instead of assuming it was HTTP/3.
  • Produces a machine-readable k6 summary for CI.
  • Rechecks HTTP/3 after load so an edge regression is not hidden by fallback.

The example targets https://quickpizza.grafana.com, which you can replace through BASE_URL. Your production target should be an authorized test environment with representative data and monitoring. Never direct a load test at a third-party or production service without written approval and agreed stop conditions.

Prerequisites

Use these exact baseline versions for the walkthrough:

  • k6 v2.0.0 or a later compatible v2 release. Confirm with k6 version.
  • curl 8.6.0 or later, compiled with HTTP/3 support. Confirm that the Features: line includes HTTP3.
  • Bash 5.2 or later for the verification commands.
  • A target with TLS 1.3, UDP port 443 reachable, and HTTP/3 enabled at its CDN, reverse proxy, or server.
  • Permission to generate the proposed request rate.

Install k6 using the official package for your operating system. On macOS with Homebrew, run:

brew install k6 curl

Homebrew's curl may not replace Apple's system curl. Locate the installed binary and inspect it explicitly:

K6_BIN="$(command -v k6)"
CURL_BIN="$(brew --prefix curl)/bin/curl"
"$K6_BIN" version
"$CURL_BIN" --version

Verify: k6 prints a v2 version, and curl's feature list contains HTTP3. If HTTP3 is absent, that binary cannot perform the strict protocol probe used below. Installing a newer curl version alone is insufficient if its build lacks an HTTP/3 backend.

Tool Role in this workflow What it proves What it does not prove
k6 k6/http Generates controlled application traffic Status, content, latency, error rate, and capacity over its negotiated protocol HTTP/3 negotiation or QUIC-specific behavior
curl --http3-only Performs strict protocol probes The client can reach the endpoint using HTTP/3 Sustained multi-user capacity
Edge and origin telemetry Explains server-side behavior QUIC handshakes, protocol mix, saturation, cache, and application health User-visible correctness by itself

This division is essential. A successful HTTPS response can silently arrive over HTTP/2 after HTTP/3 fails unless you force the protocol in the probe.

Step 1: Establish an HTTP/3-Only Baseline

Start with a strict preflight. The command below refuses HTTP/2 and HTTP/1.1 fallback, prints the negotiated HTTP version, and discards the response body.

CURL_BIN="$(brew --prefix curl)/bin/curl"
BASE_URL="${BASE_URL:-https://quickpizza.grafana.com}"
"$CURL_BIN" --http3-only --silent --show-error --output /dev/null \
  --write-out 'status=%{http_code} version=%{http_version} remote=%{remote_ip} time=%{time_total}\n' \
  "$BASE_URL/api/ratings"

Expected output has the form status=200 version=3 remote=... time=.... The exact duration and address depend on your location, DNS, CDN, and connection state. Do not copy a sample time into an SLO. Establish acceptable values from your own production-like environment.

An HTTP/3 endpoint often advertises support with an Alt-Svc response header, but that header is only a discovery signal. It does not prove that UDP traffic reaches the server or that a QUIC handshake completes. The strict request proves actual use for this client and network path.

Verify: run the command twice. Both attempts must exit with code 0 and report version=3. If the first succeeds and the second fails, inspect edge logs and network policy before adding load.

Step 2: Create a k6 Smoke Test and Record the Real Protocol

Create smoke.js. This script validates status and JSON content, then checks the protocol k6 actually used. The protocol assertion intentionally accepts the fallback protocols supported by k6 rather than pretending to require HTTP/3.

import http from 'k6/http';
import { check, fail } from 'k6';

const BASE_URL = __ENV.BASE_URL || 'https://quickpizza.grafana.com';

export const options = {
  vus: 1,
  iterations: 1,
  thresholds: {
    checks: ['rate==1'],
    http_req_failed: ['rate==0'],
  },
};

export default function () {
  const response = http.get(`${BASE_URL}/api/ratings`, {
    tags: { name: 'GET /api/ratings' },
    timeout: '10s',
  });

  const valid = check(response, {
    'ratings returns 200': (r) => r.status === 200,
    'ratings is JSON': (r) =>
      String(r.headers['Content-Type'] || '').includes('application/json'),
    'k6 reports its negotiated protocol': (r) =>
      r.proto === 'HTTP/2.0' || r.proto === 'HTTP/1.1',
  });

  console.log(`k6 negotiated ${response.proto}`);
  if (!valid) fail('Smoke validation failed');
}

Run it against the same base URL used by curl:

BASE_URL="${BASE_URL:-https://quickpizza.grafana.com}" \
k6 run smoke.js

The Response.proto value is direct evidence about k6's connection. It is not inherited from the server's feature list and should never be rewritten in a report. If it says HTTP/2.0, your test exercised HTTP/2.

Verify: the summary reports one iteration, zero failed HTTP requests, and a 100 percent check rate. The log reports HTTP/2.0 or HTTP/1.1, not HTTP/3. If content-type differs on your API, replace that assertion with a stable contract field rather than deleting response validation.

Step 3: Model the Endpoint Workload

A single virtual-user loop can produce a rate that changes when the service slows down. For an endpoint capacity experiment, use ramping-arrival-rate so the requested iteration rate follows a schedule independently of response time. Save this as load.js.

import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter, Rate } from 'k6/metrics';

const BASE_URL = __ENV.BASE_URL || 'https://quickpizza.grafana.com';
const EXPECTED_PROTO = __ENV.EXPECTED_K6_PROTO || 'HTTP/2.0';

const protocolMismatch = new Counter('protocol_mismatch');
const businessFailure = new Rate('business_failure');

export const options = {
  scenarios: {
    ratings_read: {
      executor: 'ramping-arrival-rate',
      startRate: 2,
      timeUnit: '1s',
      preAllocatedVUs: 20,
      maxVUs: 100,
      stages: [
        { target: 10, duration: '1m' },
        { target: 10, duration: '3m' },
        { target: 25, duration: '2m' },
        { target: 25, duration: '3m' },
        { target: 0, duration: '1m' },
      ],
      gracefulStop: '30s',
      exec: 'readRatings',
      tags: { workload: 'ratings-read' },
    },
  },
  thresholds: {
    'http_req_failed{name:GET /api/ratings}': ['rate<0.01'],
    'http_req_duration{name:GET /api/ratings}': [
      'p(95)<500',
      'p(99)<1000',
    ],
    checks: ['rate>0.99'],
    business_failure: ['rate<0.01'],
    protocol_mismatch: ['count==0'],
    dropped_iterations: ['count==0'],
  },
};

export function readRatings() {
  const response = http.get(`${BASE_URL}/api/ratings`, {
    headers: { Accept: 'application/json' },
    tags: { name: 'GET /api/ratings' },
    timeout: '10s',
  });

  const protocolIsExpected = response.proto === EXPECTED_PROTO;
  if (!protocolIsExpected) protocolMismatch.add(1);

  const ok = check(response, {
    'status is 200': (r) => r.status === 200,
    'body is nonempty': (r) => r.body !== null && r.body.length > 2,
    'protocol matches baseline': () => protocolIsExpected,
  });

  businessFailure.add(!ok);
  sleep(0.1);
}

The example thresholds are tutorial starting points, not universal targets. Replace 500 ms, 1000 ms, 1 percent, and the request-rate stages with values derived from your SLO and traffic data. dropped_iterations is particularly important with an open model: a nonzero value means k6 could not start all scheduled iterations, often because maxVUs or generator capacity was insufficient.

Verify: execute a short syntax and behavior check before the full schedule:

BASE_URL="${BASE_URL:-https://quickpizza.grafana.com}" \
EXPECTED_K6_PROTO="HTTP/2.0" \
k6 run --duration 10s --vus 2 load.js

The CLI override replaces the scenario for this quick check. Confirm there are no JavaScript exceptions, tags appear in the threshold names, and the observed protocol matches your smoke baseline.

Step 4: k6 Load Test HTTP3 Endpoints Without Mislabeling Results

Run the complete schedule only after the smoke test and protocol probe pass. Capture a JSON summary for CI review.

BASE_URL="${BASE_URL:-https://quickpizza.grafana.com}" \
EXPECTED_K6_PROTO="${EXPECTED_K6_PROTO:-HTTP/2.0}" \
k6 run --summary-export k6-summary.json load.js

Watch four client-side signals during the test. http_req_failed captures failed HTTP requests according to k6's response classification. Checks validate content and expected protocol. http_req_duration describes request sending, server processing, and response receiving, but does not include every connection setup cost. dropped_iterations exposes demand the generator failed to launch.

At the same time, monitor the HTTP/3 termination layer and origin. Useful server-side signals include requests by negotiated protocol, QUIC handshake failures, UDP packet loss, active connections, connection migrations, 0-RTT acceptance if enabled, edge-to-origin protocol, cache hit ratio, upstream latency, CPU, memory, queue depth, and dependency saturation. Metric names differ across Cloudflare, Fastly, Envoy, NGINX, HAProxy, managed load balancers, and custom QUIC stacks, so use the names exported by your platform.

Do not compare this run directly with an HTTP/3 browser trace and claim the difference is caused by QUIC. The clients may differ in DNS cache, TLS session state, connection reuse, congestion control, request concurrency, geographic path, and content behavior. Use k6 here to establish backend capacity under controlled demand. Use a protocol-capable load generator when the research question is transport-specific.

Verify: the command exits 0 only when all thresholds pass. Confirm k6-summary.json exists and contains metrics for http_req_duration, checks, business_failure, protocol_mismatch, and dropped_iterations. A passing result also requires the monitoring window to show no hidden origin or edge saturation.

Step 5: Recheck HTTP/3 During and After Load

A preflight alone can miss failures triggered by resource pressure. Open a second terminal while k6 is running and execute a bounded series of strict probes:

CURL_BIN="$(brew --prefix curl)/bin/curl"
BASE_URL="${BASE_URL:-https://quickpizza.grafana.com}"
for attempt in 1 2 3 4 5; do
  "$CURL_BIN" --http3-only --silent --show-error --output /dev/null \
    --max-time 10 \
    --write-out "attempt=$attempt status=%{http_code} version=%{http_version} total=%{time_total}\n" \
    "$BASE_URL/api/ratings" || exit 1
  sleep 2
done

This is a canary, not a QUIC load generator. Its purpose is to catch complete loss of HTTP/3 availability while k6 stresses the shared application path. Five successful requests do not establish a QUIC latency percentile or loss curve.

After k6 finishes, rerun the same loop. Compare the probe timestamps with edge protocol metrics and the k6 stage transitions. If HTTP/3 probes fail only near peak demand while k6 remains healthy over HTTP/2, suspect resource pools or limits specific to the QUIC termination layer. If both fail together, inspect shared edge, upstream, and dependency saturation.

Verify: every line reports status=200 version=3, and the loop returns exit code 0. Preserve failed stderr output in CI because messages such as connection timeout and unsupported protocol point to very different causes.

Step 6: Test Cold and Warm Paths Separately

HTTP/3's connection behavior makes test boundaries important. A warm scenario reuses established state and emphasizes request processing. A cold scenario includes more handshake and connection setup work. Mixing them into one percentile produces a number that is hard to interpret.

For a warm application-capacity test, keep k6's default connection reuse. That is what load.js does. For a diagnostic k6 fallback run that discourages reuse, invoke:

BASE_URL="${BASE_URL:-https://quickpizza.grafana.com}" \
EXPECTED_K6_PROTO="${EXPECTED_K6_PROTO:-HTTP/2.0}" \
k6 run --no-connection-reuse --duration 30s --vus 2 load.js

This option affects k6's own HTTP connections. It still does not turn them into QUIC. Keep the rate low because repeated connection establishment changes both client resource use and server load. Compare http_req_connecting, http_req_tls_handshaking, and http_req_blocked with the normal run, but remember that reused connections legitimately report zero setup time on later requests.

For actual HTTP/3 cold-path research, use a QUIC-capable tool that exposes connection controls and metrics, then validate its behavior with packet captures and server telemetry. Do not disable certificate verification simply to make a test pass. A handshake that bypasses trust checks is not representative of a normal client and can hide deployment errors.

Verify: label the diagnostic result k6 fallback cold-connection experiment. Confirm the protocol remains the expected k6 protocol and that the run is stored separately from the warm baseline.

Step 7: Add the Workflow to CI

CI should fail for three distinct reasons: HTTP/3 is unavailable, application behavior is wrong, or performance thresholds are breached. Keep those outputs separate so responders know where to begin. A minimal Bash job is:

set -euo pipefail

: "${BASE_URL:?Set BASE_URL to an authorized environment}"
CURL_BIN="${CURL_BIN:-curl}"
EXPECTED_K6_PROTO="${EXPECTED_K6_PROTO:-HTTP/2.0}"

"$CURL_BIN" --http3-only --fail --silent --show-error \
  --output /dev/null "$BASE_URL/api/ratings"

BASE_URL="$BASE_URL" EXPECTED_K6_PROTO="$EXPECTED_K6_PROTO" \
  k6 run --summary-export k6-summary.json load.js

"$CURL_BIN" --http3-only --fail --silent --show-error \
  --output /dev/null "$BASE_URL/api/ratings"

Use a small smoke or baseline profile on pull requests. Schedule larger tests in a controlled environment where noisy neighbors, data resets, autoscaling, and observability are understood. Store the k6 summary, logs, target release identifier, region, test configuration, and server dashboard snapshot as one result set.

Performance gates should be stable enough to guide engineering decisions. A threshold copied from this article without traffic analysis will produce either false confidence or noisy failures. Start with a service SLO, translate it into endpoint-specific latency and error budgets, then calibrate rates against recent representative traffic. The k6 thresholds and checks guide provides a deeper gating strategy, while k6 scenarios and executors helps choose the right workload model.

Verify: deliberately set EXPECTED_K6_PROTO=HTTP/1.1 when the baseline is HTTP/2 and confirm the job fails on protocol_mismatch. Restore the correct value, then test the HTTP/3 probe failure path against a controlled endpoint without QUIC. Never sabotage a shared environment to test the pipeline.

How to Interpret k6 Load Test HTTP3 Endpoints Results

Use precise labels in the report. A suitable result title is: Application capacity test for HTTP/3-enabled endpoints, traffic generated by k6 over HTTP/2. That sentence captures the target feature and the actual client transport.

Separate conclusions by evidence:

Observation Defensible conclusion Conclusion to avoid
k6 thresholds pass over HTTP/2 The tested application path met the defined workload criteria via HTTP/2 HTTP/3 meets the same latency percentiles
Strict curl probes report version 3 HTTP/3 was reachable from that client location at probe times All users can reach HTTP/3
QUIC request share falls under load HTTP/3 use changed during the test window The application caused the change without further correlation
k6 dropped iterations increase The generator could not schedule all requested work The server rejected every missing iteration
Origin latency rises for all protocols A shared upstream bottleneck is plausible QUIC is necessarily the bottleneck

Protocol selection also affects connection counts and concurrency. HTTP/2 multiplexes streams over TCP, while HTTP/3 multiplexes streams over QUIC and avoids TCP-level head-of-line blocking between streams. That architectural difference matters most under realistic network loss and mobility. A data-center k6 run with a clean path cannot simulate those transport effects merely by targeting the same hostname.

If you need protocol-to-protocol evidence, control request content, geographic path, loss, latency, connection state, and offered load. Use independent runs, randomized ordering, enough repetitions, and confidence intervals appropriate to your decision. Pair client measurements with edge logs that prove the negotiated protocol for each cohort.

Best Practices

  • Name the transport honestly. Record Response.proto from k6 and HTTP version from curl. Never infer protocol from the hostname or Alt-Svc alone.
  • Keep protocol checks strict. Use --http3-only, not a preference flag that silently falls back when QUIC is broken.
  • Tag logical endpoints. Stable name tags prevent dynamic IDs from creating high-cardinality metrics and make per-route thresholds reliable.
  • Validate content under load. A fast 200 response with an error object, stale cache entry, or empty payload is a failure.
  • Use an open model for arrival demand. Arrival-rate executors reveal whether the system can serve the intended rate without coupling demand to response speed.
  • Watch the generator. CPU exhaustion, network limits, insufficient VUs, and dropped iterations can make a server look better than it is.
  • Define abort criteria. Stop for data corruption, sustained severe errors, dependency risk, or breach of an agreed production safety limit.
  • Preserve test context. Record build SHA, configuration, dataset, region, protocol evidence, and monitoring links with every result.

For tests that need multiple generators, read distributed k6 testing with the Kubernetes Operator. Distribution adds capacity, but it also adds clock, network, and result-aggregation considerations.

Troubleshooting

Problem: curl says the installed build does not support HTTP/3. -> Inspect curl --version. Use a binary whose Features: line contains HTTP3, and call that exact path in local scripts and CI. Do not treat an Alt-Svc header fetched over HTTP/2 as an equivalent verification.

Problem: --http3-only times out while ordinary HTTPS succeeds. -> Check outbound and inbound UDP 443, firewall policy, load balancer listeners, DNS records, CDN HTTP/3 settings, and QUIC server logs. Ordinary HTTPS probably succeeded through TCP fallback, which is precisely what the strict probe prevents.

Problem: k6 logs HTTP/1.1 instead of the expected HTTP/2.0. -> Confirm ALPN and HTTP/2 are enabled on the route reached by the k6 host. Inspect proxies, TLS termination, and redirects. Set EXPECTED_K6_PROTO only after investigation; changing it to silence the threshold hides a deployment change.

Problem: dropped_iterations is nonzero. -> Increase preAllocatedVUs or maxVUs after checking generator CPU and memory, reduce scripted think time, or lower the scheduled rate. A dropped iteration never reached the endpoint, so exclude it from claims about server throughput.

Problem: latency is low but business checks fail. -> Inspect response bodies, cache behavior, test data, authentication, and downstream errors. HTTP transport success is not application correctness. Keep business failures as a separate metric so status-only summaries cannot hide them.

Problem: HTTP/3 probes fail only during peak k6 load. -> Align timestamps with QUIC handshake, UDP drop, edge CPU, connection-limit, origin latency, and cache metrics. Repeat at a lower rate to locate the onset. The correlation narrows the investigation but does not by itself prove whether the fault sits in QUIC termination or a shared dependency.

Interview Questions and Answers

The strongest interview answer starts with the limitation: stock k6 does not generate HTTP/3. Then explain the two-part workflow, evidence boundaries, workload model, thresholds, and observability. The model answers in the structured section below cover protocol verification, arrival-rate executors, connection reuse, metrics, and reporting.

A senior candidate should also challenge an ambiguous request to "benchmark HTTP/3 with k6." Ask whether the team wants backend capacity for an HTTP/3-enabled service or a comparison of QUIC against TCP transports. Those goals need different clients, controls, and conclusions.

Where To Go Next

You now have a defensible workflow: strict HTTP/3 probes establish QUIC reachability, k6 generates controlled load against the shared application path, and telemetry connects client symptoms to edge and origin behavior. Keep those evidence streams separate in dashboards and reports.

Deepen the implementation with these verified resources:

When the requirement becomes a true QUIC transport benchmark, choose a load generator with documented HTTP/3 support and prove its wire behavior before trusting its numbers. Keep the k6 suite as the application-capacity baseline, because it remains valuable for detecting shared backend regressions and for continuous performance gates.

Interview Questions and Answers

How would you use k6 to test a service that supports HTTP/3?

I would separate application capacity from protocol validation. k6 would drive realistic load against the shared HTTPS routes, with checks, endpoint tags, arrival-rate scenarios, and thresholds. An HTTP/3-capable client would perform strict QUIC probes, while edge and origin telemetry would show protocol mix and saturation.

Why is Alt-Svc not enough to prove HTTP/3 works?

Alt-Svc advertises that an alternative service is available, but the response carrying it may arrive over HTTP/2. UDP 443 can still be blocked or the QUIC handshake can fail. A strict HTTP/3 request plus server evidence proves actual negotiation more reliably.

What does response.proto tell you in a k6 test?

It reports the protocol used for that k6 HTTP response, such as HTTP/2.0 or HTTP/1.1. I use it as an assertion and report field so a proxy or ALPN change cannot silently alter the test path. It does not report server capabilities that the client did not use.

Why choose ramping-arrival-rate for this endpoint test?

It schedules iterations at a target rate rather than waiting for each virtual-user loop to finish. That preserves offered demand as latency changes and makes capacity limits easier to observe. I also threshold dropped iterations because they indicate the generator did not launch all planned work.

How do warm and cold connection tests differ?

Warm tests reuse connection state and emphasize steady request processing. Cold tests repeatedly incur more setup work, which can expose handshake or connection limits but also consume far more client and server resources. I run and label them separately because combining their latency distributions obscures both behaviors.

Which metrics would you correlate for an HTTP/3-enabled service?

On the client side I track latency percentiles, request failures, checks, custom business failures, protocol mismatch, and dropped iterations. At the edge I want negotiated protocol counts, QUIC handshake failures, UDP loss, connection state, CPU, and cache behavior. At the origin I correlate upstream latency, errors, queues, resource saturation, and dependency health.

What conclusion can you draw when k6 passes but HTTP/3 probes fail?

The shared application path may be healthy over k6's negotiated fallback while HTTP/3 reachability is impaired. I would investigate UDP routing, QUIC termination, certificates, edge limits, and protocol-specific telemetry. The evidence does not justify declaring the entire service healthy or blaming the origin without correlation.

Frequently Asked Questions

Can k6 load test HTTP/3 directly?

Stock k6 v2 does not originate HTTP/3 traffic through `k6/http`. It can load-test the HTTPS application routes behind an HTTP/3-enabled edge over its negotiated fallback protocol, while a separate HTTP/3-capable client verifies QUIC.

How do I verify that an endpoint really uses HTTP/3?

Use an HTTP/3-capable curl build with `--http3-only` and inspect `%{http_version}`. A successful result reporting version 3 proves that client and network path negotiated HTTP/3 at that moment; an `Alt-Svc` header alone does not.

Does an HTTP/3-enabled endpoint automatically use HTTP/3 for k6 requests?

No. Server support does not force a client to implement the protocol. Record `response.proto` in the k6 script, and report the value it returns, usually HTTP/2.0 or HTTP/1.1.

Can I compare k6 HTTP/2 latency with browser HTTP/3 latency?

Not as a clean protocol comparison unless you control client behavior, connection state, request concurrency, cache, route, and network conditions. The measurements can support investigation, but attributing the difference to QUIC alone would be unjustified.

Which k6 executor is suitable for endpoint capacity testing?

A constant or ramping arrival-rate executor is useful when the requirement is expressed as requests or iterations per unit of time. It keeps scheduled demand independent of response latency and exposes unscheduled work through `dropped_iterations`.

Why should HTTP/3 be probed again after the load test?

Resource pressure can break QUIC termination while a preflight remains green. Strict probes during and after load can reveal loss of HTTP/3 reachability, which you then correlate with edge and origin telemetry.

What should an HTTP/3 performance report call a k6 result?

Label it as an application-capacity test for HTTP/3-enabled endpoints and state the actual protocol reported by k6. Do not describe its percentile metrics as HTTP/3 measurements when the client used HTTP/2 or HTTP/1.1.

Related Guides