把 nginx 的控制套接字绑到一个普通 TCP 端口上,从容器外发一条不带任何请求头、不带任何凭据的 curl,它把两个 worker 进程的实时 PID 直接交了出来。再发一条同样空手的 curl,运行中的配置就被换成了另一份。

打开网易新闻 查看精彩图片

这是 nginx 在 2026 年 9 月 2 日加入开源版本的控制 API,随 1.31.5 落地。它让你用 HTTP 触发重载,替代 kill -HUP 或 nginx -s reload,并且拿回一个结构化响应,而不是错误日志里的一行字。测试在 1.31.8 上进行,当前主线版本原样保留了这个特性。

开启方式是一个新的命令行参数,不是配置指令:

  • nginx -g 'daemon off;' -l unix:/tmp/nginx_control.sock
  • 官方 nginx:1.31.8 镜像已内置 --with-control-api 编译选项,无需自行构建
  • 在容器内逐个 GET 可以走完端点树:根路径返回 ["1"],/1/ 返回 ["control","nginx"],/1/control 返回 ["processes","config"]

GET /1/control/config 以 JSON 返回当前配置文件的名字和完整内容。PATCH /1/control/config 不带请求体时,会从磁盘重新读取文件并重载——这是最值得关注的一个端点。

故意把配置写坏,把 return 拼成 retrun,两种方式各重载一次。控制 API 返回 HTTP/1.1 422 Unprocessable Entity,响应体是 JSON,logs 数组里装着编译器原话:unknown directive "retrun" in /etc/nginx/nginx.conf:6。信号方式则是在错误日志里留下同样一行,外加一个进程退出码。

两种方式都拒绝坏配置、保留旧配置继续服务,这一点 nginx 一直做得安全,每次尝试后旧配置仍在生效。差别在于调用脚本能拿到什么:信号路线只给退出码和一行只存在于错误日志里的文字,部署脚本要么去解析日志文本,要么信任退出码然后祈祷。控制 API 给的是 422 加上精确的编译器消息,就在响应体里,不需要 tail 任何文件。

它并没有更快。 在发现错误报告差异之前,本以为 API 路线至少会快一点,毕竟省掉了发信号和等 master 察觉的过程。同一容器内各做五次重载,在两份有效配置之间交替,保证每次都是真实重载而非空操作。这些数字包含 docker exec 自身的开销,只能当作两种方法的横向对比,而非 nginx 真实重载延迟。按这个对比,信号路线即便有差别也是略快,从未更慢。控制 API 的价值完全在于它告诉了你什么,而不在于它多快到达。

并发下在途请求也不受影响。 把一个请求代理到会睡三秒才应答的后端,请求发出,一秒后重载 nginx,API 和信号各做一次,每组三次试验。没有连接被丢弃,与不重载的基线(3.012 秒)没有差别。这个结果一开始让人起疑,因为"没有差别"也是测试坏掉时的默认答案。于是核查了最明显的失败可能:重载是否真的发生在请求进行期间。worker PID 证实了这一点——慢请求还在途中时,/1/control/processes 里旧 worker 显示 "exiting":true,新 PID 已经出现,排空过程确实与请求重叠。nginx 的优雅关闭不管从哪扇门触发都一样,这属于值得验证而非想当然的细节。

它到底暴露了什么。 nginx 自己的发布说明称,把控制接口绑到网络端口"强烈不建议",并明确该 API 自身不带任何认证。这里要的是风险的具体形状,不只是那句警告。

  • 用 -p 19000:9000 把容器端口映射出来,从宿主机直接 curl /1/control/processes,返回两个 worker 的 PID 和 exiting 状态
  • 再 curl -X PATCH /1/control/config,返回 {"logs":[]},运行中的配置被真正替换,对外提供的内容立刻改变
  • 两次请求都从容器外的宿主机发出,没有 Authorization 头,没有 API key,只有一个裸 URL

没有登录、没有令牌、没有内置 IP 白名单。任何能触达那个端口的人,都能读取实时进程状态并推送一次完整的配置替换。

官方给出的缓解办法是把控制套接字绑到文件系统上,依靠普通文件权限。默认 unix 套接字起来时权限是 srw-------,所有者和组都是 root,其他人没有任何权限。以 nobody 用户尝试连接,退出码 7,连接在到达 nginx 之前就被拒绝。这个绕法是真的:权限收紧的 unix 套接字确实能挡住同主机上的非特权用户,而 API 自己在网络接口上什么都不挡。

开着的代价。 对比 master 进程的常驻内存,同镜像、同样近乎空白的配置,加不加 -l 参数相差约 304KB,约 3.3%,仅仅是为了让监听器活着、且没有任何连接。数字很小,做容量规划的人不会注意到,但对一个可能开一次就忘掉的功能来说,这是真实且非零的成本。

如果部署脚本里已经在跑 nginx -s reload,并且遇到过失败却悄无声息、事后才发觉的情况,仅凭结构化错误这一条,控制 API 就值得接进去——一个带编译器原话的 422,胜过 grep 一份你可能根本没在收集的错误日志。别把它绑到 unix 套接字以外的任何地方,也别把套接字的默认权限当成可选项,那是这个功能唯一的访问控制。如果正在审计一台已经跑着 nginx 1.31.5 或更新版本的主机,现在就去查有没有人用 -l 把它指向了网络地址,因为功能本身不会阻止任何请求生效。