Implement graceful shutdown for servers and workers: drain connections, finish in-flight work, release resources, and exit cleanly on SIGTERM/SIGINT.
Scanned 9/11/2026
Install to Claude Code
npx -y skills add ranbot-ai/awesome-skills --skill graceful-shutdown --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Graceful Shutdown?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ranbot-ai-graceful-shutdown)More formats (shields.io, HTML) on the badges page.
---
name: graceful-shutdown
description: Implement graceful shutdown for servers and workers: drain connections, finish in-flight work, release resources, and exit cleanly on SIGTERM/SIGINT.
category: AI & Agents
source: antigravity
tags: [python, typescript, node, api, claude, ai, template, docker, kubernetes]
url: https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/graceful-shutdown
---
# Graceful Shutdown
## Overview
A skill for implementing graceful shutdown in servers, workers, and long-running processes. Ensures in-flight requests complete, background jobs finish or checkpoint, database connections close cleanly, and the process exits with a proper status code. Essential for zero-downtime deployments in container orchestrators (Kubernetes, ECS, Docker Compose) and bare-metal process managers (systemd, PM2).
## When to Use This Skill
- Use when building an HTTP server that must not drop active connections during deploys
- Use when writing a background worker that processes jobs from a queue
- Use when deploying to Kubernetes, Docker, or any environment that sends SIGTERM before killing
- Use when the user says "graceful shutdown", "drain connections", "handle SIGTERM", "zero downtime", or "don't kill active requests"
- Use when implementing health check endpoints (`/healthz`, `/readyz`) for orchestrators
## How It Works
### Step 1: Register signal handlers early
Trap `SIGTERM` (orchestrator shutdown) and `SIGINT` (Ctrl+C) at process startup. Set a flag so the application knows it is shutting down.
```typescript
let isShuttingDown = false;
function onShutdownSignal(signal: string): void {
if (isShuttingDown) return; // prevent double-shutdown
isShuttingDown = true;
console.log(`Received ${signal}, starting graceful shutdown...`);
shutdown();
}
process.on("SIGTERM", () => onShutdownSignal("SIGTERM"));
process.on("SIGINT", () => onShutdownSignal("SIGINT"));
```
### Step 2: Stop accepting new work
Immediately stop the server from accepting new connections. For HTTP servers, call `server.close()`. For queue workers, stop polling for new jobs.
```typescript
async function shutdown(): Promise<void> {
// 1. Stop accepting new connections
server.close(() => {
console.log("Server closed — no new connections accepted");
});
// 2. Mark health check as not-ready so load balancers stop routing
// (readiness probe returns 503 from this point)
}
```
### Step 3: Drain in-flight work with a deadline
Wait for active requests and background tasks to finish, but enforce a hard deadline so the process never hangs indefinitely.
```typescript
const DRAIN_TIMEOUT_MS = 25_000; // must be less than orchestrator's terminationGracePeriodSeconds
async function drainAndExit(): Promise<void> {
const deadline = setTimeout(() => {
console.error("Drain timeout reached — forcing exit");
process.exit(1);
}, DRAIN_TIMEOUT_MS);
deadline.unref(); // don't keep the event loop alive just for the timer
try {
// Wait for active connections to finish
await waitForActiveConnections();
// Flush buffered data (logs, metrics, queues)
await flushBuffers();
// Close external resource handles
await closeResources();
console.log("Graceful shutdown complete");
process.exit(0);
} catch (err) {
console.error("Error during shutdown:", err);
process.exit(1);
}
}
```
### Step 4: Implement readiness and liveness probes
Orchestrators use these to decide whether to route traffic and whether to restart the container. Liveness proves the process is alive; readiness controls whether traffic is routed. While the listener is still available during a drain, keep liveness healthy and return 503 only from readiness. After the listener closes, new probes cannot connect, so do not promise that HTTP liveness remains reachable for the entire termination window.
```typescript
import { createServer, IncomingMessage, ServerResponse } from "node:http";
function handleHealthCheck(req: IncomingMessage, res: ServerResponse): void {
if (req.url === "/healthz") {
// Keep liveness distinct from readiness while the listener is available.
// Drain-rejection middleware must not turn this endpoint into a 503.
res.writeHead(200).end("ok");
return;
}
if (req.url === "/readyz") {
// Readiness: 503 during shutdown so the load balancer stops routing.
if (isShuttingDown) {
res.writeHead(503).end("shutting down");
} else {
res.writeHead(200).end("ready");
}
return;
}
}
```
### Step 5: Track active connections
Maintain a count of in-flight requests so you know when draining is complete. Use a once guard covering both `finish` and `close` events so that client disconnects (aborted requests) correctly decrement the counter.
```typescript
let activeConnections = 0;
let drainResolve: (() => void) | null = null;
function trackRequest(res: ServerResponse): void {
activeConnections++;
let counted = true;
function release(): void {
if (!counted) return;
counted = false;
activeConnections--;
if (isShuttingDown && activeConnections === 0 && drainResolve) {
drainResolve();
}
}
res.on("finish", release);
res.on("close", release);
}
function waitForActiveConnections(): Promise<void> {
if (activeConnections === 0) return Promise.resolve();
return new Promise((resolve) => {
drain
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!