# bombardier: a Go HTTP benchmarking CLI that ships as a single binary

> codesenberg/bombardier is a cross-platform HTTP(S) load generator built on fasthttp, with an optional net/http client for HTTP/2. It is a good fit for quick latency and throughput checks from a shell; it is not a scripted scenario runner.

**codesenberg/bombardier** — Fast cross-platform HTTP benchmarking tool written in Go

- Repository: https://github.com/codesenberg/bombardier
- Stars: 6,840 · Forks: 329
- Language: Go
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/codesenberg-bombardier

## What bombardier does that curl and ab do not

bombardier is an HTTP(S) benchmarking tool written in Go. Its purpose is narrow: point it at a URL, tell it how many requests or how long to run, and it prints request rate, latency statistics and HTTP status code counts. The README's own example output shows the shape of that report: a Statistics block with Avg, Stdev and Max for Reqs/sec and Latency, a per-class HTTP code tally, and a Throughput figure in MB/s.

The design decision that separates it from Go's standard tooling is the client. The README states that bombardier uses fasthttp instead of Go's default http library "because of its lightning fast performance." That choice buys throughput on the client side, and it costs protocol fidelity, which the project acknowledges rather than hides. From v1.1 onward you can pass --http1 or --http2 to use net/http instead, described in the README as the option for testing HTTP/2.x services or for a more RFC-compliant client.

The intended user is an engineer at a terminal who wants a number now: a backend developer checking whether a change moved p99, an SRE confirming a staging box can take a burst, or anyone comparing two endpoints without standing up a load-testing cluster. It is not a browser simulator and not a test framework.

## Two HTTP clients behind one flag set

The repository layout supports the README's story. clients.go sits at the top level next to dialer.go, headers.go and proxy_reader.go, and go.mod pins github.com/valyala/fasthttp v1.59.0 as a direct dependency. The net/http path comes from the standard library, so it does not appear in go.mod at all.

That split matters when you read results. fasthttp is not a drop-in equivalent of net/http: it pools and reuses connections aggressively and does not implement every corner of the specification. The README's Known issues section is explicit about one consequence: "it's impossible to pass Host header correctly with fasthttp," and the suggested workaround is to use net/http via --http1 or --http2. If your service routes on Host, or sits behind a virtual host that does, a default fasthttp run is measuring the wrong thing.

The rest of the runtime is assembled from ordinary pieces visible in the file listing: args_parser.go and flags.go for the CLI surface, limiter.go and limiter_barrier_test.go for pacing, rateestimator.go for the live rate display, format.go for output, error_map.go for turning transport failures into readable strings, and completion_barriers.go for coordinating the worker pool. The README's second example shows error_map.go doing its job, listing "dialing to the given TCP address timed out - 5" under an Errors heading alongside the status code counts.

## Installing bombardier and running a first benchmark

The README gives two installation routes. The first is to download a binary from the releases section on GitHub, which is the path for anyone who does not have a Go toolchain. The second is to build from source with Go 1.18 or newer:

```bash
go install github.com/codesenberg/bombardier@latest
```

That places a bombardier binary in your Go bin directory. The invocation form in the README is `bombardier [<flags>] <url>`, so the URL is a positional argument, not a flag.

A fixed-count run is the smallest useful thing to do. The README's first example uses -c for connections and -n for total requests:

```bash
bombardier -c 125 -n 10000000 http://localhost:8080
```

The first line of output confirms the parameters before any traffic is sent: "Bombarding http://localhost:8080 with 10000000 requests using 125 connections." A progress bar follows, then the statistics table. If you would rather bound the run by time, the second README example uses -d with a duration instead of -n:

```bash
bombardier -c 200 -d 10s -l http://ya.ru
```

Here -l adds the latency distribution block, which prints p50, p75, p90 and p99. That block is the reason to reach for this tool over a simple loop: an average latency of 29.86ms with a 99th percentile of 48.00ms, as the README shows, tells a different story than the average alone. Expect the HTTP codes section to be dominated by 3xx if you point it at a redirecting endpoint; the README's own ya.ru run reports 66561 responses in the 3xx class.

## The fasthttp trade-off is the thing to plan around

The Known issues entry about the Host header is not a footnote. It is the clearest statement of what you give up by default. Any benchmark whose result depends on correct Host handling, whether that is virtual hosting, TLS SNI routing, or a reverse proxy that dispatches on the header, is measuring a request the server would not normally receive. The README offers --http1 and --http2 as the workaround, but it does not document a comparison of the two clients' throughput, so you cannot assume the net/http path performs like the fasthttp one.

