CLI tool
hatoo/oha avatar
hatoo/oha

oha: an HTTP load generator that shows its work while it runs

Ohayou(おはよう), HTTP load generator, inspired by rakyll/hey with tui animation.

10,552 stars298 forksRustMIT

At a glance

What is it?
A small Rust program built on tokio and ratatui that hammers a URL and animates the results live. Its best argument is how many protocol details it exposes as flags.
Who is it for?
oha earns its place next to hey and wrk when you want a load test you can watch while it runs and a rate limiter that is honest about scope. It fits a single URL, a list of URLs from a file, or a generated URL pattern, and it does not fit scenario scripting, multi-step workflows or anything that needs assertions on response bodies, because none of that is in the option list.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 26 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

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

Editorial analysis

A terminal program, not a load testing service

The README's opening description is short enough to be useful: a tiny program that sends some load to a web application and shows realtime tui, inspired by rakyll/hey. That inspiration matters because it fixes the shape of the tool. oha is a single command you point at a URL, not a platform with agents, scenarios and a dashboard. Nothing in the repository suggests a server mode, and the whole feature surface is a list of flags in `--help`.

The implementation is Rust on tokio with ratatui and crossterm doing the drawing, and hyper handling the HTTP client with both http1 and http2 features enabled. The Cargo.toml is honest about the floor: edition 2024 and a rust-version of 1.88. The repository topics list load-generator, load-testing, http2 and tui, which is an accurate summary.

One detail worth noticing is in the summary output rather than the feature list. The v1.16.0 release notes mention removing TenSeconds and TenMinutes time scales from the summary, which is the kind of small presentation fix that tells you the tool is actively shaped by people who read their own output closely. The last push was on 2026-09-10 and the license is MIT.

Installing through cargo, a package manager or a container

The primary path is cargo, and the README asks for stable Rust with make and cmake present:

bash
cargo install oha

Cargo is also where the compile-time choices live. TLS defaults to rustls and can be swapped for native-tls, and there are feature flags for VSOCK and for experimental HTTP/3:

bash
cargo install --no-default-features --features native-tls oha
bash
cargo install --features vsock oha

The package manager routes are one line each, which is the fastest way to judge the project's reach:

bash
brew install oha
bash
pacman -S oha

On Windows the README documents a winget package named hatoo.oha, and there is a Debian repository with its own keyring instructions. Pre-built binaries are published on the release page, and the repository also has a publish workflow per commit, so a nightly binary is a real option rather than a theoretical one. The HTTP/3 path is explicitly gated behind the H3 library from the Hyper developers and stays experimental for as long as H3 does, which is the honest way to describe it.

Reading -n, -c, -p and the -q flag that differs from hey

If you are moving over from hey, one flag will mislead you. The README calls it out directly: `-q` is a total query per second limit across the whole run, not a per-worker rate as in hey. That difference decides whether a test ramps gently or hits the target the instant it starts.

The core options are worth reading together:

bash
-n <N_REQUESTS>
-c <N_CONNECTIONS>
-p <N_HTTP2_PARALLEL>
-q <QUERY_PER_SECOND>
-z <DURATION>

`-n` defaults to 200 requests and accepts suffixes such as 10k and 1m. `-c` defaults to 50 concurrent connections, and the help text is candid that you should raise your open file limit for large values. `-p` multiplies concurrency on HTTP/2, since oha runs c times p workers in total, which is how you get real parallelism out of a connection-multiplexed protocol.

Duration has a genuine asymmetry. On HTTP/1, hitting `-z` aborts in-flight requests and counts them as aborted due to deadline. On HTTP/2 it waits for them instead, and `-w` is ignored. If you are comparing results across protocol versions, that difference is a confounder, not a footnote.

Coordinated omission, which is the flag serious users care about

There is one option in this list that reflects a real methodological problem rather than a convenience:

bash
--latency-correction

Coordinated omission is the distortion that appears when a load generator keeps its request rate fixed but stops issuing requests when responses slow down, so the latency you record describes only the requests that happened to be in flight during the slowdown. Correcting for it produces numbers that look worse and are more accurate. The catch is documented too: the flag is ignored unless `-q` is set, because there is no fixed rate to fall behind in an unthrottled run.

The rate limiting options around it are more capable than a single QPS number. Burst mode introduces a delay between a predefined number of requests, with `--burst-rate` defaulting to 1, and both burst flags are ignored when a QPS limit is in force.

bash
--burst-delay <BURST_DURATION>
--burst-rate <BURST_REQUESTS>

