Open-source project
linkxzhou/http_bench avatar
linkxzhou/http_bench

http_bench: load testing HTTP/1, HTTP/2, HTTP/3 and WebSocket from one Go binary

golang HTTP stress testing tool, support single and distributed, http/1, http/2 and http/3.

763 stars31 forksGoLicense varies

At a glance

What is it?
http_bench is a flag-driven Go load testing tool that covers HTTP/1, HTTP/2, HTTP/3 and WebSocket in single-node or distributed form, with a polling web dashboard and template variables for request payloads. It is a good fit for quick regression checks, but its TLS verification default and unfinished stepping feature deserve a close look.
Who is it for?
http_bench suits backend engineers who want a single static binary to put repeat load on an HTTP/1, HTTP/2, HTTP/3 or WebSocket endpoint and read back QPS, latency and error distributions without scripting a benchmark harness. It is the wrong choice if your workload needs ramping profiles, if you need a licence you can clear with legal, or if you want TLS verification on by default.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One static binary that puts load on four protocol flavours

http_bench exists for the case where a service has just changed its protocol stack and someone needs to know how it behaves under concurrent connections rather than under a single manual request. It is a load testing tool written in Go, and the same executable handles HTTP/1, HTTP/2, HTTP/3 and WebSocket traffic, in either a single-node or a distributed arrangement.

The approach is deliberately narrow. Everything is driven by command line flags, there is no scripting runtime and no configuration file format, and the load generator is a single static binary that a container image can carry around. That is a different shape from tools that expose their entire workflow through a web interface or require a test script to be maintained alongside the system under test. The trade-off is that the shape you get back is mostly numeric: request counts, per-status-code curves, latency and error distributions, all of which the tool records but none of which it interprets for you.

The audience is narrow too. This suits an engineer doing a regression check against a local or staging service, or a person who needs to compare HTTP/1 against HTTP/3 against WebSocket on the same endpoint in one afternoon. It is less suited to a team that wants scenario modelling, because the feature checklist in the documentation leaves stepping stress testing unchecked.

Getting the binary with go get, a clone, or the Alpine image

Installation is a Go install. The project states that Go 1.20 or newer is required, and offers two ways to get the binary. The first pulls the module into an existing Go workspace:

bash
go get github.com/linkxzhou/http_bench

The second clones the repository and compiles it in place:

bash
git clone [email protected]:linkxzhou/http_bench.git
cd http_bench
go build .

A compiled `http_bench` executable is what every later example expects on the command line, so the clone and build path is the one to follow if you want to work from source. The `go.mod` file declares `go 1.20` and pulls in `github.com/gorilla/websocket` for WebSocket support and `github.com/quic-go/quic-go` for HTTP/3.

There is also a container path. The `Dockerfile` builds in a Go 1.21 Alpine image and ships the binary in a final Alpine layer, listening on port 9000:

dockerfile
FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY . .
RUN go build -o http_bench .

The final stage copies that binary forward, marks it executable and sets the entrypoint to bind on all interfaces:

dockerfile
FROM alpine:latest

WORKDIR /app
COPY --from=builder /app/http_bench .
RUN chmod +x http_bench

EXPOSE 9000
ENTRYPOINT ["./http_bench", "-listen", "0.0.0.0:9000"]

Note what that entrypoint does: it starts the tool in listening mode on every interface, not a benchmark run. Anyone deploying that image has to understand the dashboard and worker API before exposing it.

The shared QPS budget and the rule that -d beats -n

The option list is where the real behaviour of the tool shows, because most of these flags change how the load is shaped rather than how results are printed.

`-n` sets an exact total request count and `-d` sets a duration such as `10s` or `2m`. When both are present, `-d` wins and the run continues for the full duration, so `-n` becomes irrelevant rather than a per-worker share. `-c` sets the concurrency level. The flag worth understanding is `-q`, which is a global requests-per-second limit shared by every concurrent worker, so the ceiling applies to the run as a whole rather than to each connection.

Two flags have sharp edges. `-t` takes a duration such as `500ms` or `2s`, not a bare millisecond integer, so a value like `500` will not mean what it looks like it means. `-insecure` defaults to `true` for compatibility, which means certificate verification is skipped unless you pass `-insecure=false`.

