The project is in a healthy, maintained state
A Puma plugin that intercepts a configurable signal (default: SIGQUIT) and waits a configurable number of seconds before telling Puma to stop. This gives orchestrators like Kubernetes and Docker Swarm time to remove the container from load balancing before connections are closed.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

>= 2.0
~> 13.0
~> 3.0

Runtime

>= 5.0, < 8
 Project Readme

puma-plugin-delayed_stop

A Puma plugin that delays shutdown after receiving a signal, giving container orchestrators (Kubernetes, Docker Swarm, ECS, etc.) time to remove the instance from load balancing before connections are closed.

The problem

When Puma receives SIGTERM, it begins shutting down immediately. In orchestrated environments, the termination signal often arrives before the orchestrator has finished removing the container from its service mesh or load balancer. Requests routed to the container during this window are dropped.

The solution

This plugin intercepts a configurable signal (default: SIGQUIT) and waits a configurable number of seconds before telling Puma to stop. This gives the orchestrator time to update its routing tables.

A typical Kubernetes setup would configure the pod's preStop hook to send SIGQUIT before the kubelet sends SIGTERM:

lifecycle:
  preStop:
    exec:
      command: ["kill", "-QUIT", "1"]

In Docker Swarm, configure stop_signal to send SIGQUIT first and set stop_grace_period long enough to cover both the drain and Puma's graceful shutdown:

services:
  web:
    image: myapp:latest
    stop_signal: SIGQUIT
    stop_grace_period: 30s
    environment:
      PUMA_DELAYED_STOP_DRAIN_SECONDS: "5"

Swarm sends stop_signal when removing a task, then waits up to stop_grace_period before sending SIGKILL. The plugin sleeps through the drain period while Swarm updates its routing mesh, then tells Puma to shut down gracefully with the remaining time.

Installation

Add to your Gemfile:

gem "puma-plugin-delayed_stop"

Then in your config/puma.rb:

plugin :delayed_stop

Configuration

Configuration is via environment variables:

Variable Default Description
PUMA_DELAYED_STOP_SIGNAL QUIT Signal name (without SIG prefix) that triggers the delayed stop
PUMA_DELAYED_STOP_DRAIN_SECONDS 5 Seconds to wait before telling Puma to stop

Warning: Do not set PUMA_DELAYED_STOP_SIGNAL to a signal that Puma already handles (TERM, INT, HUP, USR1, USR2). Puma registers its own handlers for these signals after plugins start, so the plugin's handler will be silently overwritten. The default (QUIT) is safe because Puma does not trap it. See Puma's signal handling documentation for the full list of reserved signals.

How it works

  1. On startup, the plugin registers a signal handler for the configured signal.
  2. When the signal is received, the handler sleeps for the configured drain period.
  3. After sleeping, it calls launcher.stop, which initiates Puma's normal graceful shutdown.

Development

bundle install
bundle exec rspec

License

MIT