Benchmarking Nginx with wrk turns tuning work into numbers that can be compared across changes, making regressions obvious and improvements defensible. A repeatable baseline is especially useful when adjusting worker counts, buffers, caching, TLS settings, or upstream timeouts.

wrk is a multi-threaded load generator for HTTP/1.1 that keeps many connections open and issues requests as fast as possible, reporting throughput plus average and percentile latency when --latency is enabled. Results are sensitive to request shape (path, headers, body size), connection reuse (keep-alive), and network conditions, so a single well-defined URL and a fixed option set matter more than absolute numbers.

Synthetic load can overwhelm a server and also mislead when the load generator becomes CPU- or network-bound, so runs belong in controlled test windows with server-side metrics visible. Prefer a dedicated client host close to the server, keep the target endpoint stable (avoid redirects), and treat each run as a comparison between configurations rather than a promise of production capacity.

Steps to benchmark Nginx with wrk:

  1. Confirm the benchmark URL returns 200 with no redirects.
    $ curl --head http://127.0.0.1/
    HTTP/1.1 200 OK
    Server: nginx
    Date: Tue, 30 Dec 2025 00:33:14 GMT
    Content-Type: text/html
    Content-Length: 20
    Connection: keep-alive
    Keep-Alive: timeout=15
    Vary: Accept-Encoding
    Last-Modified: Mon, 29 Dec 2025 22:23:50 GMT
    X-Cache-Status: STALE

    A 301/308 redirect adds an extra request per hit, so benchmark the final URL (often by adding a trailing /).

  2. Record the Nginx version plus the exact configuration under test before capturing a baseline.
    $ nginx -v 2>&1
    nginx version: nginx/1.24.0

    Capturing effective config with nginx -T improves repeatability, but the output may include sensitive paths and upstream details.

  3. Run a short warm-up from the load generator to stabilize caches before measuring.
    $ wrk --threads 2 --connections 50 --duration 10s --timeout 10s http://127.0.0.1/
    Running 10s test @ http://127.0.0.1/
      2 threads and 50 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency   250.52us  802.04us  24.75ms   97.37%
        Req/Sec   153.69k    18.08k  176.47k    83.50%
      3057258 requests in 10.01s, 798.80MB read
    Requests/sec: 305486.48
    Transfer/sec:     79.82MB

    If the client CPU is saturated, results reflect the load generator rather than Nginx.

  4. Run a baseline benchmark with --latency enabled while saving the output for comparison.
    $ wrk --threads 2 --connections 50 --duration 30s --timeout 10s --latency http://127.0.0.1/ | tee wrk-baseline.txt
    Running 30s test @ http://127.0.0.1/
      2 threads and 50 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency   323.52us    1.92ms  59.35ms   98.78%
        Req/Sec   158.70k    16.00k  176.03k    88.00%
      Latency Distribution
         50%  106.00us
         75%  188.00us
         90%  394.00us
         99%    3.31ms
      9477534 requests in 30.06s, 2.42GB read
      Socket errors: connect 0, read 0, write 0, timeout 18
    Requests/sec: 315322.04
    Transfer/sec:     82.39MB

    Do not run load tests against production endpoints without capacity planning, approval, and a rollback plan.

  5. Query stub_status during the run to spot saturation signals like rising active connections or stalled requests.
    $ curl -s http://127.0.0.1/nginx_status
    Active connections: 1 
    server accepts handled requests
     23485 23485 22959263 
    Reading: 0 Writing: 1 Waiting: 0 

    Auto-refresh with watch -n 1 curl -s http://127.0.0.1/nginx_status when interactive monitoring is needed.

  6. Check /var/log/nginx/error.log immediately after the run for timeouts, upstream errors, or worker crashes.
    $ sudo tail -n 50 /var/log/nginx/error.log
    2025/12/30 00:27:10 [error] 5922#5922: *10932 client intended to send too large body: 11534336 bytes, client: 127.0.0.1, server: _, request: "POST / HTTP/1.1", host: "127.0.0.1"
    2025/12/30 00:29:29 [error] 6074#6074: *10940 access forbidden by rule, client: 127.0.0.2, server: _, request: "GET /admin/ HTTP/1.1", host: "127.0.0.1"
    ##### snipped #####

    Latency improvements are not meaningful if the run introduces 5xx errors or upstream failures.

  7. Repeat the benchmark after each tuning change while keeping the URL and wrk options identical.
    $ wrk --threads 2 --connections 50 --duration 30s --timeout 10s --latency http://127.0.0.1/ | tee wrk-after-change.txt
    Running 30s test @ http://127.0.0.1/
      2 threads and 50 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency   294.37us    1.42ms  47.83ms   98.42%
        Req/Sec   155.06k    17.51k  175.39k    87.50%
      Latency Distribution
         50%  111.00us
         75%  185.00us
         90%  365.00us
         99%    3.66ms
      9259286 requests in 30.03s, 2.36GB read
    Requests/sec: 308322.41
    Transfer/sec:     80.56MB

    Only one variable should change per comparison run, otherwise the cause of a difference is ambiguous.

  8. Increase --connections in small steps to find the point where latency spikes or socket timeouts appear.
    $ wrk --threads 2 --connections 200 --duration 30s --timeout 10s --latency http://127.0.0.1/ | tee wrk-c200.txt
    Running 30s test @ http://127.0.0.1/
      2 threads and 200 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     1.31ms    7.77ms 168.98ms   98.70%
        Req/Sec   163.66k    20.34k  188.79k    87.42%
      Latency Distribution
         50%  352.00us
         75%  745.00us
         90%    1.42ms
         99%   16.21ms
      9727213 requests in 30.04s, 2.48GB read
    Requests/sec: 323783.38
    Transfer/sec:     84.60MB

    Driving the server into sustained timeouts can trigger autoscaling, upstream circuit breakers, or cascading failures in shared environments.

  9. Compare Requests/sec and percentile latency across saved results to confirm the desired direction of change.
    $ grep -E "Requests/sec:|50%|90%|99%" -n wrk-*.txt
    wrk-after-change.txt:7:     50%  111.00us
    wrk-after-change.txt:9:     90%  365.00us
    wrk-after-change.txt:10:     99%    3.66ms
    wrk-after-change.txt:12:Requests/sec: 308322.41
    ##### snipped #####