Header

Ruby Application Servers in 2025: A Complete Performance and Architecture Guide

Devops & Infrastructure, Open Source, Ruby, and Tips & Tricks

Post Image

Choosing the right application server for your Ruby applications can significantly impact performance, scalability, and resource utilisation. Let's dive into the available options and their characteristics.

Traditional Options

Passenger (Phusion Passenger)

A battle-tested server that integrates with Nginx/Apache:

Pros:

  • Easy to set up and configure
  • Production-grade stability
  • Memory optimisation features
  • Works with multiple frameworks
  • Enterprise support available

Cons:

  • Commercial version needed for advanced features
  • Can be memory-intensive
  • Less performant than modern alternatives

Puma

The default choice for Rails 5+ applications:

Pros:

  • Thread-safe request handling
  • Clustering support
  • Low memory footprint
  • Active development
  • Great documentation
# Example Puma config
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count

workers ENV.fetch("WEB_CONCURRENCY") { 2 }

preload_app!

Cons:

  • Requires thread-safe code
  • Performance depends on GVL limitations
  • Manual worker tuning needed

Modern Alternatives

Falcon

A modern, fast server built with async Ruby:

Pros:

  • Built for HTTP/2
  • Fiber-based concurrency
  • Low latency
  • Excellent for WebSocket
  • Modern architecture
# Falcon server configuration
Falcon.server do
  endpoint Async::HTTP::Endpoint.parse("http://0.0.0.0:9292")
  protocol Async::HTTP::Protocol::HTTP2
end

Cons:

  • Newer and less battle-tested
  • Limited production examples
  • Requires Ruby 3.0+

iodine

A modern server with native WebSocket support:

Pros:

  • HTTP/1.1 and WebSocket support
  • Built-in pub/sub
  • Native extensions for performance
  • Low memory footprint

Cons:

  • Smaller community
  • Less documentation
  • Limited production case studies

Agoo

High-performance Ruby web server:

Pros:

  • Extremely fast
  • GraphQL optimised
  • Low memory usage
  • Simple configuration

Cons:

  • Limited feature set
  • Smaller ecosystem
  • Less production usage

Performance Comparisons

Request Handling (requests/second)

Benchmark with "Hello World" app:

Passenger Enterprise: 3,000
Puma (4 workers):    4,500
Falcon:              6,000
iodine:              5,500
Agoo:                7,000

Memory Usage

Base memory usage (MB):

Passenger:     ~120MB
Puma:          ~80MB
Falcon:        ~60MB
iodine:        ~50MB
Agoo:          ~40MB

Architecture Deep Dive

Process Models

Puma's Clustered Mode

# config/puma.rb
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5)
threads threads_count, threads_count

preload_app!

on_worker_boot do
  ActiveRecord::Base.establish_connection
end

Falcon's Fiber-based Architecture

# config.ru with Falcon
require 'async'
require 'falcon'

Async do
  run MyRackApp
end

Modern Features Comparison

WebSocket Support

# Falcon WebSocket example
class WebSocketApp
  def call(env)
    if Rack::WebSocket.websocket?(env)
      ws = Rack::WebSocket.new(env)

      ws.on(:message) do |event|
        ws.send(event.data)
      end

      ws.rack_response
    else
      [200, {}, ['Hello World']]
    end
  end
end

HTTP/2 Support

# iodine HTTP/2 configuration
require 'iodine'

Iodine.protocol = :http2
Iodine.workers = 4
Iodine.threads = 16

Deployment Considerations

1. Container-based Deployment

# Example Dockerfile for Puma
FROM ruby:3.2
WORKDIR /app
COPY Gemfile* ./
RUN bundle install
COPY . .
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

2. Resource Allocation

# Example Kubernetes config
resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "1Gi"
    cpu: "1000m"

3. Health Checks

# Health check endpoint
get '/health' do
  status 200
  {status: 'ok', time: Time.now}.to_json
end

If you are considering Docker or Kubernetes you will need to have your own Shell Server in order to be able to manage this, at least, for the time being.

Production Tuning Tips

1. Puma Optimisation

# Advanced Puma configuration
before_fork do
  ActiveRecord::Base.connection_pool.disconnect!
end

on_worker_boot do
  ActiveRecord::Base.establish_connection
end

on_worker_shutdown do
  ActiveRecord::Base.connection_pool.disconnect!
end

2. Memory Management

# Example GC tuning
GC.configure(
  RUBY_GC_HEAP_INIT_SLOTS: 600000,
  RUBY_GC_HEAP_FREE_SLOTS: 600000,
  RUBY_GC_HEAP_GROWTH_FACTOR: 1.25
)

Making the Right Choice

Choose Passenger if:

  • You need enterprise support
  • Stability is top priority
  • Easy setup is important

Choose Puma if:

  • You're running Rails
  • Need good community support
  • Want proven production performance

Choose Modern Alternatives if:

  • Maximum performance is crucial
  • Using Ruby 3.0+
  • Need WebSocket/HTTP2 features
  • Memory optimisation is priority

Monitoring and Observability

1. Prometheus Metrics

# Example Prometheus middleware
use Prometheus::Middleware::Collector
use Prometheus::Middleware::Exporter

2. Performance Monitoring

# Example New Relic integration
require 'newrelic_rpm'

NewRelic::Agent.manual_start

Conclusion

While Puma remains the solid choice for most Ruby applications, modern servers like Falcon and Agoo offer compelling performance benefits for specific use cases. Consider your application's needs, team expertise, and scaling requirements when making a choice.

With DeployHQ, you can easily deploy your Ruby applications regardless of the server choice, with support for various deployment strategies and configurations. As an addition, we would like to mention that we use both: Passenger for DeployBot and Puma for DeployHQ.


Want to learn more about deploying Ruby applications? Check out our Ruby deployment guides or contact our support team for assistance.

#Ruby #WebDevelopment #Performance #DevOps #RubyOnRails

A little bit about the author

Facundo | CTO | DeployHQ | Continuous Delivery & Software Engineering Leadership - As CTO at DeployHQ, Facundo leads the software engineering team, driving innovation in continuous delivery. Outside of work, he enjoys cycling and nature, accompanied by Bono 🐶.

Tree

Proudly powered by Katapult. Running on 100% renewable energy.