Raptor
Raptor is a high-performance, preloading, pre-forking, multi-threaded Ruby 4+ web server implementing Rack 3.2+, using NIO for non-blocking I/O and Ractors for parallel HTTP/1.1 and HTTP/2 parsing via native C extensions, which also implement HPACK compression.
[!NOTE] Your application does not need to be Ractor-safe. Ractors handle protocol-level work only; your Rack application is invoked on a thread pool, so any thread-safe Rack app (including Rails) works as-is.
Reference documentation is published at https://joshuay03.github.io/raptor.
Installation
Install the gem and add to the application's Gemfile by executing:
bundle add raptor
If bundler is not being used to manage dependencies, install the gem by executing:
gem install raptor
Usage
# hello_world.ru
# frozen_string_literal: true
run proc { |_env| [200, { "content-type" => "text/plain" }, ["Hello, World!"]] }
> bundle exec raptor -w 4 -t 3 hello_world.ru
[Raptor 76577|Main|Main] Cluster initializing:
[Raptor 76577|Main|Main] ├─ Version: 0.9.0
[Raptor 76577|Main|Main] ├─ Ruby Version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
[Raptor 76577|Main|Main] ├─ Environment: development
[Raptor 76577|Main|Main] ├─ Master PID: 76577
[Raptor 76577|Main|Main] │ └─ 4 worker processes
[Raptor 76577|Main|Main] │ ├─ 1 server thread
[Raptor 76577|Main|Main] │ ├─ 1 reactor thread
[Raptor 76577|Main|Main] │ ├─ 1 pipeline ractor
[Raptor 76577|Main|Main] │ ├─ 1 pipeline collector thread
[Raptor 76577|Main|Main] │ ├─ 3 worker threads
[Raptor 76577|Main|Main] │ └─ 1 stats thread
[Raptor 76577|Main|Main] └─ Listening on 0.0.0.0:9292
[Raptor 76579|Main|Main] Worker 0 booted
[Raptor 76580|Main|Main] Worker 1 booted
[Raptor 76581|Main|Main] Worker 2 booted
[Raptor 76582|Main|Main] Worker 3 booted
> curl localhost:9292
Hello, World!%
Also works with rackup and rails server:
> bundle exec rackup -s raptor hello_world.ru
> bundle exec rails server -u raptor
Configuration
Raptor accepts configuration via command-line flags, a Ruby config file, or both (CLI flags override config file
values). Run bundle exec raptor --help for the full flag list.
The config file is a Ruby file that evaluates to a hash of options. By default Raptor loads raptor.rb then
config/raptor.rb from the working directory; pass -c PATH to point at a specific file. Settings are nested under
connection: (shared across protocols), http1: (HTTP/1.1-specific), and http2: (HTTP/2-specific).
# raptor.rb
{
binds: ["tcp://0.0.0.0:9292"],
socket_backlog: 1024,
drain_accept_queue: false,
workers: 4,
ractors: 1,
threads: 3,
chdir: nil,
environment: nil,
connection: {
first_data_timeout: 30,
chunk_data_timeout: 10,
write_timeout: 5,
max_body_size: nil,
body_spool_threshold: 1024 * 1024,
},
http1: {
persistent_data_timeout: 65,
max_keepalive_requests: 100,
},
http2: {
max_concurrent_streams: 100,
},
worker_boot_timeout: 60,
worker_timeout: 60,
worker_drain_timeout: 25,
worker_shutdown_timeout: 30,
stats_file: "tmp/raptor.json",
pid_file: nil,
stdout_file: nil,
stderr_file: nil,
access_log_file: nil,
}
Bindings
Raptor accepts multiple binds: URIs across three schemes.
tcp://host:portfor TCP. Host can be a specific IP,0.0.0.0/[::], orlocalhost(expanded to both IPv4 and IPv6 loopback addresses).unix:///path/to/socketfor a Unix domain socket. Stale sockets left by crashed processes are cleaned up automatically.ssl://host:port?cert=/path/to.crt&key=/path/to.keyfor TLS. HTTP/1.1 and HTTP/2 are negotiated via ALPN.
Multiple binds can be combined freely.
Signals
Send to the master process.
| Signal | Effect |
|---|---|
INT |
Graceful shutdown |
TERM |
Graceful shutdown |
HUP |
Reopen stdout_file, stderr_file, and access_log_file |
USR1 |
Phased restart (rolling worker replacement) |
USR2 |
Hot restart (re-exec master, inheriting listening sockets) |
Restarts
- Phased restart (
USR1) replaces workers one at a time, waiting for each new worker to boot before retiring the previous one. The master process keeps running, so existing workers continue serving until they are individually replaced. Use to pick up code changes that don't affect the master's boot path. - Hot restart (
USR2) re-execs the master process with its original command line, inheriting the listening sockets so accepted connections continue to be served across the swap. The successor master re-runs initialization from scratch. Use to pick up changes that affect master-level state (config layout, dependency upgrades, Raptor itself).
systemd
Raptor implements socket activation (LISTEN_FDS) and sd_notify, so it integrates cleanly with Type=notify units.
When the socket unit is active, systemd hands the pre-bound listening file descriptors to Raptor, which serves them in
place of binds:. READY=1, STOPPING=1, and RELOADING=1 lifecycle messages are emitted automatically.
# /etc/systemd/system/myapp.socket
[Socket]
ListenStream=0.0.0.0:9292
[Install]
WantedBy=sockets.target
# /etc/systemd/system/myapp.service
[Service]
Type=notify
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/bundle exec raptor
ExecReload=/bin/kill -USR2 $MAINPID
KillMode=mixed
Stats
Each worker writes per-worker stats (request count, busy threads, backlog, last check-in) to shared memory and to a
JSON file (default tmp/raptor.json; set via stats_file).
> bundle exec raptor stats
Master PID: 91348
Worker 0 (phase 0): pid=91350, requests=1234, busy=2/3, backlog=0, booted, last_checkin=10:42:01
Worker 1 (phase 0): pid=91351, requests=1199, busy=1/3, backlog=0, booted, last_checkin=10:42:01
...
(Micro) Benchmarks
Raptor 0.9.0 vs Puma 8.0.2, median of 3 runs across two workload profiles: IO-bound (sleep for a random 5-50ms then return small JSON) and CPU-bound (serialise a JSON array of 20-200 items).
| Protocol | Workload | Raptor | Puma | +/- vs Puma |
|---|---|---|---|---|
| HTTP/1.1 | IO | 0.4k req/s | 0.4k req/s | +3.4% |
| HTTP/1.1 | CPU | 11.5k req/s | 9k req/s | +27.6% |
| HTTP/1.1 (keep-alive) | IO | 0.4k req/s | 0.4k req/s | +1.5% |
| HTTP/1.1 (keep-alive) | CPU | 28.8k req/s | 26k req/s | +10.9% |
| HTTP/2 | IO | 0.3k req/s | N/A | - |
| HTTP/2 | CPU | 29.6k req/s | N/A | - |
ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [aarch64-linux] 4 workers, 3 threads, 24 concurrent connections
See bin/benchmark for more details.
Development
After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rake to compile native
extensions and run the tests. You can also run bin/console for an interactive prompt that will allow you to
experiment.
On macOS (or any non-Linux host), bin/dev builds and drops you into a Docker image with Ruby and the required Linux
toolchain preinstalled, mounting the repo at /workspace. Run bin/dev for an interactive shell, or
bin/dev <command> for one-off commands like bin/dev bundle exec rake or bin/dev bin/benchmark.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/joshuay03/raptor. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Raptor project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.