# 配置

> shutdown-check.json 中每个配置字段的说明：command、baseUrl、readiness、workload、启动确认和 shutdown，包括默认值、取值范围和常见错误。

Source: https://shutdown.jscrate.dev/zh/docs/configuration
Last updated: 2026-09-23

shutdown-check 的配置是一个名为 `shutdown-check.json` 的 JSON 文件。它告诉 CLI 如何启动你的服务、如何等待服务就绪、如何发起进行中的工作、如何发送 `SIGTERM`，以及如何判断这次关闭是否通过。

## 生成初始配置文件

运行：

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

这个命令会写入下面这份完整配置。如果文件已存在，它会拒绝覆盖：

```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 }
}
```

第一次运行前，先改好启动命令、端口、就绪检查路径和测试请求路径。[快速开始](https://shutdown.jscrate.dev/zh/docs/quick-start)里有一个和这份配置配套的服务端示例。

## 配置规则

- CLI 只接受 JSON，不允许注释和末尾逗号。
- 所有数值都必须是整数，并且在文档规定的范围内。
- 校验遇到第一个无效值就会停止。
- 配置无效时，CLI 会打印 `shutdown-check: <message>`，并以退出码 `2` 退出。
- 未知字段会被忽略。所以可选字段拼错时不会报错，而是直接回退到默认值。
- 路由路径只能是 `baseUrl` 下的路径；完整 URL 和 `//host/path` 形式都会被拒绝。

## 全部字段

| 选项 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `command` (必填) | `string[]` | — | 服务的启动命令，写成参数数组，而不是 shell 字符串：`["node", "dist/server.js"]`。命令会直接启动，并运行在独立的进程组中。 |
| `cwd` | `string` | 配置文件所在目录 | `command` 的工作目录，相对于配置文件解析（在 API 中相对于 `baseDirectory`）。 |
| `env` | `Record<string, string>` | `{}` | 传给启动进程的环境变量，会加到检查自身的环境变量中，或覆盖其中的同名变量。 |
| `baseUrl` (必填) | `string` | — | 服务的源地址（origin）。必须是 `localhost`、`127.0.0.1` 或 `[::1]` 上的纯 HTTP 地址，不能带路径、查询参数或凭据。使用专门留给测试的端口。 |
| `readiness` (必填) | `object` | — | 表明服务已可以接收流量的路由。检查会持续轮询这个路由，直到返回 `status`。 |
| `readiness.path` (必填) | `string` | — | 就绪检查路由的本地路径，例如 `/health`。 |
| `readiness.status` | `number` | `200` | 表示服务已就绪的状态码，范围 100–599。 |
| `readiness.timeoutMs` | `number` | `10000` | 等待服务就绪的最长时间，范围 100–300000 ms。 |
| `readiness.intervalMs` | `number` | `100` | 两次就绪轮询之间的间隔，范围 10–10000 ms。 |
| `workload` (必填) | `object` | — | SIGTERM 到达时仍在进行中的慢请求，以及它的响应必须满足的条件。 |
| `workload.path` (必填) | `string` | — | 端点的本地路径。该端点的处理时间要足够长，确保发送信号时它仍在运行。 |
| `workload.method` | `string` | `"GET"` | HTTP 方法（大写）。 |
| `workload.headers` | `Record<string, string>` | `{}` | 请求头，值为字符串。 |
| `workload.body` | `string` | — | 请求体，以字符串形式提供。 |
| `workload.status` | `number` | `200` | 每个进行中的请求都必须以这个状态码结束。其他状态码会报 SC202。 |
| `workload.bodyIncludes` | `string` | — | 每个进行中请求的响应体都必须包含的文本，用来证明工作确实完成了。缺少这段文本会报 SC203。 |
| `workload.concurrent` | `number` | `1` | 同时处于进行中的测试请求数量，范围 1–20。大于 1 时需要使用 `response-headers` 启动确认。 |
| `workload.started` (必填) | `object` | — | 发送 SIGTERM 之前，检查靠什么确认工作已经真正开始，即启动确认（start barrier）。参见[启动确认](https://shutdown.jscrate.dev/zh/docs/in-flight-work)。 |
| `workload.started.type` (必填) | `"response-headers" \| "probe"` | — | `"response-headers"`：响应头已到达、响应体仍未结束时，视为工作已开始。`"probe"`：由单独的路由报告工作是否已开始。 |
| `workload.started.timeoutMs` | `number` | `5000` | 等待工作开始的最长时间，范围 100–300000 ms。未按时开始会报 SC111。 |
| `workload.started.path` | `string` | — | 用于 `type: "probe"`（此时必填）：报告是否有工作正在运行的路由。 |
| `workload.started.inactiveStatus` | `number` | `204` | 用于 `type: "probe"`：没有工作在运行时，探测路由返回的状态码。 |
| `workload.started.activeStatus` | `number` | `200` | 用于 `type: "probe"`：有工作在运行时，探测路由返回的状态码。必须与 `inactiveStatus` 不同。 |
| `workload.started.intervalMs` | `number` | `50` | 用于 `type: "probe"`：两次探测轮询之间的间隔，范围 10–10000 ms。 |
| `shutdown` | `object` | — | 发送 SIGTERM 后，一次合格的优雅关闭必须满足的条件。 |
| `shutdown.signal` | `"SIGTERM"` | `"SIGTERM"` | 要发送的信号。1.0 版本只支持 `SIGTERM`。 |
| `shutdown.deadlineMs` | `number` | `10000` | 从发送信号算起，工作必须完成、进程必须退出的时限，范围 100–300000 ms。 |
| `shutdown.exitCode` | `number` | `0` | 进程退出时必须使用的退出码，范围 0–255。 |
| `shutdown.readinessWithdrawal` | `boolean` | `false` | 要求在 SIGTERM 之后、截止时间之前，就绪检查路由不再返回就绪状态码（或直接拒绝连接）。 |
| `shutdown.newRequests` | `object` | — | 在排空期间发送一个新的 GET 请求，并要求服务拒绝它。需要设置 `readinessWithdrawal: true`。 |
| `shutdown.newRequests.path` (必填) | `string` | — | 一个安全、仅供测试使用的路由。旧工作排空期间，新请求会发到这里。 |
| `shutdown.newRequests.rejectStatuses` | `number[]` | `[503]` | 算作拒绝新请求的状态码。 |
| `shutdown.newRequests.allowConnectionRefused` | `boolean` | `true` | 连接被拒绝时是否也算作拒绝。超时一律不算。 |
| `shutdown.repeatSignalAfterMs` | `number` | — | 在第一次 SIGTERM 之后等待这段时间，趁工作仍在运行时再发送一次 SIGTERM，确认重复的信号不会中断工作。范围 10–300000 ms，且必须小于 `deadlineMs`。 |

上表是自动生成的，列出了每个字段的类型和默认值。下面几节说明这些字段如何配合使用，以及在真实服务中该怎么选。

## command、cwd 和 env

```json title="shutdown-check.json"
{
  "command": ["node", "dist/server.js"],
  "cwd": ".",
  "env": {
    "PORT": "3510",
    "NODE_ENV": "production"
  }
}
```

### `command`

把可执行文件和每个参数分别写成数组中的一项。shutdown-check 不经过 shell 执行命令。

尽量直接启动真正的服务进程：

```json
{ "command": ["node", "dist/server.js"] }
```

能不用 shell 字符串或包装进程，就尽量不用：

```json
{ "command": ["npm", "start"] }
```

包装进程收到 `SIGTERM` 后可能不会转发给服务。它也可能自己先退出，而子进程里的服务仍占着端口，这会导致 [SC302](https://shutdown.jscrate.dev/zh/docs/codes/sc302)。

### `cwd`

`cwd` 基于配置文件所在的目录解析。不写时，命令就在该目录下运行。

如果配置文件是 `config/shutdown-check.json`，下面的写法会让命令从项目根目录启动：

```json
{ "cwd": ".." }
```

### `env`

`env` 会在 CLI 继承的环境变量基础上新增或覆盖变量。所有值都必须是字符串。可以用它给每个测试分配专用端口，或者开启仅供测试使用的路由。

```json
{
  "env": {
    "PORT": "3510",
    "ENABLE_TEST_ROUTES": "1"
  }
}
```

## baseUrl

`baseUrl` 是就绪检查、测试请求、探测路由和新请求这些路径共用的源地址（origin），必须是本机上的普通 HTTP 地址。

| 接受                     | 拒绝                        |
| ------------------------ | --------------------------- |
| `http://127.0.0.1:3510`  | `https://127.0.0.1:3510`    |
| `http://localhost:3510`  | `http://example.com:3510`   |
| `http://[::1]:3510`      | `http://0.0.0.0:3510`       |
| `http://127.0.0.1:3510/` | `http://127.0.0.1:3510/api` |

为一个测试专门预留这个端口。shutdown-check 要求启动前端口是空闲的，进程退出后端口已关闭。并行运行的测试必须使用不同端口。

## 就绪检查

就绪检查（readiness）用来告诉 shutdown-check 服务什么时候启动完成。

```json
{
  "readiness": {
    "path": "/health",
    "status": 200,
    "timeoutMs": 30000,
    "intervalMs": 100
  }
}
```

| 字段         | 默认值 | 允许的取值               |
| ------------ | ------ | ------------------------ |
| `path`       | 必填   | 以 `/` 开头的本地路径    |
| `status`     | `200`  | 100 到 599 之间的 HTTP 状态码 |
| `timeoutMs`  | `10000` | 100 到 300000           |
| `intervalMs` | `100`  | 10 到 10000              |

shutdown-check 会不断轮询这个路由，直到它返回的状态码恰好等于 `status`。连接被拒绝或返回其他状态码时都会重试。如果进程一直在运行却始终没有就绪，结果是 [SC100](https://shutdown.jscrate.dev/zh/docs/codes/sc100)；如果进程在就绪前就退出了，结果是 [SC101](https://shutdown.jscrate.dev/zh/docs/codes/sc101)。

启用撤回就绪状态的检查后，发送 `SIGTERM` 之后也会使用这个路由。

## 测试请求

测试请求（workload）是 `SIGTERM` 到达时必须仍在进行中的请求。

```json
{
  "workload": {
    "path": "/reports/export",
    "method": "POST",
    "headers": { "content-type": "application/json" },
    "body": "{\"rows\":5000}",
    "status": 200,
    "bodyIncludes": "export complete",
    "concurrent": 3,
    "started": {
      "type": "response-headers",
      "timeoutMs": 5000
    }
  }
}
```

| 字段           | 默认值 | 作用                                 |
| -------------- | ------ | ------------------------------------ |
| `path`         | 必填   | 执行这项工作的本地路由               |
| `method`       | `GET`  | HTTP 方法                            |
| `headers`      | `{}`   | 请求头，值必须是字符串               |
| `body`         | 无     | 原始请求体                           |
| `status`       | `200`  | 期望的最终响应状态码                 |
| `bodyIncludes` | 无     | 响应中必须包含的文本，区分大小写     |
| `concurrent`   | `1`    | 请求数量，1 到 20                    |
| `started`      | 必填   | 用来确认测试请求已在进行中的规则     |

选一个可以安全调用、而且耗时足够长的路由，保证请求还没结束时信号就能到达。如果仅凭成功的状态码不足以证明工作已经完成，就加上 `bodyIncludes`。shutdown-check 对每个响应体最多捕获 1 MiB。

发送 `SIGTERM` 后，每个请求都必须在截止时间前完成。超时、请求被中断、状态码不对或响应体不对，分别会产生 `SC200` 到 `SC203` 中的相应诊断码。

## 启动确认

启动确认（start barrier）用来避免误判为通过：如果测试请求在信号到达前就已经结束，检查不应该通过。启动确认有两种类型，选其中一种。

### response-headers

```json
{
  "started": {
    "type": "response-headers",
    "timeoutMs": 5000
  }
}
```

每个测试请求都必须先收到响应头，同时响应体仍未结束。这种启动确认支持 1 到 20 个并发请求。测试路由通常会先调用 `response.flushHeaders()`，稍后再发送完响应体。

### probe

```json
{
  "started": {
    "type": "probe",
    "path": "/test/work-active",
    "inactiveStatus": 204,
    "activeStatus": 200,
    "timeoutMs": 5000,
    "intervalMs": 50
  }
}
```

发出测试请求之前，探测路由必须返回 `inactiveStatus`。请求还在进行时，它必须改为返回 `activeStatus`。这两个状态码必须不同。

`probe` 只支持一个测试请求，适用于工作全部完成后才发送响应头的处理函数。如果这个路由会暴露应用的内部状态，请只在测试环境中开放它。

完整的服务端示例和失败时的输出，见[进行中的工作与启动确认](https://shutdown.jscrate.dev/zh/docs/in-flight-work)。

## 关闭

`shutdown` 对象定义了第一次 `SIGTERM` 之后的关闭约定。

```json
{
  "shutdown": {
    "signal": "SIGTERM",
    "deadlineMs": 15000,
    "exitCode": 0,
    "readinessWithdrawal": true,
    "newRequests": {
      "path": "/test/new-work",
      "rejectStatuses": [503],
      "allowConnectionRefused": true
    },
    "repeatSignalAfterMs": 500
  }
}
```

### `signal`

只支持 `SIGTERM`。除非你想在配置里显式写明信号，否则可以省略这个字段。

### `deadlineMs`

默认值是 10000 ms。计时从第一次发送信号开始，涵盖进行中的响应、排空流量相关的检查以及进程退出。应把它设得大于最慢的正常请求加上清理所需的时间，同时小于部署平台强制终止进程的截止时间。

### `exitCode`

默认值是 `0`。进程必须以这个退出码退出，完全一致才算通过。被信号终止的进程没有退出码，因此不算匹配。

### `readinessWithdrawal`

设为 `true` 时，就绪检查路由必须在截止时间前不再返回 `readiness.status`。返回其他状态码或拒绝连接都算通过，超时则不算。

### `newRequests`

这项检查要求 `readinessWithdrawal: true`。就绪状态撤回之后、原来的测试请求仍在进行时，shutdown-check 会向 `newRequests.path` 发送一个 `GET` 请求。

如果这个请求返回的状态码在 `rejectStatuses` 中，或者 `allowConnectionRefused` 为 `true` 且连接被拒绝，检查通过。超时或返回正常的成功响应都会失败。

这个请求是真实发出的，所以请使用一个可以安全调用的测试路由。[就绪检查与排空指南](https://shutdown.jscrate.dev/zh/docs/guides/readiness-and-draining)介绍了返回 `503` 和关闭监听这两种做法。

### `repeatSignalAfterMs`

设置后，shutdown-check 会在工作仍在进行时再发送一次 `SIGTERM`。这个值必须小于 `deadlineMs`，也必须小于测试请求的持续时间。用它可以发现那些收到重复信号后就提前强制退出的处理函数。

## 一份严格的完整配置

这个示例会检查三个请求、响应内容、撤回就绪状态、拒绝新请求、重复信号、进程退出和端口关闭：

```json title="shutdown-check.json"
{
  "command": ["node", "dist/server.js"],
  "cwd": ".",
  "env": { "PORT": "3510", "NODE_ENV": "production" },
  "baseUrl": "http://127.0.0.1:3510",
  "readiness": { "path": "/health", "status": 200, "timeoutMs": 30000 },
  "workload": {
    "path": "/slow",
    "status": 200,
    "bodyIncludes": "work complete",
    "concurrent": 3,
    "started": { "type": "response-headers", "timeoutMs": 5000 }
  },
  "shutdown": {
    "deadlineMs": 15000,
    "exitCode": 0,
    "readinessWithdrawal": true,
    "newRequests": {
      "path": "/test/new-work",
      "rejectStatuses": [503],
      "allowConnectionRefused": true
    },
    "repeatSignalAfterMs": 500
  }
}
```

测试请求保持进行的时间要足够长，覆盖就绪检查轮询、新请求和重复信号这几步，同时又要在截止时间前完成。

## 常见校验错误

| 消息                                                                                                                 | 修复方法                                 |
| -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| `command must be a non-empty array of strings, for example ["node", "server.js"]`                                    | 把可执行文件和参数拆成数组               |
| `baseUrl must use http://localhost, http://127.0.0.1 or http://[::1]`                                                | 使用本地的普通 HTTP 地址                 |
| `readiness.path must be a local path beginning with one /`                                                           | 写成 `/health`，不要写 URL 或 `health`   |
| `workload.started.type must be response-headers or probe`                                                            | 从支持的两种启动确认中选一种             |
| `workload.concurrent above 1 requires the response-headers start barrier, which verifies every request individually` | 改用这种启动确认，或只发一个请求         |
| `shutdown.newRequests requires shutdown.readinessWithdrawal: true so rejection is tested after drain begins`         | 开启撤回就绪状态                         |
| `shutdown.repeatSignalAfterMs must be shorter than shutdown.deadlineMs`                                              | 调小重复信号的延迟，或调大截止时间       |
| `v0.1 supports only SIGTERM`                                                                                         | 删除 `signal`，或将它设为 `SIGTERM`      |

在 1.0.1 版本的包中，报错信息里的确写的是 `v0.1`。按症状分类的错误说明见[故障排查](https://shutdown.jscrate.dev/zh/docs/troubleshooting)。

## 可以用 TypeScript 代替 JSON 吗？

CLI 只读取 JSON。在测试或脚本中，可以用 `defineConfig()` 创建一个带类型的配置对象，再传给 `checkShutdown()`：

```ts title="shutdown.test.ts"
import { checkShutdown, defineConfig } from "shutdown-check";

const config = defineConfig({
  command: ["node", "dist/server.js"],
  baseUrl: "http://127.0.0.1:3510",
  readiness: { path: "/health" },
  workload: {
    path: "/slow",
    started: { type: "response-headers" },
  },
  shutdown: { readinessWithdrawal: true },
});

const result = await checkShutdown(config);
```

这个对象遵循同样的规则和默认值。校验和结果处理见 [Node API](https://shutdown.jscrate.dev/zh/docs/node-api)。

## 相关内容

- [快速开始](https://shutdown.jscrate.dev/zh/docs/quick-start)
- [CLI 参考](https://shutdown.jscrate.dev/zh/docs/cli)
- [Node API](https://shutdown.jscrate.dev/zh/docs/node-api)
- [进行中的工作与启动确认](https://shutdown.jscrate.dev/zh/docs/in-flight-work)
- [诊断码](https://shutdown.jscrate.dev/zh/docs/codes)