The second limitation is scope. bombardier sends requests to one URL. Nothing in the README or the repository file listing describes a scenario file, a scripting layer, a request chaining mechanism, or assertions on response bodies. There is a template/ directory and templates.go, and the README does not describe what they do, so treat any claim about templated bodies as unverified until you read the source. If your load test needs a login, a token exchange, then a search, bombardier is the wrong shape of tool.

The third is that bombardier is a client-side generator sharing a machine with whatever else you run. The README's first example reaches 264560 requests per second against a local server. At that rate the generator itself is a significant consumer of CPU and file descriptors, and the -c value sets the connection count directly. A saturated client produces latency numbers that describe the client, not the server.

## How bombardier compares to wrk and k6

wrk takes a similar position: a small C binary, scriptable through Lua, built for high request rates against a single endpoint. The difference is the extension model. wrk exposes a Lua hook so you can generate custom requests and inspect responses inside the same process; bombardier's documented surface is flags only. If you need a request body that varies per request, wrk's scripting is the more direct answer, at the cost of embedding a Lua runtime and its build dependencies.

k6 goes further in the other direction. It is a JavaScript-scripted load tester with scenarios, thresholds and checks, designed so a run can pass or fail as part of a pipeline. bombardier produces a report for a human to read. That is a real difference in kind: k6 can gate a deployment, bombardier cannot, because the README documents no exit-code-on-threshold behaviour.

Where bombardier keeps an edge is deployment friction. It is a Go program that compiles to a single binary with no runtime dependency, and go install works on any platform with a Go toolchain. There is no interpreter, no package manager, no container required. For a one-off measurement on a box you just SSH'd into, that is the whole argument.

## Maintenance, releases and what the MIT licence means here

The repository is not archived, and the last push was on 2026-08-25. The most recent tagged release is v2.0.2 from 2025-03-04, following v2.0 and v2.0.1 in February 2025. The gap between the last tag and the last commit is worth noticing: development activity continues on master without a matching release, so anyone installing with @latest from the Go module proxy gets whatever the default branch last published, not the v2.0.2 tag.

go.mod declares go 1.22 with a toolchain line of go1.24.0, which means building from source pulls a specific toolchain version. The direct dependency list is short and includes fasthttp v1.59.0, kingpin for flag parsing, cheggaaa/pb for the progress bar, and juju/ratelimit for pacing. That is a modest dependency surface for a tool of this kind, and it is the main reason the upgrade cost is low: there is no plugin ecosystem to keep in sync.

The licence is MIT. In practical terms that permits commercial and internal use, modification and redistribution provided the copyright notice and permission notice are retained. It offers no patent grant and no warranty, and it imposes no copyleft obligation on your own code. This is a description of the licence text, not legal advice; if you are redistributing bombardier inside a product, have your own counsel read the LICENSE file.

## Conclusion

Use bombardier when you want a single binary that turns a URL into request rate and latency percentiles in one command, and when fasthttp's non-RFC behaviour is acceptable. Do not use it if you need multi-step user journeys, assertion-based pass/fail, or a Host header that fasthttp will not send; switch to --http1 or --http2 for that last case. Before adopting it, run one command against a local server and check that the reported latency distribution and throughput match what your own server logs show, since the README documents no calibration procedure.

## FAQ

### How do I install bombardier?

Either download a prebuilt binary from the GitHub releases section, or build it from source with Go 1.18 or newer using go install github.com/codesenberg/bombardier@latest. The README lists both routes.

### How do I use bombardier against a URL?

Pass the URL as a positional argument and control the run with flags such as -c for connections, -n for a fixed request count, or -d for a duration. Adding -l prints the latency distribution with p50, p75, p90 and p99.

### How do I use the deadline flag in bombardier?

The README's second example uses -d 10s to run the benchmark for a fixed duration instead of a fixed request count. The output line confirms the duration, showing "Bombarding <url> for 10s using 200 connections" in that example.

## Sources

- [codesenberg/bombardier on GitHub](https://github.com/codesenberg/bombardier)
- [Issues](https://github.com/codesenberg/bombardier/issues)
- [License: MIT](https://github.com/codesenberg/bombardier/blob/master/LICENSE)
- [README](https://github.com/codesenberg/bombardier/blob/master/README.md)
- [Releases](https://github.com/codesenberg/bombardier/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/codesenberg-bombardier
