Staying secure while load testing: respecting rate limits with deliberate throttling, refusing header spoofing, injecting secrets via environment variables instead of writing them in the script, and isolating test environments so production applications aren't damaged by tests.

In episode 11 you now hold tokens, sessions, and API keys inside your script. That capability comes with new responsibility: a load test is fundamentally very dense real traffic. Executed wrongly, a test meant to measure performance can trigger security alarms, flood rate limiters, get the office IP blocked, or even corrupt production environment data.
Imagine a fire drill in a building that's actually occupied. You need a realistic drill, but you can't put out a fake fire with water that leaves everyone soaked. This episode is about running that drill safely: how to test without breaking the application's security rules, how to protect the credentials you use, and how to make sure production environments don't become the victim.
Every request k6 sends is a real request. In the eyes of the application, WAF, and rate limiter, there is no difference between a k6 request and a user request. The first thing you must do before running a test: communicate with the environment owners. When is the test window? What is the agreed maximum load? Are there alarms that can be temporarily muted? A well-prepared load test is a collaborative effort with the infra team, not a solo act.
One of the biggest beginner mistakes is launching thousands of VUs all at once at the same endpoint. That's not realistic load — it resembles a DDoS attack, and the WAF will predictably treat it as one. The solution is deliberate ramp-up:
export const options = {
stages: [
{ duration: "1m", target: 10 },
{ duration: "3m", target: 50 },
{ duration: "3m", target: 100 },
{ duration: "2m", target: 100 },
{ duration: "1m", target: 0 },
],
thresholds: {
http_req_failed: ["rate<0.01"],
},
};These stages raise the load from 10 to 100 VUs gradually, hold it, then bring it down. There are three reasons this isn't just politeness but correct technique:
Important
Before a test, ask the infra team about the rate limits and IP protections in your application. If your test load exceeds those limits, the result is a block, not performance data. Adjust the target VUs and ramp-up to the agreed limits — or schedule the test in an environment that doesn't have strict limits.
There's a temptation you must resist: faking headers like X-Forwarded-For or randomly changing the User-Agent to trick the rate limiter into thinking there are many different clients. This is wrong in dangerous ways:
If you genuinely need to simulate many clients from many IP addresses — for example to test a load balancer — k6 provides localIPs to use many real local IP addresses. And for global scale, load distribution is covered in upcoming episodes. The takeaway: test real behavior, with an honest identity.
This is the most important rule of this episode, and breaking it means production credentials leaking into the repository. Putting passwords, tokens, or API keys inside script files is an invitation to the most embarrassing event in engineering: secrets committed to git.
A direct comparison:
const TOKEN = "sk_live_9f8d7c6b5a4e3d2c1b0a";
const TOKEN = __ENV.TOKEN;
const params = {
headers: {
Authorization: `Bearer ${TOKEN}`,
},
};The red line is a disaster waiting to happen. The green line is the correct way: the value comes from outside the script. k6 provides two complementary mechanisms:
k6 run -e TOKEN=... script.js — the -e flag injects the value into __ENV, which the script can read. This is the most common and explicit way.--include-system-env-vars — forwards system environment variables into __ENV. Note: k6 run enables it by default, which means your entire shell environment (possibly containing many secrets) gets read. In CI, disable it with --include-system-env-vars=false so no environment secrets leak into the script or logs.Warning
Two rules you must never break. First: never commit real credentials —
once they enter git history, they're never truly gone; rotation is the only
path forward. Second: never print secrets to logs — console.log(__ENV.TOKEN)
in a script being debugged will display the token in the terminal and CI
logs, which are usually easier to access than the repo itself.
The practical way to do it:
k6 run -e TOKEN=sk_test_abc123 -e TEST_USER=budi script.jsk6 run --include-system-env-vars=false -e TOKEN="$K6_TOKEN" script.jsIn CI, secret values come from the pipeline's secret store (GitHub Actions secrets, GitLab CI variables), mapped to -e when running k6. Your script stays clean, and dangerous values never touch the repository.
The healthiest habit in performance testing is confining yourself to an environment meant for testing: staging, or an environment that closely mirrors production. There are obvious reasons, plus ones that aren't always thought of:
If testing in a dedicated environment isn't possible (for example you want to measure production), constrain it with a time window, an agreed load, and isolated test accounts — then make sure everyone involved signs off on that plan. Also remember: __ENV.BASE_URL (the pattern from episodes 7 and 11) lets the same script be pointed at staging or production without changing a single line — use that, don't create two different scripts.
__ENV, filled at runtime through -e or the CI secret store. No secrets in the script.--include-system-env-vars=false in CI so the system environment doesn't leak into the script.console.log or log.info that prints token values.In this episode 12 you've made security part of the process: understanding that a load test is real traffic that must respect security rules, raising load gradually with stages so you don't attack, refusing header spoofing because the results are misleading and dangerous, injecting secrets via -e and __ENV instead of hardcoding, disabling --include-system-env-vars in CI, and isolating testing to the staging environment.
Key points to take away:
-e KEY=value is the secret entry door; __ENV is the read door.Now your tests are safe and honest. The next question is: how do you read the results correctly? In episode 13 we'll cover Performance Analysis and Scripting Optimization — interpreting metrics like VU, latency, p95 and p99, trimming JavaScript overhead so the test engine doesn't lie, and structuring thresholds that target SLAs or SLOs. See you in episode 13!