wrk HTTP Benchmarking

wrk cheatsheet — multi-threaded HTTP benchmarking. wrk -t12 -c400 -d30s http://localhost — 12 threads, 400 connections, 30 seconds. Lua scripts for custom request logic.

7 min read

What it is

wrk is a modern, multi-threaded HTTP benchmarking tool used to measure the performance of web servers and applications under load.

Installation

Linux

# Using apt (Debian/Ubuntu)
sudo apt update
sudo apt install wrk

# Using yum/dnf (Fedora/CentOS/RHEL)
sudo dnf install wrk
# or
sudo yum install wrk

# From source (if not in package manager)
git clone https://github.com/wg/wrk.git
cd wrk
make
sudo cp wrk /usr/local/bin/

macOS

# Using Homebrew
brew install wrk

Windows

wrk is not officially supported on Windows. You can:

  • Use the Windows Subsystem for Linux (WSL).
  • Compile from source using a MinGW-w64 environment.
  • Find pre-compiled binaries from unofficial sources (use with caution).

Core Concepts

wrk operates by launching a specified number of threads, each running its own event loop. These threads concurrently send HTTP requests to the target URL. The tool measures latency, throughput (requests per second), and the distribution of response times.

Commands / Usage

Basic Benchmarking

Target a single URL:

wrk http://localhost:8080/

This command benchmarks http://localhost:8080/ with default settings (4 threads, 2 connections per thread, 10-second duration).

Specify duration:

wrk -d 30s http://localhost:8080/

Benchmarks the URL for 30 seconds. Supports s (seconds), m (minutes), h (hours).

Specify number of threads:

wrk -t 8 http://localhost:8080/

Uses 8 threads for the benchmark.

Specify number of connections per thread:

wrk -c 100 http://localhost:8080/

Sets 100 concurrent connections per thread. Total connections = -t * -c.

Specify total number of connections:

wrk -t 8 -c 100 http://localhost:8080/

Uses 8 threads and 100 connections per thread, for a total of 800 connections.

Specify requests per connection:

wrk -R 1000 http://localhost:8080/

Each connection will make 1000 requests. If not specified, connections persist until the duration ends.

Specify total number of requests:

wrk -R 100000 http://localhost:8080/

The benchmark will stop once 100,000 requests have been sent, regardless of duration or connections.

HTTP Method and Headers

Specify HTTP method:

wrk -t 4 -c 100 -d 20s -m POST http://localhost:8080/submit

Uses the POST method instead of the default GET. Supports GET, POST, PUT, DELETE, etc.

Add custom headers:

wrk -t 4 -c 100 -d 20s \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token_here" \
  http://localhost:8080/api/users

Adds multiple custom headers to requests.

Use a connection header:

wrk -H "Connection: keep-alive" http://localhost:8080/

Explicitly sets the Connection header (though wrk defaults to keep-alive).

Request Body

Specify request body from file:

wrk -t 4 -c 100 -d 20s \
  --body-file ./payload.json \
  -H "Content-Type: application/json" \
  http://localhost:8080/create

Reads the request body from the file payload.json.

Specify request body inline:

wrk -t 4 -c 100 -d 20s \
  --body '{"key": "value"}' \
  -H "Content-Type: application/json" \
  http://localhost:8080/create

Sets the request body directly as a string.

Lua Scripting for Complex Requests

wrk can execute Lua scripts to generate dynamic requests, handle responses, and customize behavior.

Run a basic Lua script:

wrk -t 4 -c 100 -d 30s --script ./my_script.lua http://localhost:8080/

Executes the Lua script my_script.lua.

Example my_script.lua:

-- my_script.lua

-- Called once before the benchmark starts.
function setup(addr)
    print("Setting up connection to " .. addr)
    -- You can return a table of options here, e.g., headers
    return {
        headers = {
            ["X-Custom-Header"] = "SetupValue"
        }
    }
end

-- Called for each request.
-- The `intensity` parameter is the current number of requests sent by this thread.
function request(intensity)
    -- Return a table with method, path, and body
    return {
        method = "POST",
        path = "/data",
        body = string.format('{"id": %d, "timestamp": %f}', intensity, os.time())
    }
end

-- Called for each response.
-- `latency` is the time taken for the request in microseconds.
-- `status` is the HTTP status code.
-- `length` is the response body length in bytes.
function response(latency, status, length)
    -- You can log or process the response here
    if status ~= 200 then
        print(string.format("Request failed: status %d, latency %d us", status, latency))
    end
end

Scripting with multiple hosts:

wrk -t 4 -c 100 -d 30s --script ./multi_host.lua http://host1.example.com:8080/

The script can then define setup(addr) to return different configurations for different addr values (e.g., host1.example.com:8080, host2.example.com:8080).

