# wrk User Guide

`wrk` is a modern HTTP benchmarking tool. It generates significant load from a single multi-core machine by combining a multithreaded design with a scalable event-notification system (`epoll`, `kqueue`, or `event ports`). An optional embedded LuaJIT script lets you customize request generation, response processing, and reporting.

This guide shows you how to install `wrk`, run benchmarks, read the results, and script custom load tests. Every command, option, and value here is taken directly from the `wrk` source code.

> [!NOTE]
> `wrk` measures how fast a server *responds* under concurrent load. It is a load generator and latency measurement tool, not a functional test framework and not a distributed swarm. One `wrk` process drives load from one machine.

---

## 1. Quick Start

Run a 30-second benchmark using 12 threads and keeping 400 connections open:

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

Output:

```text
Running 30s test @ http://127.0.0.1:8080/index.html
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   635.91us    0.89ms  12.92ms   93.69%
    Req/Sec    56.20k     8.07k   62.00k    86.54%
  22464657 requests in 30.00s, 17.76GB read
Requests/sec: 748868.53
Transfer/sec:    606.33MB
```

That is the whole workflow: point `wrk` at a URL, tell it how many threads and connections to use, and how long to run. Everything else is refinement.

> [!TIP]
> Start with a small connection count and short duration to confirm the target responds, then scale up `-c` and `-d`. The number of connections must always be greater than or equal to the number of threads.

---

## 2. Requirements

`wrk` runs on UNIX-like operating systems. There is no Windows build.

| Requirement | Detail |
|---|---|
| Operating system | Linux, macOS, FreeBSD, or illumos/Solaris (any UNIX-like OS supported by LuaJIT and OpenSSL) |
| Build toolchain | A C compiler and GNU `make` (`gmake` on \*BSD) |
| Runtime libraries | LuaJIT and OpenSSL — bundled with the source and built automatically, or supplied by your system |
| Privileges | None required to run; you only need network access to the target |

> [!IMPORTANT]
> `wrk` does **not** read any configuration file and does **not** read any environment variables. All behavior is controlled by command-line options and, optionally, a Lua script. There is nothing to set up in your shell profile.

---

## 3. Installation

`wrk` is distributed as source and built with `make`. The build bundles compatible versions of LuaJIT and OpenSSL and compiles them statically, so a plain `make` usually produces a working binary.

=== "Linux"

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

=== "macOS"

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

=== "FreeBSD / *BSD"

    ```bash
    git clone https://github.com/wg/wrk.git
    cd wrk
    gmake
    ```

The build produces a single executable named `wrk` in the project directory. Copy it somewhere on your `PATH` to use it system-wide:

```bash
sudo cp wrk /usr/local/bin/
```

Verify the build:

```bash
wrk --version
```

```text
wrk 4.2.0 [epoll] Copyright (C) 2012 Will Glozer
```

The bracketed name (`epoll`, `kqueue`, `select`, or `evport`) reports which event-notification system this build will use — it is selected automatically for your platform.

### Using system libraries

If you are packaging `wrk` for a distribution, or you prefer your system's LuaJIT and OpenSSL, point the build at them with `WITH_LUAJIT` and `WITH_OPENSSL`:

```bash
# Use the system version of both libraries on Linux
make WITH_LUAJIT=/usr WITH_OPENSSL=/usr
```

```bash
# Use the Homebrew OpenSSL on macOS
make WITH_OPENSSL=/usr/local/opt/openssl
```

> [!NOTE]
> When building against system OpenSSL, version **1.1.0 or newer** is required.

> [!TIP]
> Some platforms need extra `CFLAGS` or `LDFLAGS`. The top of the `Makefile` contains per-OS examples for SunOS, Darwin, Linux, and FreeBSD.

---

## 4. Command-Line Options

The full invocation grammar is:

```text
wrk <options> <url>
```

The `<url>` is required and must include a scheme and host, for example `http://localhost:8080/` or `https://example.com/api`.