For request shapes rather than pacing, `--rand-regex-url` generates URLs from a pattern using the rand_regex crate, and the README is specific that the dot is disabled per query. It also warns that dynamic scheme, host and port combined with keep-alive do not work well, which is the kind of limitation that only gets written down after someone lost an afternoon.

Bodies, forms, auth and proxy headers

Most load tests are not GET tests, and the body flags cover the common cases without inventing a config format. A literal string goes to `-d`, a file to `-D`, and a file read line by line to `-Z` for the case where each line is a different payload:

bash
-d <BODY_STRING>
-D <BODY_PATH>
-Z <BODY_PATH_LINES>

For form posts, `-F` follows curl's convention, with `-F 'name=value'` and `-F 'file=@path/to/file'`, and `-T` sets the content type. If your endpoint needs a JSON content type and oha is guessing wrong, that is the flag to reach for.

bash
-F, --form <FORM>
-T <CONTENT_TYPE>

Headers, proxy headers and auth each have their own option. `-H` takes a name and value pair, `--proxy-header` does the same for the proxy connection, and `-a` accepts either basic auth credentials or AWS access key and secret key, with `--aws-session` for the session token. Timeouts are split: `-t` covers each request and `--connect-timeout` defaults to 5s for establishing a connection.

Two smaller flags are worth knowing. `--no-tui` drops the animation for CI logs, and `--fps` sets the frame rate, defaulting to 16.

PGO builds, a layered container and recent releases

oha has an opinion about optimisation that most small tools skip. The README documents building with profile-guided optimization through a script:

bash
bun run pgo.js

That needs the cargo-pgo package, installed with `cargo install cargo-pgo`, and the resulting binary lands at `target/[target-triple]/pgo/oha`. There is a `pgo/` directory and a `Cross.toml` in the tree, so cross-compilation and tuned builds are both first-class enough to have files.

The Dockerfile takes the same layering discipline with cargo-chef, so dependency compilation is cached separately from the final binary:

bash
RUN cargo chef cook --release --no-default-features --features rustls --recipe-path recipe.json
bash
RUN cargo build --release --no-default-features --features rustls --bin oha
bash
RUN strip /app/target/release/oha

The runtime stage is fedora-minimal rather than alpine, runs as user 65535 rather than root, and sets the binary as the entrypoint, which is a leaner image than the base layer name might suggest.

The releases show the same attention. v1.16.0 on 2026-08-23 tuned HTTP/3 client performance and added a `--worker-threads` flag for the runtime thread count. v1.15.0 on 2026-07-11 added shell completion, capped connections to the minimum needed, replaced a tokio select loop with a timeout, added metrics to the output, fixed a NO_COLOR regression and changed the default redirect limit to 0. That last one can silently change your results if you were following redirects before.

Editorial conclusion

oha earns its place next to hey and wrk when you want a load test you can watch while it runs and a rate limiter that is honest about scope. It fits a single URL, a list of URLs from a file, or a generated URL pattern, and it does not fit scenario scripting, multi-step workflows or anything that needs assertions on response bodies, because none of that is in the option list. Two details deserve attention before the first run: `-q` is a global query per second limit rather than a per-worker one, and `-z` aborts in-flight requests on HTTP/1 unless you pass `-w`. The last push was on 2026-09-10 and version 1.16.0 shipped on 2026-08-23, so build the rustls default and read `oha --help` start to finish rather than trusting muscle memory from another tool.

Frequently asked questions

What does oha mean?

The project is named after the Japanese greeting it renders in its own title and package description. The program itself is an HTTP load generator written in Rust, inspired by rakyll/hey, that shows results in a realtime terminal interface instead of printing only at the end.

How do I install oha on macOS or Ubuntu?

With cargo and stable Rust, run cargo install oha. Package managers are documented too, brew install oha on macOS and pacman -S oha on Arch, with a winget package on Windows and a Debian repository with its own keyring. A container image is published as ghcr.io/hatoo/oha, or you can build it locally with docker build and run it with host networking.

How is oha different from rakyll/hey?

The clearest difference is rate limiting. In oha the -q option is a total query per second limit for the whole run, whereas in hey it applies to each worker. oha also ships a realtime terminal interface with --no-tui and --fps for non-interactive use, burst pacing options, corrected latency for coordinated omission, and a profile-guided optimization build path.

Official sources

  1. hatoo/oha on GitHub
  2. Issues
  3. License: MIT
  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/hatoo-oha.svg)](https://hysenlabs.com/projects/hatoo-oha)