wrk: How to Load Test an API from the Command Line

Learn how to load test an API with wrk from the command line: install, threads and connections flags, reading latency output, and POST requests via Lua.

Ashley Innocent

Ashley Innocent

6 July 2026

wrk: How to Load Test an API from the Command Line

Apidog for Enterprise

On-Premises Deploy

SSO & RBAC

SOC 2 Compliant

Explore Apidog Enterprise

You shipped an endpoint. It works in your browser. But you have no idea what happens when 400 people hit it at once. Does latency stay flat, or does the ninety-ninth percentile blow up? Can the box hold 1,000 requests per second, or does it fall over at 300?

wrk answers that. It’s a small command-line tool that fires a lot of HTTP traffic at a URL and reports how fast the server responded under that load.

button

What wrk Is and When to Use It

wrk is a modern HTTP benchmarking tool. It generates load from a single multi-core machine and measures the latency and request rate the server returns. It uses multithreading plus a scalable event loop (epoll on Linux, kqueue on macOS), so one instance can push a lot of traffic without needing a fleet of load boxes.

Reach for wrk when you want raw performance numbers:

wrk is a benchmarking tool, not a test suite. It measures speed. It does not check that the JSON body is right, that the status code is 200, or that the API contract held. Keep that distinction in mind. We’ll come back to it near the end, because it changes how you fit wrk into a real testing workflow. If you want the broader picture first, this guide to API load testing covers the concepts wrk puts into practice.

Installing wrk

macOS

Homebrew has a prebuilt binary, which is the path of least resistance:

brew install wrk

On Apple Silicon this matters. Building from source can hit LuaJIT ARM64 issues, so the Homebrew binary saves you the headache.

Linux (build from source)

There’s no official apt package, so you build it. Install the toolchain and OpenSSL headers first:

sudo apt-get install build-essential libssl-dev git -y

Then clone and compile:

git clone https://github.com/wg/wrk.git
cd wrk
make

That produces a wrk binary in the current directory. Move it onto your PATH so you can call it from anywhere:

sudo cp wrk /usr/local/bin

Confirm it runs:

wrk --version

The Basic Command

Here’s the shape of every wrk run:

wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html

Four things are happening. Let’s break down the flags.

Two more flags you’ll use constantly:

A run you’ll reach for often looks like this:

wrk -t8 -c200 -d30s --latency http://localhost:3000/api/users

Eight threads, 200 connections, 30 seconds, with the full latency distribution printed at the end.

Reading the Output

wrk prints a compact report. Here’s a real run against a small service:

Running 5s test @ http://10.135.232.163:3000
  2 threads and 5 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.82ms    2.64ms  26.68ms   85.81%
    Req/Sec   550.90    202.40     0.98k    68.00%
  5494 requests in 5.01s, 1.05MB read
Requests/sec:   1096.54
Transfer/sec:    215.24KB

Read it from the bottom up, because the last two lines are the headline.

Requests/sec is throughput: how many requests the server completed per second on average. Here it’s 1,096. This is the number you compare across runs and across code changes.

Transfer/sec is bandwidth: how much data moved per second. Useful when payloads are large or you suspect you’re bandwidth-bound rather than CPU-bound.

Now the Thread Stats table, which describes distribution, not just the average:

The +/- Stdev column tells you what percentage of samples fell within one standard deviation. Lower percentages mean a wider, less predictable spread.

The line 5494 requests in 5.01s confirms the total volume the run actually pushed.

When you add --latency, wrk prints a percentile block so you can see the tail directly:

  Latency Distribution
     50%    3.21ms
     75%    4.86ms
     90%    7.09ms
     99%   14.13ms

The 99th percentile is the number to watch. If 99% of requests finish in 14ms but your average is 3.82ms, one in a hundred users is waiting far longer than the average suggests. Averages lie about tails. Percentiles don’t.

Sending POST Requests and Custom Headers with a Lua Script

By default wrk sends GET requests. To send a POST, add a body, or set custom headers, you pass a Lua script with -s.

Create a file called post.lua:

wrk.method = "POST"
wrk.body   = '{"name": "Ada", "role": "engineer"}'
wrk.headers["Content-Type"] = "application/json"

Three fields do the work. wrk.method sets the HTTP verb. wrk.body sets the request body. wrk.headers is a table where each key is a header name.

Run it by pointing -s at the script:

wrk -t4 -c100 -d30s -s post.lua --latency http://localhost:3000/api/users

For a form-encoded POST instead of JSON, the wrk repo ships this exact example:

wrk.method = "POST"
wrk.body   = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

You can also set headers with the -H flag for simpler cases, without a script:

wrk -t4 -c100 -d30s -H "Authorization: Bearer TOKEN123" --latency http://localhost:3000/api/protected

Use -H for a header or two. Use a Lua script when you need a body, a non-GET method, or per-request logic.

The Limits: wrk Doesn’t Check Correctness

