# Quick start

> Install shutdown-check, create a config, and test that your Node.js server finishes in-flight HTTP requests and exits cleanly when it receives SIGTERM.

Source: https://shutdown.jscrate.dev/docs/quick-start
Last updated: 2026-09-23

shutdown-check starts your Node.js service, opens a request, sends `SIGTERM`
while that request is active, and watches what happens. A passing result means
the request finished, the process exited on time, and the port closed.

## Before you begin

You need:

- Node.js 22 or later;
- macOS or Linux;
- a local HTTP/1 service;
- a readiness route, such as `/health`;
- a route that stays active long enough to receive `SIGTERM`.

The package works with node:http, Express, Fastify, NestJS, and other Node.js
frameworks. It does not add shutdown behavior to your app; it tests the
behavior you already wrote. See [compatibility and limits](https://shutdown.jscrate.dev/docs/compatibility)
before using HTTPS, HTTP/2, WebSockets, Windows, or remote services.

## 1. Install the package

Add shutdown-check as a development dependency:

```bash
npm install --save-dev shutdown-check
```

The package has no runtime dependencies and does not need to ship with your
production application.

## 2. Create the config

Run the initializer from the project root:

```bash
npx shutdown-check init
```

The command creates `shutdown-check.json`. It will not overwrite an existing
file. The generated file is:

```json title="shutdown-check.json"
{
  "command": ["node", "server.js"],
  "cwd": ".",
  "baseUrl": "http://127.0.0.1:3000",
  "readiness": { "path": "/health", "status": 200, "timeoutMs": 10000 },
  "workload": {
    "path": "/slow",
    "method": "GET",
    "status": 200,
    "started": { "type": "response-headers", "timeoutMs": 5000 }
  },
  "shutdown": { "deadlineMs": 10000, "exitCode": 0 }
}
```

Update these values:

| Field            | What to enter                                                             |
| ---------------- | ------------------------------------------------------------------------- |
| `command`        | The direct command that starts the built service                          |
| `baseUrl`        | A local port reserved for this test                                       |
| `readiness.path` | A route that returns the expected status when the service is ready        |
| `workload.path`  | A route that remains active long enough for the shutdown signal to arrive |

Prefer a direct command such as `["node", "dist/server.js"]`. A shell or
package-manager wrapper may receive the signal instead of the server.

## 3. Make the workload testable

The starter config uses the `response-headers` start barrier. The workload
route must send headers and keep the response body open. This lets
shutdown-check prove that the request is active before it sends `SIGTERM`.

Here is a complete server that matches the starter config:

```js title="server.js"
const http = require("node:http");

const port = Number(process.env.PORT ?? 3000);

const server = http.createServer((request, response) => {
  if (request.url === "/health") {
    response.writeHead(200, { "content-type": "text/plain" });
    response.end("ok");
    return;
  }

  if (request.url === "/slow") {
    response.writeHead(200, { "content-type": "text/plain" });
    response.flushHeaders();
    setTimeout(() => response.end("work complete\n"), 2000);
    return;
  }

  response.writeHead(404).end();
});

server.listen(port, "127.0.0.1", () => {
  console.log(`listening on http://127.0.0.1:${port}`);
});

process.on("SIGTERM", () => {
  console.log("SIGTERM received; closing the server");
  server.close((error) => {
    if (error) {
      console.error(error);
      process.exitCode = 1;
    }
  });
});
```

`response.flushHeaders()` sends the `200` headers immediately. The body ends
two seconds later. When `SIGTERM` arrives, `server.close()` stops accepting
new connections and waits for the open response to finish.

If your handler sends nothing until its work is complete, use a
[probe start barrier](https://shutdown.jscrate.dev/docs/in-flight-work#use-the-probe-barrier)
instead. A normal fast route is not useful: it may finish before the signal,
which produces [SC111](https://shutdown.jscrate.dev/docs/codes/sc111).

## 4. Run the test

```bash
npx shutdown-check test
```

The test now:

1. checks that the configured port is free;
2. starts `command` in its own process group;
3. waits for the readiness route;
4. opens the workload request and confirms that it is active;
5. sends `SIGTERM`;
6. waits for the response and process exit;
7. checks that the port is closed.

The command exits with `0` after a pass, `1` after a shutdown failure, and `2`
when invalid setup or configuration prevents the test from running.

## 5. Read a passing result

The server above prints a result like this. Timings and process IDs vary.

```text
PASS SC000: Graceful shutdown verified