Output is selected with `-o`, which accepts `summary`, `csv` or `html`. `-H` adds a request header and can be repeated, `-file` loads requests from a `.http` file, and `-verbose` takes a level from `0=TRACE` through `4=ERROR` with a default of `3`.

Selecting http2, http3 or ws with the -http flag

Protocol selection is one flag, `-http`, and the same request shape is reused across the choices. HTTP/2 runs like this:

bash
./http_bench -d 10s -c 10 -http http2 -m POST "http://127.0.0.1/test1" -body "{}"

HTTP/3 uses the identical form with the value changed to `http3`:

bash
./http_bench -d 10s -c 10 -http http3 -m POST "http://127.0.0.1/test1" -body "{}"

WebSocket testing uses `ws` and, in the documented example, points at a `ws://` URL:

bash
./http_bench -d 10s -c 10 -http ws "ws://127.0.0.1" -body "{}"

Because the protocol is a parameter rather than a separate binary, the useful comparison is straightforward: run the same endpoint at the same concurrency with `http2` and `http3` and read the two latency distributions side by side. The documentation does not describe negotiation behaviour, fallback to HTTP/1 when HTTP/3 is refused, or how a failed upgrade is counted, so an HTTP/3 result should be checked against a known-good transport before it is quoted.

For plain HTTP/1 the two equivalent forms are shown, one with an explicit `-url` and one with the target as a bare argument:

bash
./http_bench -n 1000 -c 10 -m GET -url "http://127.0.0.1/test1"
./http_bench -n 1000 -c 10 -m GET "http://127.0.0.1/test1"

Controller and worker endpoints in distributed mode

Distributed mode splits the tool into two roles that run on different machines. A worker listens and does nothing until it is told what to send. The controller sends the load and collects the results back.

Workers are started with `-listen`, and the documented example starts two of them on separate ports:

bash
# Step 1: Start worker instances on different machines
./http_bench -listen "127.0.0.1:12710" -verbose 1
./http_bench -listen "127.0.0.1:12711" -verbose 1

The controller then names those workers with the repeatable `-W` flag and points at the target URL:

bash
# Step 2: Run the controller to coordinate the test
./http_bench -c 1 -d 10s "http://127.0.0.1:18090/test1" -body "{}" -W "127.0.0.1:12710" -W "127.0.0.1:12711" -verbose 1

The worker API lives at `/api`, and the documentation states that worker responses carry a merged snapshot plus per-worker success and failure detail when the run is dispatched by the controller. That per-worker breakdown is the part worth using, since a merged snapshot alone hides the case where one machine saturated while the others were idle.

One gap the documentation leaves open: the controller in the example uses `-c 1`, which means one concurrent worker on the coordinating side. How that value is apportioned across the named workers is not described, so treat the concurrency figure in a distributed report as the controller's own setting and confirm the effective load per node from the per-worker results.

The dashboard polls the worker API instead of streaming metrics

The optional dashboard is started by the same binary in listening mode:

bash
# Step 1: Start the dashboard server
./http_bench -listen "127.0.0.1:12345" -verbose 1

Opening `http://127.0.0.1:12345` in a browser gives the interface. The mechanism matters here. The documentation says the dashboard starts the benchmark asynchronously on the worker node and then polls the `/api` worker endpoint for live metrics, so what you see on screen is a sampled view rather than a pushed event stream, and the sampling interval is adjustable with a default of 2000 ms. A run shorter than a couple of refresh cycles will finish with very few frames, which is worth knowing before you interpret an empty-looking chart as a failure.

What the interface provides is a real-time QPS curve and a per-status-code curve, summary cards for average latency, average QPS, the fastest and slowest request, and total errors, plus a latency distribution histogram and an error distribution pie chart. You can start and stop a run in one click, and the final metrics frame is rendered automatically when the run completes. The interface switches between Chinese and English.

There is a separate static deployment too. A `package.json` at the top level describes a private Vercel dashboard package whose own description states that the Go binary is not built there, and `vercel.json` sits alongside it, with the project homepage pointing at a hosted dashboard.

Template functions put different data in every request

