Your Rails app can be perfect and still feel slow if the server under it is wrong. For two decades the Ruby community has fought the same three-way battle — Puma, Phusion Passenger, and Unicorn — and in 2026 the gap between them has grown into three genuinely different deployment philosophies. Puma (7,914 stars, BSD-3-Clause) is the default Rails server: multi-threaded, cluster-capable, actively maintained. Passenger (5,088 stars, MIT) is a C++-core application server that also runs Python and Node.js apps beside your Ruby ones. Unicorn (1,478 stars on its GitHub mirror, GPLv2+) is the prefork classic whose own README now tells you not to use it for new deployments. Pick the wrong one and you are either wasting RAM on processes that could share threads, or debugging thread-safety crashes that a prefork model would have hidden.
TL;DR / Quick Verdict
If you are starting a new Rails or Rack project, run Puma — it is the default server bundled with Rails, it combines threads with optional pre-fork cluster mode, and it is the only one of the three with an active release cadence. Choose Phusion Passenger when you manage several apps (Ruby and Python and Node.js) behind one nginx or Apache instance and want enterprise-grade process supervision without writing systemd units yourself. Choose Unicorn for exactly one scenario: a legacy, non-thread-safe application that already runs on it — and treat that choice as a migration debt, because even Unicorn’s maintainer says new deployments are strongly discouraged. There is no fourth option that matters in production Ruby in 2026.
Head-to-Head Comparison Table
| Dimension | Puma | Phusion Passenger | Unicorn |
|---|---|---|---|
| Stars (Sep 2026) | 7,914 | 5,088 | 1,478 (GitHub mirror) |
| License | BSD-3-Clause | MIT (core) | GPLv2+ or Ruby terms |
| Language | Ruby (C ext) | C++ core | Ruby (C ext) |
| Concurrency model | Threads + optional pre-fork cluster | Hybrid: evented, threaded, multi-process | Pre-fork, one request per worker |
| Thread-safety required? | Yes | Yes (hybrid) | No — process isolation |
| Last push (Sep 2026) | 2026-09-03 | 2026-09-06 | Mirror 2025-04 (v6.1.0 tag) |
| Default Rails server | Yes | No | No |
| Other languages | Ruby only | Ruby, Python, Node.js, Meteor | Ruby only |
| Rolling restarts | Built-in (cluster) | Enterprise + basic | Binary upgrade style |
| SSL built-in | Yes | Via nginx/Apache | No — proxy required |
| Best for | New Rails/Rack apps | Multi-app, multi-language servers | Legacy prefork apps |
Decision Matrix: Pick in 10 Seconds
| Use case | Recommendation | Why |
|---|---|---|
| New Rails or Rack app, single language | Puma | Default Rails server; threads + cluster; active development; built-in SSL and rolling restarts |
| One server runs Ruby and Python/Node apps | Phusion Passenger | Native multi-language support with shared nginx/Apache supervision |
| Non-thread-safe legacy app already on Unicorn | Unicorn (for now) | Process isolation hides the thread-unsafety; budget a migration to Puma |
| Brand-new deployment considering Unicorn | Do not | Upstream README explicitly discourages it — the ecosystem moved on |
| App dominated by blocking network IO on MRI | Puma | Threads overlap IO waits despite the GVL; raise RAILS_MAX_THREADS |
| Running JRuby or TruffleRuby | Puma | Truly parallel runtimes unlock Puma’s thread model |
Puma — The Default for a Reason
Puma describes itself as “a simple, fast, multi-threaded, and highly parallel HTTP 1.1 server for Ruby/Rack applications,” and the key phrase in its README is that it is “currently the most popular Ruby webserver, and is the default server for Ruby on Rails.” Two mechanisms carry the load: multi-threading (each request runs in a thread from a pool, so blocking IO waits overlap) and cluster mode (workers “pre-fork” with copy-on-write memory savings). It also ships SSL support, zero-downtime rolling restarts, and a built-in request bufferer, and its HTTP parser is inherited from Mongrel with over 15 years of production use behind it.
One nuance every operator should internalize: on MRI, the Global VM Lock (GVL) means only one thread executes Ruby code at a time. As Puma’s own docs put it, threads still improve throughput when “doing a lot of blocking IO (such as HTTP calls to external APIs)” — the wait happens in parallel even if the code does not. Truly parallel Ruby implementations (TruffleRuby, JRuby) remove the limitation entirely.
Installation and startup are minimal:
| |
The config/puma.rb generated by Rails is the best real-world template for tuning. Its comments make the thread/IO trade-off explicit and wire defaults to environment variables:
| |
The generated file carries one warning worth quoting: “Any libraries that use a connection pool or another resource pool should be configured to provide at least as many connections as the number of threads. This includes Active Record’s pool parameter in database.yml.” Threads multiply your database connections — under-provision the pool and you stall exactly the throughput you added threads to gain.
Phusion Passenger — The Multi-Language Workhorse
Passenger is not a pure Ruby server: its C++ core, zero-copy architecture, watchdog process, and hybrid evented/threaded/multi-process design are what let it supervise Ruby, Python, Node.js, and Meteor apps from a single nginx or Apache installation. Its README claims deployment at high-profile companies (Apple, Pixar, The New York Times, Airbnb) and over 650,000 websites. For operators, the pitch is operational: rather than hand-writing systemd units and socket files per app, you install the module once and declare apps in the web server config. Note that “Passenger” and “Phusion Passenger” are registered trademarks of Asynchronous B.V., and some enterprise-grade features (rolling restarts with zero downtime, advanced analytics) live behind the commercial offering — the core is MIT but “enterprise” is not free.
Installation follows one of three paths, straight from the README:
| |
In module mode you enable the app from inside the nginx virtual host. The directives below are the minimal set from Passenger’s nginx module reference (the installer generates the exact passenger_root/passenger_ruby lines for your system):
| |
Standalone mode (passenger start) is the middle ground: no nginx module compilation, but still supervised processes, and you can put a plain reverse proxy in front. Passenger’s real differentiator remains the multi-language story — if your team runs a Rails API beside a Python service on the same box, one Passenger install supervises both, with the watchdog restarting crashed processes and the hybrid core absorbing slow IO.
Unicorn — The Honest Legacy Choice
Unicorn’s own documentation is refreshingly blunt, and you should quote it before choosing it: it is “an HTTP server for Rack applications that has done decades of damage to the entire Ruby ecosystem due to its ability to tolerate (and thus encourage) bad code.” It is “only designed to handle fast clients on low-latency, high-bandwidth connections,” so slow clients must sit behind “a reverse proxy capable of fully buffering both the request and the response.” And the README’s disclaimer states outright: “The use of unicorn in new deployments is STRONGLY DISCOURAGED.”
That honesty aside, the prefork model has real engineering virtues for legacy code: the OS kernel does the load balancing, requests never pile up behind a busy worker, each worker runs in an isolated address space serving one client at a time — so your app does not need to be thread-safe. Unicorn also pioneered operational conveniences that are still worth copying: USR1-triggered log reopening for atomic logrotate, nginx-style binary upgrades without dropping connections, and systemd socket activation since 5.0. The latest tag on the GitHub mirror is v6.1.0, and canonical development continues on the yhbt.net repository. The official sample configuration (from yhbt.net/unicorn/examples/unicorn.conf.rb) shows the core pattern:
| |
If you maintain an app in this state, the operating guidance is straightforward: keep it stable while you work through the two migration blockers — auditing gems for thread safety and moving long-running jobs out of the request path (a background processor is a prerequisite; see below) — then move to Puma.
Migration and Operations Pitfalls
- Thread count vs. database pool. Every Puma thread can hold a connection. If
RAILS_MAX_THREADS=5anddatabase.ymlhaspool: 5, a handful of slow queries starves the whole app. Size the pool tothreads + headroom, and remember Sidekiq-style workers need their own pool separately. - Slow clients belong behind a proxy. Unicorn requires a fully buffering reverse proxy (nginx recommended by upstream); Passenger and Puma tolerate slow clients better, but a fronting nginx or CDN still protects upstream sockets. Do not expose a prefork server with a tiny backlog directly to the internet.
- The GVL is not a bug in your server. On MRI, more Puma threads buy IO overlap, not CPU parallelism; past roughly 5 threads per worker, latency degrades while throughput plateaus. For CPU-bound workloads, scale processes (cluster mode) or move to JRuby/TruffleRuby before over-threading.
- Fork safety after
preload_app. Unicorn’s memory savings come from forking a loaded app — which is exactly why thebefore_forkhook must disconnect database connections, and why any gem that spawns threads at load time (some connection pools, monitoring agents) breaks under prefork. Audit load-time behavior before enablingpreload_app true. - Log rotation without lost lines. Unicorn reopens logs on USR1 so logrotate can
renameinstead ofcopytruncate; Puma and Passenger deployments need their own signal-aware rotation config. Multi-line log entries from a single request must stay in one file — a naive rotation tears them apart. - Zero-downtime expectations differ. Puma cluster mode and Unicorn binary upgrades give you rolling restarts; Passenger’s zero-downtime rolling restarts are an enterprise-tier feature. If “restart without dropping a request” is a hard requirement, verify it against the free tier before you commit.
- Licensing review. Puma is BSD-3-Clause, Passenger core is MIT (trademarked name, commercial enterprise tier), Unicorn is GPLv2+ or Ruby terms. A GPL-sensitive shop running Unicorn has a second, legal reason to migrate.
For the surrounding Ruby deployment stack, our Ruby micro-framework comparison (Sinatra vs Roda vs Grape) shows what these servers typically front, and the Ruby background job processor guide (Sidekiq vs Shoryuken vs Faktory) is the prerequisite reading for moving slow work out of the request path — the single biggest lever on app-server sizing. If you are evaluating the server layer across languages, our nginx Unit vs Gunicorn vs Caddy application-server comparison puts the Ruby choices in a wider context.
FAQ
Is Puma still the default web server for Rails in 2026?
Yes. Puma remains the default server in the Rails Gemfile and the most popular Ruby web server. Rails’ generated config/puma.rb wires RAILS_MAX_THREADS and PORT environment variables straight into the server, and the rails server command boots it for development.
Why does Unicorn’s README say new deployments are strongly discouraged? Because its prefork model tolerates non-thread-safe code, upstream argues that this “encouraged bad code” and set the Ruby ecosystem back on parallelism. Unicorn also requires a fully buffering reverse proxy for slow clients. It remains viable only for legacy apps already running on it.
Is Phusion Passenger free and open source? The core is MIT-licensed open source and can run Ruby, Python, and Node.js apps. However, “Passenger” is a registered trademark of Asynchronous B.V., and advanced operational features such as zero-downtime rolling restarts and extended enterprise tooling require a commercial subscription.
Does the Ruby GVL make Puma’s threads useless on MRI? No. The GVL serializes Ruby code execution, but threads still overlap blocking IO — database queries, external HTTP calls, file reads — which is why Puma’s README highlights throughput gains for IO-bound workloads on MRI. JRuby and TruffleRuby run threads truly in parallel.
How many Puma workers and threads should I run?
Start with the Rails defaults (one worker, RAILS_MAX_THREADS=3), then add threads for IO-bound workloads and workers for CPU-bound ones, keeping the database pool at least as large as the thread count. On memory-constrained boxes, prefer threads over workers because cluster workers duplicate the app heap (mitigated by copy-on-write).
Can Passenger and Puma coexist on one server? Yes. Passenger runs its own supervised processes for the apps you assign to it, while Puma processes can run independently (for example behind systemd). Many shops standardize on Puma for Ruby apps and add Passenger only when Python or Node.js apps join the same host.
💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com