Here’s the part people miss. wrk tells you how fast the server answered. It does not tell you whether the answer was right.

Point wrk at an endpoint that returns HTTP 500 on every request, and you’ll get a clean-looking report with a high requests-per-second number. wrk counts a completed HTTP exchange. It does not assert on the status code, validate the response body against a schema, or confirm the API did what it was supposed to do. Errors can even look fast, because a server rejecting requests early does less work per request.

So wrk answers “is it fast enough under load?” It cannot answer “is it correct?” Both questions matter, and they need different tools. A load number on a broken endpoint is a number you shouldn’t trust. This is exactly why teams pair a benchmarking tool with a functional test suite. One proves speed. The other proves behavior.

Where Apidog and Functional Testing Fit

The clean workflow is two layers, run in order.

First, validate behavior. Before you care how fast an endpoint is, confirm it’s correct. In Apidog you build test scenarios that send real requests and assert on what comes back: status codes, JSON fields, response schema, and business logic. You can chain requests, pass data between steps, and run the same scenario across environments. This is the layer that catches the broken 500 that wrk happily benchmarks.

Then, benchmark throughput. Once behavior is verified, run wrk against the same endpoints to see how they hold up under concurrency and sustained load. Apidog also has built-in performance testing if you’d rather keep functional and load work in one place, but wrk is a fine dedicated tool for raw command-line benchmarking.

The functional layer runs in CI, not just on your laptop. The Apidog CLI is headless, so it drops into any pipeline step that can run Node. Install it:

npm install -g apidog-cli

Then run a saved test scenario or suite by ID:

apidog run \
  --access-token "$APIDOG_ACCESS_TOKEN" \
  -t <scenarioOrSuiteId> \
  -e <environmentId> \
  -r cli,html,junit

-t is the scenario, folder, or suite ID to run. -e is the environment ID. -r picks the report formats, one or more of cli, html, json, and junit. The JUnit output plugs straight into most CI systems for pass/fail gating. For data-driven runs, add -d (or --iteration-data) with a file path or test-data ID to iterate the same scenario over many input rows.

The CLI runs saved Apidog scenarios and suites. It is headless, not an interactive request sender, and it is not a load generator. It’s the correctness gate. wrk is the speed gauge. Run the correctness gate in your pipeline (see this CLI CI/CD walkthrough or the GitHub Actions guide for copy-paste config), then benchmark with wrk when you need the throughput numbers. The full CLI reference covers the rest of the flags.

FAQ

What’s the difference between wrk and ab (ApacheBench)? Both fire HTTP load and report requests per second. wrk is multithreaded and uses an event loop, so it generates more load from one machine and handles high concurrency better. ab is single-threaded. For heavy load from a single box, wrk usually scales further. Neither one checks response correctness.

How many threads and connections should I use? Start with one thread per CPU core, and set connections to the concurrency level you want to simulate. If you have 8 cores and want to model 200 concurrent clients, try -t8 -c200. Watch the client machine. If wrk itself is CPU-bound, your numbers reflect the load generator’s limit, not the server’s. Ramp connections up until throughput stops rising.

Can wrk test HTTPS endpoints? Yes. Point it at an https:// URL and wrk handles TLS. That’s why the Linux build needs libssl-dev. TLS handshakes add CPU cost on both ends, so expect lower raw throughput against HTTPS than plain HTTP.

Does wrk validate the response body or status code? No. wrk counts completed HTTP exchanges and measures timing. It does not assert on status codes or bodies, so an endpoint returning errors can still post a high requests-per-second number. Use a functional test suite, such as one run through the Apidog CLI, to verify correctness, then use wrk for throughput.

How long should a load test run? Long enough to get past warm-up effects like cold caches and JIT compilation. A few seconds is fine for a quick check, but 30 seconds to a few minutes gives more stable numbers and surfaces degradation that only shows up under sustained load. Use -d30s as a sane default and lengthen it when you’re chasing slow leaks.

Explore more

How to Use the Apidog CLI in Hermes Agent

How to Use the Apidog CLI in Hermes Agent

Teach Hermes Agent your API testing workflow in an AGENTS.md file, then let its terminal tool run apidog run and read the exit code. Plus the Apidog MCP server.

6 July 2026

ApacheBench (ab): How to Load Test an API from the Terminal

ApacheBench (ab): How to Load Test an API from the Terminal

Learn how to load test an API with ApacheBench (ab): install it, run -n and -c tests, POST with -p and -T, and read requests/sec and percentile output.

6 July 2026

autocannon: Node.js HTTP Load Testing (Step-by-Step)

autocannon: Node.js HTTP Load Testing (Step-by-Step)

Load test HTTP endpoints with autocannon, the Node.js benchmarking tool. Install, run with -c/-d/-p, read latency percentiles, and script it in CI.

6 July 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs

wrk: How to Load Test an API from the Command Line