Caching layers, unique-constraint tables and idempotency caches all collapse under a benchmark that sends the identical payload every time, so http_bench supports template functions that expand inside URL parameters and request bodies.

Sums and random values are covered. `intSum` adds integers, `random` returns an integer within a range, `randomDate` and `date` produce formatted dates, `randomString` builds an alphanumeric string of a given length, and `randomNum` builds a numeric one:

bash
./http_bench -c 1 -n 1 "https://127.0.0.1:18090?data={{ intSum 1 2 3 4}}" -verbose 0
./http_bench -c 1 -n 1 "https://127.0.0.1:18090" -body "data={{ random 1 100000 }}" -verbose 0

A UUID function is available, and the pipe form shows it escaped for use in a URL:

bash
./http_bench -c 1 -n 1 "https://127.0.0.1:18090?data={{ UUID | escape }}" -verbose 0

The `escape` function handles special characters, and the README notes it applies to string values.

A `.http` file gives the same control at the request level, and can be combined with a duration:

bash
./http_bench -n 1000 -c 10 -file requests.http
./http_bench -d 10s -c 10 -file requests.http

How a template is parsed, and whether an unknown function raises an error or leaves the literal text in place, is not described in the documentation, so a typo inside `{{ ... }}` should be confirmed in the request log rather than assumed to work.

Skipped TLS verification and an unchecked stepping feature

Two constraints stand between this tool and an unattended benchmark job.

The first is the `-insecure` default. The documentation states plainly that TLS verification is skipped by default for compatibility, and that `-insecure=false` should be used in production. That default is convenient against a staging endpoint with a self-signed certificate, and dangerous everywhere else, because a benchmark that silently accepts any certificate tells you nothing about handshake behaviour. Every command that hits a real TLS endpoint should carry `-insecure=false` explicitly.

The second is scope. Stepping stress testing appears as an unchecked item in the feature list, so there is no way to ramp concurrency through stages and watch where the service bends. Everything is flat: a fixed `-c` and a fixed `-q` for the whole run. If your question is about the shape of a degradation curve rather than a single operating point, this tool does not answer it.

On maintenance and licensing, the record is thin but not empty. The repository is not archived, the last push was on 2026-09-21, and the three most recent releases are v3.04 on 2026-09-21 adding execution history, v3.03 on 2026-09-14 fixing a packaging problem, and v3.02 on 2026-09-14 fixing a Vercel deployment problem. Two of those three releases are fixes to packaging and hosting rather than to load generation, which is what a project with a static dashboard and a Docker image tends to accumulate. The licence is unknown: no licence file appears among the top-level repository entries, and the project description records the licence as unknown, so there is nothing here to check the terms of use against.

Editorial conclusion

http_bench suits backend engineers who want a single static binary to put repeat load on an HTTP/1, HTTP/2, HTTP/3 or WebSocket endpoint and read back QPS, latency and error distributions without scripting a benchmark harness. It is the wrong choice if your workload needs ramping profiles, if you need a licence you can clear with legal, or if you want TLS verification on by default. Before the first run, confirm that your Go toolchain is at least 1.20, decide whether `-insecure=false` belongs in your command line, and check that the dashboard port you expose with `-listen` is not reachable from an untrusted network.

Frequently asked questions

How do I install http_bench?

The project states that Go 1.20 or newer is required, then gives either `go get github.com/linkxzhou/http_bench` or a clone followed by `go build .` in the repository root. A Dockerfile is also included, and its final stage runs the binary with `-listen 0.0.0.0:9000`.

How do I run a distributed http_bench test?

Start worker instances with `-listen` on each machine, then run the controller against the target URL with a repeatable `-W` flag naming each worker address. Worker responses come from the `/api` endpoint and include a merged snapshot plus per-worker success and failure detail.

Does http_bench verify TLS certificates?

No. The `-insecure` option defaults to `true` for compatibility, so certificate verification is skipped unless you pass `-insecure=false`, which the documentation recommends for production use.

Official sources

  1. Issues
  2. linkxzhou/http_bench on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/linkxzhou-http-bench.svg)](https://hysenlabs.com/projects/linkxzhou-http-bench)

Was this useful?