Output and Reporting

Print statistics to stderr (default): wrk prints detailed statistics to standard error by default.

Save detailed statistics to a file:

wrk -t 4 -c 100 -d 30s http://localhost:8080/ > output.log 2>&1

Redirects both stdout and stderr to output.log. wrk prints its detailed stats to stderr.

Print statistics in JSON format:

wrk --latency -j http://localhost:8080/

Outputs detailed latency statistics in JSON format to stdout.

Print statistics in CSV format:

wrk --latency -u http://localhost:8080/

Outputs detailed latency statistics in CSV format to stdout.

Print summary statistics only:

wrk -s http://localhost:8080/

Prints only the summary line (Requests/sec, Latency, Transfer/sec, Status Codes).

TLS/HTTPS

Benchmark an HTTPS URL:

wrk https://example.com/

wrk automatically handles TLS for HTTPS URLs.

Specify client certificate and key:

wrk --cert cert.pem --key key.pem https://localhost:8443/

Uses the provided client certificate and private key for mutual TLS authentication.

Specify TLS version:

wrk --tls-version tlsv1.2 https://localhost:8443/

Forces a specific TLS version (e.g., tlsv1.0, tlsv1.1, tlsv1.2, tlsv1.3).

Other Flags

Warm-up connections:

wrk --warmup 5s http://localhost:8080/

Waits for 5 seconds before starting the actual measurement, allowing connections to be established and warmed up.

Disable connection reuse:

wrk --no-reuse http://localhost:8080/

Forces a new connection for every request. This is much slower and not typical for benchmarking unless testing specific connection overheads.

Timeout for connection/request:

wrk --timeout 5s http://localhost:8080/

Sets the timeout for establishing a connection and receiving a response.

Print version:

wrk --version

Displays the wrk version information.

Print help:

wrk --help

Displays the full help message with all options.

Common Patterns

Basic GET benchmark:

wrk -t 8 -c 200 -d 60s https://api.example.com/v1/items

Tests an API endpoint with 8 threads, 200 connections per thread, for 60 seconds.

POST benchmark with JSON payload:

wrk -t 8 -c 100 -d 30s \
  --body-file ./data.json \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  POST https://api.example.com/v1/items

Benchmarks a POST request with a JSON body and specific headers.

Testing a login endpoint with dynamic credentials (Lua script):

-- login_test.lua
local users = {"user1", "user2", "user3"}
local passwords = {"pass1", "pass2", "pass3"}

function request(intensity)
    local user_index = (intensity % #users) + 1
    return {
        method = "POST",
        path = "/login",
        body = string.format('{"username": "%s", "password": "%s"}', users[user_index], passwords[user_index]),
        headers = {
            ["Content-Type"] = "application/json"
        }
    }
end
wrk -t 4 -c 50 -d 60s --script ./login_test.lua http://localhost:8080/

Benchmarking multiple URLs concurrently (using shell features):

# Run two benchmarks in parallel, each in the background
wrk -t 4 -c 100 -d 30s http://localhost:8080/api/users &
wrk -t 4 -c 100 -d 30s http://localhost:8080/api/posts &
wait # Wait for both background jobs to finish

Saving results and analyzing latency distribution:

wrk --latency -t 8 -c 200 -d 60s http://localhost:8080/ > latency_data.csv 2>/dev/null
# Then analyze latency_data.csv with a script or spreadsheet

This captures the detailed latency CSV output and discards the summary stats to stderr.

Gotchas

  • Total Connections: Remember that the total number of connections is -t (threads) multiplied by -c (connections per thread). A high number of threads and connections can overwhelm the client machine or the network.
  • Resource Limits: On Linux, you might hit file descriptor limits (ulimit -n) or network buffer limits. Increase these if necessary.
  • wrk vs. ab: wrk is generally preferred over ApacheBench (ab) for modern applications due to its multi-threaded nature, Lua scripting capabilities, and better handling of keep-alive connections.
  • Scripting Overhead: While powerful, complex Lua scripts can introduce overhead and slightly skew results. Keep scripts as simple as possible for pure benchmarking.
  • DNS Resolution: wrk resolves DNS once per connection by default. If you need to test DNS behavior under load, you might need more advanced setups or other tools.
  • Client-Side Bottlenecks: Ensure your client machine has sufficient CPU, memory, and network bandwidth to generate the desired load. If wrk reports high CPU usage or network saturation on the client, the results might not reflect the server’s true capacity.
  • --latency Format: The --latency flag outputs detailed latency percentiles. The -j (JSON) and -u (CSV) flags format this detailed output. The default output is a summary with a separate latency histogram.
  • HTTP/2: wrk does not natively support HTTP/2. For HTTP/2 benchmarking, consider tools like h2load or bombardier.