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.

INEZA Felin-Michel

INEZA Felin-Michel

6 July 2026

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

Apidog for Enterprise

On-Premises Deploy

SSO & RBAC

SOC 2 Compliant

Explore Apidog Enterprise

You shipped an endpoint. It returns the right JSON in Postman. But you have no idea what happens when 200 clients hit it at once. Does it hold 500 requests per second, or does latency fall apart at 50? ApacheBench answers that question in one command.

ApacheBench, invoked as ab, is a command-line tool that fires a fixed number of HTTP requests at a single URL and reports throughput and latency. It ships with Apache HTTP Server and has been around for decades. It’s small, it’s fast to run, and it gives you a quick read on how much load one endpoint can take.

This guide covers installing ab, running a basic test, testing POST endpoints, reading the output, and knowing where ab stops being the right tool. Every command here maps to a flag documented in the official Apache ab reference.

button

What ApacheBench is and where it comes from

ab is a benchmarking client bundled with Apache HTTP Server. The name is short for ApacheBench. It opens connections to one URL, sends requests as fast as your concurrency setting allows, times each response, and prints a summary.

It measures one thing well: how many requests per second a single endpoint can serve, and how response time is distributed under load. That’s throughput and latency for one URL.

What it does not do matters as much. ab doesn’t check whether the response body is correct. It doesn’t validate a schema or assert on a field. It doesn’t walk a multi-step flow like login, then fetch, then update. It hits one URL and counts. Keep that scope in mind and ab stays useful. Expect more and you’ll be disappointed.

If you want a broader view of the discipline ab fits into, see what API load testing is and why API response time matters.

Installing ab

ab comes packaged with Apache utilities. The package name differs by platform.

On Debian and Ubuntu, install the apache2-utils package:

sudo apt-get update
sudo apt-get install -y apache2-utils

On CentOS, RHEL, and Fedora, the package is httpd-tools:

# CentOS 7
sudo yum install -y httpd-tools

# Fedora and CentOS 8+
sudo dnf install -y httpd-tools

On macOS, ab is already present because the system ships with Apache. Check it:

ab -V

You should see a version line like Version 2.3. If the command is missing on macOS, install it through Homebrew’s Apache formula. Once ab -V prints a version, you’re ready.

Running a basic load test

The two flags you’ll use every time are -n and -c.

Here’s 1000 requests, 50 at a time, against a JSON endpoint:

ab -n 1000 -c 50 https://api.example.com/v1/users

Note the trailing path. ab needs a full URL with a path. If you point it at a bare host with no path, it errors out. For the root, use a trailing slash: https://api.example.com/.

Real clients reuse TCP connections instead of opening a fresh one per request. Add -k to enable HTTP KeepAlive so ab reuses connections within a session:

ab -n 1000 -c 50 -k https://api.example.com/v1/users

The -k run is usually closer to how a browser or a well-behaved API client behaves, since it avoids paying the connection setup cost on every request. Compare the two numbers to see how much of your latency is connection overhead.

You can also cap the run by time instead of count. -t sets a maximum number of seconds and implies -n 50000 internally, so the test stops at whichever limit you hit first:

ab -t 30 -c 50 -k https://api.example.com/v1/users

That runs for up to 30 seconds at concurrency 50. Handy when you want a fixed-duration read rather than a fixed count.

Testing a POST endpoint

Most API work isn’t GET. To test a POST, put the request body in a file and pass it with -p. You also have to set the content type with -T, or the server will reject the body.

Create the payload:

cat > payload.json <<'EOF'
{"name": "Ada Lovelace", "email": "ada@example.com"}
EOF

Then send it:

ab -n 500 -c 25 -k \
  -p payload.json \
  -T application/json \
  https://api.example.com/v1/users

The -p flag names the file containing the POST body. The -T flag sets the Content-Type header. The default content type is text/plain, which is almost never what a JSON API wants, so set -T application/json explicitly.

If your endpoint needs auth or other headers, add them with -H. Repeat the flag for each header:

ab -n 500 -c 25 -k \
  -p payload.json \
  -T application/json \
  -H "Authorization: Bearer YOUR_TOKEN" \
  https://api.example.com/v1/users

For a refresher on building JSON request bodies by hand, see how to POST JSON data with curl. The same body works as an ab payload file.

Reading the output

A run prints a block of numbers. Here’s a trimmed example:

