Puma
Puma is the default multi-threaded web server used to run Ruby on Rails applications in production, handling concurrent requests efficiently through a combination of worker processes and threads.
Table of Contents
What Is Puma?
Puma is a Ruby web server built for speed and concurrency. Since Rails 5, it has shipped as the default application server when generating a new Rails app, replacing earlier defaults like WEBrick for production use.
Puma uses a combination of multiple worker processes and multiple threads within each process to handle incoming requests. This hybrid process/thread model allows Puma to take advantage of multiple CPU cores while also handling many concurrent connections efficiently within each process.
Puma typically sits behind a reverse proxy (like Nginx) in production, which handles tasks like SSL termination and serving static assets, while Puma focuses on running the Rails application itself.
Why Is Puma Useful?
Without a concurrent, production-ready server, Rails applications can struggle under real-world traffic, leading to:
- Requests queuing up and timing out under load
- Poor utilization of multi-core servers
- Difficulty handling many simultaneous connections efficiently
- Slower response times during traffic spikes
Puma helps by:
- Handling multiple requests concurrently using threads
- Utilizing multiple CPU cores through worker processes (in clustered mode)
- Providing low memory overhead compared to purely process-based servers
- Supporting graceful restarts and zero-downtime deploys (via phased restarts)
- Being the Rails-recommended default, with strong community support and documentation
How Does Puma Work?
Puma can run in two main modes:
-
Single mode – One process, with multiple threads handling requests concurrently. Simpler, but limited to a single CPU core.
-
Clustered mode – Multiple worker processes, each with its own set of threads. This allows Puma to use multiple CPU cores, since each worker runs as a separate OS process (important in Ruby, where threads within a single process are limited by the Global VM Lock / GVL for CPU-bound work).
Within each worker process, threads allow Puma to handle multiple requests concurrently, particularly effective for I/O-bound work (database queries, API calls, file I/O), where a thread can yield control while waiting on I/O rather than blocking the whole process.
Key configuration concepts:
-
Workers – Number of separate processes Puma runs (clustered mode).
-
Threads (min/max) – Number of threads per worker, handling concurrent requests within that process.
-
Phased restarts – Restarting workers one at a time to avoid downtime during deploys.
Examples
Scenario 1: A Typical puma.rb Configuration
# config/puma.rb max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } threads min_threads_count, max_threads_count worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" port ENV.fetch("PORT", 3000) environment ENV.fetch("RAILS_ENV") { "development" } workers ENV.fetch("WEB_CONCURRENCY", 2) preload_app! plugin :tmp_restart
This configures Puma with 2 worker processes, each running up to 5 threads, a common starting point for small-to-medium production apps.
Scenario 2: Starting Puma
bundle exec puma -C config/puma.rb
In most Rails setups, this is handled automatically by the deployment tool (e.g., Kamal, Capistrano, or a Procfile on platforms like Heroku).
Scenario 3: Scaling Workers and Threads Based on Server Resources
# config/puma.rb workers ENV.fetch("WEB_CONCURRENCY", 4) threads 5, 5
A common rule of thumb is to set the number of workers close to the number of available CPU cores, and tune thread count based on how I/O-bound the application's workload is.
Scenario 4: Enabling Phased Restarts for Zero-Downtime Deploys
# config/puma.rb plugin :tmp_restart
Combined with a deploy tool that sends the appropriate restart signal, this allows workers to restart one at a time, keeping the app available throughout a deploy.
Where Is Puma Used?
-
Serving Rails applications in production (the Rails default since Rails 5)
-
Any Ruby web application needing a concurrent, production-grade server
-
Deployments behind a reverse proxy like Nginx or a platform like Heroku/Kamal
-
Applications that need to balance CPU-bound and I/O-bound request handling via workers and threads
In Summary
Puma is the default multi-threaded, multi-process web server for Rails applications in production. By combining worker processes with per-process threading, it efficiently handles concurrent requests, takes advantage of multi-core servers, and supports features like phased restarts for zero-downtime deploys.