| Option | Long form | Argument | Default | Description |
|---|---|---|---|---|
| `-c` | `--connections` | `<N>` | `10` | Total HTTP connections to keep open, spread across all threads |
| `-d` | `--duration` | `<T>` | `10` (seconds) | How long to run the test |
| `-t` | `--threads` | `<N>` | `2` | Number of OS threads to use |
| `-s` | `--script` | `<file>` | — | Load a LuaJIT script (see [Scripting](#7-scripting-with-lua)) |
| `-H` | `--header` | `<header>` | — | Add an HTTP header to each request; repeatable |
| | `--latency` | — | off | Print a detailed latency percentile distribution |
| | `--timeout` | `<T>` | `2s` | Record a timeout if no response arrives within this time |
| `-v` | `--version` | — | — | Print version and the event-system name, then continue |
| `-h` | `--help` | — | — | Print usage |

### Units in arguments

Numeric and time arguments accept unit suffixes, so you rarely need to type raw numbers:

- **Numeric arguments** (`-t`, `-c`) accept SI units: `1k` = 1,000, `1M` = 1,000,000, `1G` = 1,000,000,000.
- **Time arguments** (`-d`, `--timeout`) accept time units: `s` (seconds), `m` (minutes), `h` (hours) — for example `2s`, `2m`, `2h`.

```bash
# 2 threads, 1000 connections, run for 5 minutes
wrk -t2 -c1k -d5m http://127.0.0.1:8080/
```

> [!WARNING]
> The number of connections must be greater than or equal to the number of threads. `wrk -t8 -c4 ...` exits with `number of connections must be >= threads`.

> [!NOTE]
> Connections are distributed evenly across threads: each thread handles `connections / threads`. If the division is not exact, the remainder of connections is not used, so prefer a `-c` value that is a clean multiple of `-t`.

---

## 5. Reading the Output

Every run prints a header, a per-thread statistics block, totals, and throughput.

```text
Running 30s test @ http://127.0.0.1:8080/index.html
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   635.91us    0.89ms  12.92ms   93.69%
    Req/Sec    56.20k     8.07k   62.00k    86.54%
  22464657 requests in 30.00s, 17.76GB read
Requests/sec: 748868.53
Transfer/sec:    606.33MB
```

| Line | Meaning |
|---|---|
| `Running … test @ …` | Confirms the duration and target URL |
| `N threads and M connections` | The thread/connection configuration in effect |
| `Latency` row | Per-request latency: average, standard deviation, maximum, and the percentage of samples within one standard deviation of the mean |
| `Req/Sec` row | Per-thread request rate, sampled in 100 ms windows: average, standard deviation, maximum, and `+/- Stdev` |
| `N requests in T, X read` | Total completed requests, actual elapsed time, and total bytes received |
| `Requests/sec` | Overall throughput across all threads |
| `Transfer/sec` | Overall data-receive rate |

> [!TIP]
> `Req/Sec` is a **per-thread** figure. The headline number for overall throughput is `Requests/sec` at the bottom.

### The `+/- Stdev` column

`+/- Stdev` is the share of samples that fall within one standard deviation of the mean. For a healthy, consistent server this is high (around 68% or more). A low value signals erratic response times.

### Latency distribution (`--latency`)

Add `--latency` to print key percentiles:

```bash
wrk -t4 -c100 -d10s --latency http://127.0.0.1:8080/
```

```text
  Latency Distribution
     50%  590.00us
     75%  720.00us
     90%    1.05ms
     99%    3.20ms
```

The four reported percentiles are the **50th, 75th, 90th, and 99th**. The 99th percentile ("p99") is the most important single number for tail latency: 99% of requests completed at least this fast.

> [!IMPORTANT]
> `wrk` corrects for *coordinated omission* — a measurement bias where a stalled server hides its worst latencies because the load generator also stalls. After the run, recorded latencies are adjusted against the expected request interval so that long pauses are reflected in the distribution. This makes `wrk`'s latency numbers more honest than tools that do not correct for it, especially at high percentiles.

### Error and status lines

If the run encountered socket-level problems or non-success responses, extra lines appear:

```text
  Socket errors: connect 0, read 12, write 0, timeout 5
  Non-2xx or 3xx responses: 134
```

| Line | Meaning |
|---|---|
| `Socket errors: connect/read/write/timeout` | Counts of low-level connection, read, write, and timeout failures. Only shown if at least one occurred. |
| `Non-2xx or 3xx responses` | Count of responses with an HTTP status greater than `399` (i.e. 4xx and 5xx). Only shown if at least one occurred. |

> [!CAUTION]
> A high `Requests/sec` paired with many `Non-2xx or 3xx responses` is misleading — the server may be returning errors quickly rather than serving real traffic. Always check the error lines before trusting the throughput number.

---

## 6. Common Tasks

### Run a longer, heavier test

```bash
wrk -t12 -c400 -d2m --latency https://api.example.com/health
```

### Add custom headers

Repeat `-H` for each header. This is how you set authorization, content negotiation, or a custom `User-Agent`:

```bash
wrk -t4 -c100 -d30s \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Accept: application/json" \
  https://api.example.com/v1/items
```

> [!NOTE]
> A `Host` header is added automatically from the URL if you do not supply one.

### Set a request timeout

By default a request is recorded as a timeout if no response arrives within 2 seconds. Raise it for slow endpoints:

```bash
wrk -t4 -c100 -d30s --timeout 10s https://slow.example.com/report
```

### Benchmark over HTTPS

Use an `https://` URL. `wrk` negotiates TLS automatically and sends the hostname via SNI:

```bash
wrk -t8 -c200 -d1m https://example.com/
```

> [!WARNING]
> `wrk` does **not** verify the server's TLS certificate. It will connect to hosts with expired, self-signed, or mismatched certificates without complaint. This is acceptable for benchmarking but means `wrk` must never be used as a tool to validate a server's certificate.

### Stop a run early

Press `Ctrl+C`. `wrk` catches the interrupt, stops the threads cleanly, and prints the statistics gathered so far.

---

## 7. Scripting with Lua

For anything beyond a fixed `GET`, supply a LuaJIT script with `-s`. Scripts can change the HTTP method, build dynamic request bodies and paths, inspect responses, add delays, and emit custom reports.

```bash
wrk -t4 -c100 -d30s -s post.lua http://127.0.0.1:8080/
```

The repository ships ready-to-use examples in the `scripts/` directory; the sections below are based on them.

### The execution model

Each `wrk` thread runs an **independent** Lua scripting context, so script globals (counters, caches) are per-thread, not shared. A script runs across three phases:

1. **setup** — once per thread, after the target address is resolved but before load starts.
2. **running** — `init()` once, then `request()`/`response()` for every request cycle.
3. **done** — once at the end, with the aggregated results.

You define behavior by declaring global functions with specific names. All are optional.

| Function | Phase | Purpose |
|---|---|---|
| `setup(thread)` | setup | Configure a thread before it starts; pass data into it |
| `init(args)` | running | Initialize a thread; receives extra command-line arguments |
| `delay()` | running | Return milliseconds to wait before sending the next request |
| `request()` | running | Return the HTTP request string to send |
| `response(status, headers, body)` | running | Inspect each response |
| `done(summary, latency, requests)` | done | Produce a custom final report |

### The `wrk` table

A global table named `wrk` describes the request and is available to your script:

```lua
wrk = {
  scheme  = "http",
  host    = "localhost",
  port    = nil,
  method  = "GET",
  path    = "/",
  headers = {},
  body    = nil,
  thread  = <userdata>,
}
```

Set fields on it to shape the default request, and call its helper functions:

- `wrk.format(method, path, headers, body)` — build an HTTP request string, filling in any omitted argument from the `wrk` table.
- `wrk.lookup(host, service)` — return a table of all resolved addresses for a host/service pair.
- `wrk.connect(addr)` — return `true` if an address (from `wrk.lookup`) accepts a connection.

### Example: HTTP POST

`scripts/post.lua` — set the method, body, and a header. Because nothing changes per request, this is a *static* request, built once:

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

```bash
wrk -t2 -c50 -d30s -s post.lua http://127.0.0.1:8080/submit
```

### Example: a different path each request

`scripts/counter.lua` — defining `request()` as a function makes requests *dynamic* (rebuilt every cycle):

```lua
counter = 0

request = function()
   path = "/" .. counter
   wrk.headers["X-Counter"] = counter
   counter = counter + 1
   return wrk.format(nil, path)
end
```

> [!IMPORTANT]
> A script that only changes the method, path, headers, or body **once** (a static request) has no measurable performance cost. Building a new request on every cycle with a `request()` function, or parsing responses with `response()`, reduces the load `wrk` can generate. Pre-build what you can in `init()`.

### Example: inspect responses and capture a token

`scripts/auth.lua` — use `response()` to read a header from one response and apply it to all future requests:

```lua
token = nil
path  = "/authenticate"

request = function()
   return wrk.format("GET", path)
end

response = function(status, headers, body)
   if not token and status == 200 then
      token = headers["X-Token"]
      path  = "/resource"
      wrk.headers["X-Token"] = token
   end
end
```

### Example: add a random delay

`scripts/delay.lua` — pace each connection by returning a millisecond delay:

```lua
function delay()
   return math.random(10, 50)
end
```

### Example: HTTP pipelining

`scripts/pipeline.lua` — concatenate several requests into one static buffer to pipeline them:

```lua
init = function(args)
   local r = {}
   r[1] = wrk.format(nil, "/?foo")
   r[2] = wrk.format(nil, "/?bar")
   r[3] = wrk.format(nil, "/?baz")
   req = table.concat(r)
end

request = function()
   return req
end
```

### Example: pass data into threads with `setup()`

`scripts/setup.lua` — `setup(thread)` runs in the main context and can seed per-thread globals via `thread:set()`:

```lua
local counter = 1
local threads = {}

function setup(thread)
   thread:set("id", counter)
   table.insert(threads, thread)
   counter = counter + 1
end

function init(args)
   requests  = 0
   responses = 0
   print(("thread %d created"):format(id))
end

function request()
   requests = requests + 1
   return wrk.request()
end

function response(status, headers, body)
   responses = responses + 1
end
```

The thread object passed to `setup()` supports:

| Operation | Effect |
|---|---|
| `thread.addr` | Get or set the server address this thread connects to |
| `thread:get(name)` | Read a global from the thread's running environment |
| `thread:set(name, value)` | Set a global in the thread's running environment |
| `thread:stop()` | Stop the thread (only while it is running) |

> [!NOTE]
> Only `boolean`, `nil`, `number`, and `string` values — or tables containing only those types — can be transferred with `get()`/`set()`.

### Example: a custom CSV report with `done()`

`scripts/report.lua` — `done()` receives the aggregated results and two statistics objects:

```lua
done = function(summary, latency, requests)
   io.write("------------------------------\n")
   for _, p in pairs({ 50, 90, 99, 99.999 }) do
      n = latency:percentile(p)
      io.write(string.format("%g%%,%d\n", p, n))
   end
end
```

The statistics objects (`latency` and `requests`) expose:

| Accessor | Returns |
|---|---|
| `latency.min` | Minimum value seen |
| `latency.max` | Maximum value seen |
| `latency.mean` | Average value |
| `latency.stdev` | Standard deviation |
| `latency:percentile(p)` | The value at percentile `p` |
| `latency(i)` | The raw value and its count at index `i` |
| `#latency` | The number of distinct recorded values |

The `summary` table contains `duration` (microseconds), `requests`, `bytes`, and an `errors` sub-table with `connect`, `read`, `write`, `status`, and `timeout` counts.

### Passing arguments to a script

Anything after a `--` separator is passed to your script's `init(args)` function rather than interpreted by `wrk`:

```bash
wrk -t2 -c10 -d10s -s myscript.lua http://127.0.0.1:8080/ -- arg1 arg2
```

> [!TIP]
> Latency and duration values inside scripts are in **microseconds**; the request rate is in requests per second. Multiply or divide as needed when formatting your own reports.

---

## 8. Benchmarking Tips

Getting trustworthy numbers depends as much on your environment as on `wrk`'s flags.

- **Provide enough ephemeral ports.** The machine running `wrk` must have enough ephemeral ports available, and closed sockets should be recycled quickly. A high connection count can exhaust the port range otherwise.
- **Raise the server's listen backlog.** To absorb the initial connection burst, the target server's `listen(2)` backlog should be larger than the number of concurrent connections you are testing.
- **Keep scripts cheap.** Static requests are free; per-request work is not. If you must build requests dynamically, pre-generate as much as possible in `init()` and do a quick lookup in `request()`.
- **Benchmark from a separate machine.** Running `wrk` and the server on the same host makes them compete for CPU and skews results.
- **Use `--latency` for tail analysis.** Average latency hides the slow requests that users actually notice. Watch p99.

> [!TIP]
> Match `-c` to a multiple of `-t` and start `-t` near your core count. More threads than cores rarely helps and adds scheduling overhead.

---

## 9. Troubleshooting

`wrk`'s errors are short and printed to standard error. The most common ones and their fixes:

### Error: `number of connections must be >= threads`

- **Cause:** You requested more threads than connections, e.g. `-t8 -c4`.
- **Solution:** Raise `-c` so it is at least equal to `-t`. Connections are split across threads, so each thread needs at least one.
- **Prevention:** Set `-c` to a clean multiple of `-t` (e.g. `-t4 -c100`).

### Error: `invalid URL: <url>`

- **Cause:** The URL is missing a scheme or host, or is malformed. `wrk` requires both a scheme (`http`/`https`) and a host.
- **Solution:** Use a fully qualified URL such as `http://127.0.0.1:8080/path`.
- **Prevention:** Always include `http://` or `https://`.

### Error: `unable to connect to <host>:<service> <reason>`

- **Cause:** The target host could not be reached or did not accept a connection during address resolution.
- **Solution:** Verify the server is running and listening, check the port, and confirm there is no firewall in the way.
- **Prevention:** Confirm connectivity with `curl` or `nc` before benchmarking.

### Error: `unable to resolve <host>:<service> <reason>`

- **Cause:** DNS resolution failed for the host/service pair (raised from `wrk.lookup`).
- **Solution:** Check the hostname spelling and your DNS configuration; try an IP address directly.
- **Prevention:** Use a reachable, resolvable hostname or a literal IP.

### Error: `unable to initialize SSL`

- **Cause:** TLS context creation failed for an `https://` target.
- **Solution:** Confirm `wrk` was built with a working OpenSSL (1.1.0+). Rebuild against system OpenSSL with `make WITH_OPENSSL=/usr` if needed.
- **Prevention:** Verify `wrk --version` works and the binary links a valid OpenSSL.

### Error: `unable to create thread <N>: <reason>`

- **Cause:** The OS could not create one of the worker threads, usually a resource limit.
- **Solution:** Lower `-t`, or raise your process/thread limits (`ulimit`).
- **Prevention:** Keep `-t` near your CPU core count.

### Error: `<file>: <cause>` when loading a script

- **Cause:** The Lua script has a syntax error or could not be loaded.
- **Solution:** Read the reported cause; fix the script syntax. Test the file with `luajit -bl script.lua` if available.
- **Prevention:** Keep scripts small and validate them before a long run.

### Error: `<message> at <line>:<column>` or `incomplete request`

- **Cause:** Your script's `request()` produced a request that is not valid HTTP. `wrk` verifies the first request before starting and exits if it cannot be parsed.
- **Solution:** Ensure `wrk.format()` (or your hand-built string) produces a complete, well-formed request with proper CRLF line endings.
- **Prevention:** Build requests with `wrk.format()` rather than concatenating strings by hand.

### Symptom: many `Socket errors` or `timeout`s

- **Cause:** The server is overloaded, the connection count exceeds what it can handle, or the timeout is too low.
- **Solution:** Reduce `-c`, raise `--timeout`, and confirm the server's listen backlog and ephemeral port range are adequate.
- **Prevention:** Ramp load gradually and watch the error lines.

---

## 10. FAQ

**Q: Does `wrk` work on Windows?**
No. `wrk` targets UNIX-like systems (Linux, macOS, the BSDs, illumos/Solaris). There is no Windows build.

**Q: Where do I put my settings — a config file or environment variables?**
Neither exists. `wrk` is configured entirely through command-line options and an optional Lua script. It reads no config file and no environment variables.

**Q: What is the default if I omit `-t`, `-c`, or `-d`?**
2 threads, 10 connections, and a 10-second duration.

**Q: Why must connections be greater than or equal to threads?**
Connections are divided evenly among threads, so each thread must own at least one connection. `wrk` enforces this and exits otherwise.

**Q: Can I limit the request rate (throttle)?**
Not with a flag. Use a script that defines `delay()` to return a per-request delay in milliseconds (see `scripts/delay.lua`).

**Q: Can I get JSON or CSV output?**
The built-in output is the fixed text report. For machine-readable output, define a `done()` function in a script and write whatever format you like (see `scripts/report.lua`).

**Q: How do I send a POST with a body?**
Set `wrk.method`, `wrk.body`, and a `Content-Type` header in a script (see `scripts/post.lua`). The `Content-Length` header is computed for you by `wrk.format()`.

**Q: Does `wrk` verify TLS certificates?**
No. It connects to HTTPS endpoints without verifying the certificate. Do not rely on `wrk` to detect certificate problems.

**Q: Does `wrk` support HTTP/2?**
No. It speaks HTTP/1.x via the bundled HTTP parser.

**Q: My script's counter resets between threads — is that a bug?**
No. Each thread has its own independent Lua context, so a global like `counter` is per-thread. Use `setup()` with `thread:set()` to coordinate values across threads.

**Q: What does the bracketed word in `wrk --version` mean (e.g. `[epoll]`)?**
It names the event-notification system the build uses: `epoll` on Linux, `kqueue` on macOS/BSD, `evport` on illumos/Solaris, or `select` as a fallback.

**Q: How does `wrk` choose which addresses to use?**
It resolves the host, then keeps only addresses it can actually connect to (`wrk.resolve` filters with `wrk.connect`). A script can override the per-thread address via `thread.addr`.

**Q: How is `Req/Sec` different from `Requests/sec`?**
`Req/Sec` is the per-thread rate sampled every 100 ms. `Requests/sec` is the overall throughput across all threads for the whole run.

---

_Generated June 2026 from wg/wrk at a211dd5. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
