nginx 1.31.8 把 working_directory 的相对路径改成相对前缀目录,不再相对启动时的当前目录。配了一个不存在的目录时,1.31.8 会在启动阶段 emerg 退出;1.31.7 还会留下只监听、没有 worker 的 master。下面用官方镜像 nginx:1.31.7-alpine 和 nginx:1.31.8-alpine,前面挂一个 Spring Boot 4.1.1 反代,对照跑了一遍。
1.31.8 是 10 月 21 日发布的 mainline。CHANGES 里写得很清楚:path specified in the working_directory directive is relative to nginx prefix directory, but not to the current directory。同版本还修了无效目录导致 worker 退出、HTTP/3 严格对齐平台上的 SIGBUS,以及 kernel TLS 关连接时的 recv() failed (5: Input/output error) 噪音。上一周的 1.30.6 / 1.31.7 安全修复合集可以看 nginx 1.30.6 和 1.31.7 修了五个漏洞,这里只盯 working_directory 这条行为变化。
实验怎么摆
后端是一个最小的 Spring Boot 4.1.1 Web 应用,JDK 21.0.12.1,只暴露 /api/ping:
@SpringBootApplication
@RestController
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@GetMapping("/api/ping")
public String ping() {
return "pong-from-spring-boot";
}
}
nginx 配置里写 working_directory work;,用 -p /prefix 指定前缀,容器启动时的工作目录是 /launch。前缀下和启动目录下各放一个 work/marker.txt,内容分别是 IN_PREFIX 和 IN_LAUNCH,方便对照 worker 最终落到哪。反代段把 /api/ 转到宿主机上的 Spring Boot:
worker_processes 1;
error_log logs/error.log warn;
pid logs/nginx.pid;
working_directory work;
events { worker_connections 64; }
http {
access_log logs/access.log;
upstream springboot {
server host.docker.internal:18081;
}
server {
listen 18080;
location /api/ {
proxy_pass http://springboot;
proxy_set_header Host $host;
}
location / {
root html;
return 200 "nginx-static\n";
}
}
}
所有场景由一次 verify.sh 跑完,启动命令会 echo 进日志,下面的输出都从同一次 verify.log 原样截取。
相对路径:1.31.7 跟启动目录,1.31.8 跟前缀
先把 Spring Boot 拉起来:
+ java -jar target/nginx-workdir-demo-0.0.1-SNAPSHOT.jar --server.port=18081
+ curl -s http://127.0.0.1:18081/api/ping
pong-from-spring-boot
再分别起 1.31.7 和 1.31.8。容器 -w /launch,进程参数是 nginx -p /prefix -c conf/nginx.conf。启动后用宿主机上的 readlink /proc/<pid>/cwd 看 master 和 worker 的实际工作目录:
===== B: nginx 1.31.7 working_directory work (start cwd=/launch, prefix=/prefix) =====
+ docker top ngx-cwd-1317
PID USER COMMAND
1259193 root nginx: master process nginx -p /prefix -c conf/nginx.conf -g daemon off;
1259214 postgres nginx: worker process
+ sudo readlink /proc/$master/cwd /proc/$worker/cwd
master_pid=1259193 master_cwd=/launch
worker_pid=1259214 worker_cwd=/launch/work
+ curl -s http://127.0.0.1:18087/api/ping
pong-from-spring-boot
+ curl -s http://127.0.0.1:18087/
nginx-static
===== B: nginx 1.31.8 working_directory work (start cwd=/launch, prefix=/prefix) =====
+ docker top ngx-cwd-1318
PID USER COMMAND
1259365 root nginx: master process nginx -p /prefix -c conf/nginx.conf -g daemon off;
1259407 postgres nginx: worker process
+ sudo readlink /proc/$master/cwd /proc/$worker/cwd
master_pid=1259365 master_cwd=/launch
worker_pid=1259407 worker_cwd=/prefix/work
+ curl -s http://127.0.0.1:18088/api/ping
pong-from-spring-boot
+ curl -s http://127.0.0.1:18088/
nginx-static
master 两边都还停在启动目录 /launch。worker 在 1.31.7 上是 /launch/work,在 1.31.8 上是 /prefix/work。也就是说相对路径的解析基准变了:旧版本跟你怎么启动有关,新版本只跟 -p 前缀有关。反代到 Spring Boot 两边都正常,说明这条变化不挡代理路径本身,只动 worker 的 cwd。
docker top 里 worker 的 USER 显示成 postgres,是宿主机按容器内 uid 反查用户名的结果;镜像里跑的仍是 nginx 用户,不影响 cwd 结论。
无效目录:旧版空转监听,新版直接起不来
把配置改成 working_directory nosuchdir;,前缀下不建这个目录,再各起一次:
===== C: nginx 1.31.7 working_directory nosuchdir (invalid) =====
+ docker ps -a --filter name=ngx-inv-1317
status=Up 1 second
+ processes inside (if running)
1 root 0:00 nginx: master process nginx -p /prefix -c conf/nginx.conf -g daemon off;
+ prefix/logs/error.log
2026/10/23 00:11:18 [alert] 7#7: chdir("nosuchdir") failed (2: No such file or directory)
2026/10/23 00:11:18 [alert] 1#1: worker process 7 exited with fatal code 2 and cannot be respawned
+ listen sockets inside
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:18080 0.0.0.0:* LISTEN 1/nginx.conf -g dae
+ wget -T 2 -qO- http://127.0.0.1:18080/ (expect hang or fail without worker)
wget: download timed out
===== C: nginx 1.31.8 working_directory nosuchdir (invalid) =====
+ docker ps -a --filter name=ngx-inv-1318
status=Exited (1) 1 second ago
+ docker logs ngx-inv-1318 (tail)
2026/10/23 00:11:21 [emerg] 1#1: stat() "/prefix/nosuchdir" failed (2: No such file or directory)
nginx: [emerg] stat() "/prefix/nosuchdir" failed (2: No such file or directory)
1.31.7 的 worker 在 chdir 失败后以 fatal code 2 退出,而且 cannot be respawned;master 还活着,18080 仍在 LISTEN,请求会卡到超时。1.31.8 在启动时就对 /prefix/nosuchdir 做 stat,失败直接 emerg,容器状态是 Exited (1),不会留下半活的监听。
CHANGES 对应的 Bugfix 原文是:worker processes exited on start or during reconfiguration if an invalid directory was specified in the working_directory directive。1.31.8 把相对路径锚定到前缀,又在启动阶段校验目录存在,这两件事是绑在一起的。
版本确认
nginx version: nginx/1.31.7
nginx version: nginx/1.31.8
镜像分别是 Docker Hub 上的 nginx:1.31.7-alpine 和 nginx:1.31.8-alpine。稳定版分支当前仍是 1.30.6,这条 working_directory 行为变化只出现在 1.31.8 mainline。
一起交流
分享你的思考,让讨论更进一步。