Concurrency Level:      50
Time taken for tests:   4.212 seconds
Complete requests:      1000
Failed requests:        0
Non-2xx responses:      0
Requests per second:    237.42 [#/sec] (mean)
Time per request:       210.598 [ms] (mean)
Time per request:       4.212 [ms] (mean, across all concurrent requests)
Transfer rate:          142.31 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        5   18   9.4     16      64
Processing:    22  189  41.2    182     310
Waiting:       21  177  39.8    171     295
Total:         31  207  42.7    201     338

Percentage of the requests served within a certain time (ms)
  50%    201
  66%    221
  75%    236
  90%    268
  95%    291
  99%    324
 100%    338 (longest request)

Read these fields first:

The Percentage of the requests served within a certain time table is the most useful part. It’s a percentile breakdown. The 50% row is your median. The 95% and 99% rows show the slow tail. In the example, half of requests finished by 201ms, but 1% took 324ms or longer. Tail latency is where real users get hurt, so watch the 95th and 99th percentiles more than the mean.

Want the raw per-request numbers for a chart? Add -e results.csv to write a CSV of percentile-to-time pairs, or -g results.tsv for a gnuplot-friendly file.

Where ab stops being the right tool

ab is deliberately narrow. Its limits are worth stating plainly so you don’t misuse it.

One URL per run. ab hammers a single endpoint. It can’t script a sequence like authenticate, create a resource, then read it back. Real user journeys touch many endpoints in order, and ab has no concept of that.

No correctness checks. ab counts status codes and byte lengths. It doesn’t parse your JSON or assert that a field equals an expected value. A response can be structurally wrong and still count as a success. Load numbers are not a substitute for a passing test suite.

Dated HTTP handling. The official docs state ab does not implement HTTP/1.x fully and only accepts some expected forms of responses. It has no HTTP/2 support. For servers that behave differently under HTTP/2, ab won’t reflect that.

Single-machine load. ab runs from one box and one process. Past a certain concurrency, you measure your own client’s limits instead of the server’s. The docs even warn that ab’s own parsing can show up as a bottleneck in profiles.

None of this makes ab bad. It makes it a specific tool: a fast throughput probe for one endpoint. When you need scripted flows, distributed load, or HTTP/2, reach for something built for that. JMeter handles multi-step scenarios and distributed runs, and there’s a broader roundup of load testing tools if you’re comparing options.

Where functional testing fits

Throughput is one axis. Correctness is another, and ab doesn’t touch it. Before you load test an endpoint, you want to know it returns the right data with the right shape under normal conditions. That’s functional and contract testing, and it’s a different job.

This is where a full API platform earns its place next to ab. Apidog lets you build test scenarios with visual assertions, chain requests into multi-step flows, and validate responses against your schema, the things ab can’t do by design. You save those scenarios once and run them anywhere.

For continuous integration, the Apidog CLI runs your saved scenarios headlessly. Install it with Node:

npm install -g apidog-cli

Then run a saved scenario or suite against a chosen environment, emitting reports your CI can archive:

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

The -t flag targets a saved scenario, folder, or suite by id. The -e flag selects the environment. The -r flag picks one or more reporters from cli, html, json, and junit. For data-driven runs, add -d (or --iteration-data) with a data file path or test data id. The CLI is headless and runs saved scenarios, so it fits any CI step that can run Node. It’s not a request sender and not a load generator, it runs the functional tests ab was never meant to run.

A healthy setup uses both. Run Apidog scenarios to prove the endpoint is correct. Run ab to prove it’s fast enough. For the correctness side, see the Apidog performance testing guide and the general API performance testing tutorial.

FAQ

Is ApacheBench only for Apache servers?

No. Despite the name, ab sends plain HTTP and HTTPS requests to any server. It works against Nginx, Node, Go, Python, or any host that speaks HTTP. The Apache tie is only that the tool ships with Apache HTTP Server.

What concurrency and request count should I use?

Start low and climb. Try -n 1000 -c 10, then raise -c to 25, 50, 100, and watch where requests per second stops rising and failed requests start appearing. That inflection point is roughly where the endpoint saturates. Match your peak concurrency to expected real traffic rather than picking a large round number.

Why are my failed requests high when the API works fine?

ab flags a request as failed if the response length varies between requests, which is normal for dynamic JSON. Add the -l flag to stop counting length differences as failures. Then check the Non-2xx responses line to see if there are real errors underneath.

Can ab test an endpoint that needs a login token?

Yes, if you already have a token. Pass it with -H "Authorization: Bearer YOUR_TOKEN". What ab can’t do is log in first to obtain a fresh token, then use it. That’s a multi-step flow, and you need a scenario-based tool like Apidog for it.

Does ab support HTTP/2?

No. ab speaks HTTP/1.x and, per the official docs, doesn’t even implement that fully. If your server’s behavior under HTTP/2 matters, use a load tool with HTTP/2 support instead.

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

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

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

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.

6 July 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs

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