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.