Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

wrk User Guide

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.

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:

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

Output:

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

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

macOS

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

FreeBSD / *BSD

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:

sudo cp wrk /usr/local/bin/

Verify the build:

wrk --version
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:

# Use the system version of both libraries on Linux
make WITH_LUAJIT=/usr WITH_OPENSSL=/usr
# 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:

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)
-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.
# 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.

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:

wrk -t4 -c100 -d10s --latency http://127.0.0.1:8080/
  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:

  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

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:

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:

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:

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.

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:

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:

wrk.method = "POST"
wrk.body   = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
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):

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:

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:

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

Example: HTTP pipelining

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

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():

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:

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:

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 timeouts

  • 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.