How Rack::Timeout Keeps Your App Alive
Cassia Scheffer
Published on June 05, 2025
Rack::Timeout is a library that monitors long-running requests in a Ruby app and kills them if they take too long. The default configuration in Rack::Timeout is to kill requests that take 15 seconds or longer.
Why Requests Take A Long Time?
The answer here will vary from one app to another. A frequent culprit is database transactions taking too long or outgoing requests to other resources hanging.
How Does This Keep My App Alive if it Kills Things?
In a typical Ruby app using Puma, you will have a few processes to manage a Ruby Thread collection. Rack timeout will send SIGTERM
to a thread if it breaches the timeout.
Your Ruby app and Puma process will stay alive, but the stuck thread will get killed.
Puma will see that this thread died and will boot up a new one.
When is Rack::Timeout Unsafe?
Killing a running process is often unsafe. Fortunately, Rack::Timeout uses a “polite” kill message, SIGTERM
, which asks the process to shut down and clean up its resources.
SIGTERM
is less dangerous than SIGKILL
, but still has its risks.
A Ruby thread could be in the middle of creating database records when it is killed, which would mean things are left in a broken state.
Where Does Rack::Timeout Run in My App?
Rack::Timeout runs in Rack, an interface between web servers like Puma Ruby applications. Puma picks up a request, hands it off to a thread, which passes it through Rack before it finally hits your applicaiton code.
When Rack handles the request, it monitors the request duration and kills the process if the request takes too long.
Rack::Timeout keeps your application alive by killing hanging threads and allowing Puma to boot up a new thread to handle the next request.
graph TD
Client[Client Requests] --> LB[Load Balancer
nginx/HAProxy]
LB --> Container1[Container 1
Rails App]
LB --> Container2[Container 2
Rails App]
Container1 --> PumaServer1[Puma Server 1]
Container2 --> PumaServer2[Puma Server 2]
PumaServer1 --> Worker1_1[Worker 1]
PumaServer1 --> Worker1_2[Worker 2]
PumaServer2 --> Worker2_1[Worker 1]
PumaServer2 --> Worker2_2[Worker 2]
Worker1_1 --> RackTimeout1_1[Rack::Timeout
Middleware]
Worker1_2 --> RackTimeout1_2[Rack::Timeout
Middleware]
Worker2_1 --> RackTimeout2_1[Rack::Timeout
Middleware]
Worker2_2 --> RackTimeout2_2[Rack::Timeout
Middleware]
RackTimeout1_1 --> Threads1_1[3 Threads
Thread Pool
_timeout monitored per thread_]
RackTimeout1_2 --> Threads1_2[3 Threads
Thread Pool
_timeout monitored per thread_]
RackTimeout2_1 --> Threads2_1[3 Threads
Thread Pool
_timeout monitored per thread_]
RackTimeout2_2 --> Threads2_2[3 Threads
Thread Pool
_timeout monitored per thread_]
classDef container fill:#e1f5fe
classDef worker fill:#f3e5f5
classDef thread fill:#e8f5e8
classDef loadbalancer fill:#fce4ec
classDef middleware fill:#fff3e0
class Container1,Container2 container
class Worker1_1,Worker1_2,Worker2_1,Worker2_2 worker
class Threads1_1,Threads1_2,Threads2_1,Threads2_2 thread
class LB loadbalancer
class RackTimeout1_1,RackTimeout1_2,RackTimeout2_1,RackTimeout2_2 middleware