Timeline:
  +    5 ms  process launched — pid=73661
  +  112 ms  service ready — HTTP 200
  +  112 ms  work request sent — #1 GET /slow
  +  113 ms  work confirmed active — 1 response(s) sent headers; bodies still in progress
  +  113 ms  signal sent — SIGTERM
  + 2118 ms  work request finished — #1 HTTP 200
  + 2125 ms  process exited — code=0, signal=none
  + 2126 ms  shutdown verified — work completed and service exited before deadline
```

The important sequence is:

- `work confirmed active` appears before `signal sent`;
- the request finishes after the signal;
- the process exits with code `0`;
- `shutdown verified` appears last.

[SC000](https://shutdown.jscrate.dev/docs/codes/sc000) is the pass code. The
[output reference](https://shutdown.jscrate.dev/docs/output) explains every timeline event.

## 6. See a real failure

Remove the `SIGTERM` handler and run the command again. Node.js uses its
default signal behavior and exits immediately, cutting off the open request:

```text
FAIL SC201: In-flight request was interrupted: aborted

Timeline:
  +    6 ms  process launched — pid=73692
  +  112 ms  service ready — HTTP 200
  +  113 ms  work request sent — #1 GET /slow
  +  113 ms  work confirmed active — 1 response(s) sent headers; bodies still in progress
  +  113 ms  signal sent — SIGTERM
  +  116 ms  process exited — code=null, signal=SIGTERM
  +  116 ms  work request finished — #1 aborted
  +  116 ms  check failed — SC201: In-flight request was interrupted: aborted

Service stdout (last 8 KiB):
listening on http://127.0.0.1:3000
```

The first line gives the stable diagnostic code. Open
[SC201](https://shutdown.jscrate.dev/docs/codes/sc201) for the causes and fix. On failures, the CLI also
includes the last 8 KiB of the service's stdout and stderr.

An invalid config looks different. It starts with `shutdown-check:`, has no SC
code or timeline, and exits with `2`:

```text
shutdown-check: Cannot read JSON config /path/shutdown-check.json: Error: ENOENT: no such file or directory, open '/path/shutdown-check.json'
```

Use [troubleshooting](https://shutdown.jscrate.dev/docs/troubleshooting) when the command does not reach a
normal test result.

## 7. Strengthen the check

The starter config covers one request, process exit, and port closure. You can
also verify multiple requests, response content, readiness withdrawal, new
request rejection, and repeated signals:

```json title="shutdown-check.json"
{
  "command": ["node", "server.js"],
  "baseUrl": "http://127.0.0.1:3000",
  "readiness": { "path": "/health" },
  "workload": {
    "path": "/slow",
    "bodyIncludes": "work complete",
    "concurrent": 2,
    "started": { "type": "response-headers" }
  },
  "shutdown": {
    "deadlineMs": 10000,
    "readinessWithdrawal": true,
    "newRequests": { "path": "/health" },
    "repeatSignalAfterMs": 500
  }
}
```

The example server also passes the readiness and new-request checks because
`server.close()` refuses new connections. Applications that remain open while
draining can return `503` instead; see
[readiness and draining](https://shutdown.jscrate.dev/docs/guides/readiness-and-draining).

## 8. Add the test to CI

Build the service first, then run:

```bash
npx shutdown-check test --json --junit shutdown-result.xml
```

The nonzero exit code fails the job. The JUnit file can be published as a test
report, while JSON keeps the complete result in the job log. The
[CI guide](https://shutdown.jscrate.dev/docs/ci) includes GitHub Actions and GitLab examples.

## Related

- [Configuration reference](https://shutdown.jscrate.dev/docs/configuration)
- [How the check works](https://shutdown.jscrate.dev/docs/how-it-works)
- [Graceful shutdown in Node.js](https://shutdown.jscrate.dev/docs/guides/graceful-shutdown-nodejs)
- [In-flight requests and start barriers](https://shutdown.jscrate.dev/docs/in-flight-work)
- [All diagnostic codes](https://shutdown.jscrate.dev/docs/codes)
