后端容器未启动,Nginx 报 host not found in upstream
Docker 中一个可选后端没有启动,Nginx 因 host not found in upstream 无法加载配置。适用于开源版 Nginx 1.27.3 及以上版本,通过 resolve、zone 和 resolver 配置运行时解析,并核对网络、请求路径和回退条件。
一台 Nginx 代理多个 Docker 服务,其中一个服务暂时不需要启动。启动 Nginx 时,日志却停在类似的错误上,其他已经运行的服务也无法通过这台代理访问:
[emerg] host not found in upstream "optional-app:3000" in /etc/nginx/conf.d/apps.conf:6
这条错误发生在加载配置时:Nginx 需要解析配置里的后端名称,但当时无法获得地址。它与代理已经运行、访问后端时出现的 Connection refused 属于不同阶段。如果后端名称只会在相应容器加入网络后出现,把它静态写进 upstream,就可能让一个可选服务成为整台代理的启动条件。
下面的配置适用于开源版 Nginx 1.27.3 及以上版本的 HTTP 代理,且 Nginx 容器与后端使用同一个 Docker 自定义网络。目标是让后端暂时没有 DNS 记录时,Nginx 仍能加载配置;这个后端的请求仍可能返回 502,直到名称解析和应用本身都恢复。
名称错误和可选服务缺席要分开处理
先确认报错中的名称对应哪个服务,以及这个服务是否允许暂时不运行。例如,配置写的是 optional-app:3000,实际 Compose 服务却叫 optional_api,动态解析只会让这个错误延后暴露,不能修正名字。
核对报错所在配置、Compose 服务名或网络别名,以及代理与后端实际加入的网络。两份 Compose 项目即使各自都有一个名为 default 的网络,也不等于容器能互相解析。端口应当是应用在容器内监听的端口,而非宿主机发布端口。
如果名称只在后端运行时可解析,后端有意停用或尚未创建时不存在,并且错误发生在 Nginx 启动阶段,就可以按下面的方法处理。后端已经能解析、只是应用还没监听端口,应继续检查应用就绪状态;DNS 配置不能解决应用启动慢的问题。
还要核对真正加载配置的 Nginx 版本。在使用同一镜像和配置的本地校验环境中执行:
nginx -v
nginx -T
nginx -T 会检查并输出包含进来的配置。用它确认错误行属于哪个 upstream、是否还有别处静态引用同一个后端。输出可能包含凭据和内部地址,不要整份贴到公开页面。自定义了启动参数或配置路径的实例,检查时也要沿用相同参数,避免验证另一份配置。
容器内 Nginx 的版本才决定参数是否可用,宿主机安装的另一套 Nginx 不能作为依据。server ... resolve 在开源版中的版本边界是 1.27.3;更早版本中,这项能力属于商业订阅范围。旧开源版本不能直接照抄后面的配置。
将后端名称改为运行时解析
下面是放在 http 上下文中的示例片段。许多镜像在这个位置包含 conf.d/*.conf,但仍应以实际主配置为准。请用它调整对应 upstream 和虚拟主机,不要把同名 upstream、重复监听配置直接追加进去。
resolver 127.0.0.11 valid=10s;
resolver_timeout 5s;
upstream optional_backend {
zone optional_backend 64k;
server optional-app:3000 resolve;
}
server {
listen 80;
server_name gateway.example.test;
location = /proxy-health {
return 200 "nginx\n";
}
location / {
proxy_pass http://optional_backend;
}
}
optional-app 是示例 Docker 服务名或网络别名,3000 是示例容器内端口,gateway.example.test 是示例虚拟主机名。optional_backend 则是 Nginx 内部 upstream 组名,不需要在 Docker DNS 中注册。
这三个配置各有作用,缺一项都不能按示例工作:
resolve让这一条后端记录在运行期间解析名称、更新地址,不再要求配置加载时就得到固定地址。zone为 upstream 提供共享内存区,运行时更新的上游配置和状态放在这里。64k是这个小型示例使用的容量,大量后端应按实际配置评估。resolver指定运行时查询所用的 DNS 服务器。Docker 自定义网络内的容器可使用内置 DNS127.0.0.11。
只增加 resolver、却继续保留不带 resolve 的静态 server optional-app:3000;,不能达到相同效果。还应检查其他配置文件:任何仍在加载阶段解析缺席后端的静态引用,都可能继续阻止 Nginx 启动。
127.0.0.11 有明确的环境前提。Nginx 运行在宿主机上、使用另一种容器网络模式,或后端名称来自企业 DNS 时,应选择该环境中能够解析目标名称的 DNS;不要把 Docker 容器里的回环地址搬到宿主机配置。公共 DNS 通常也不知道 Docker 的私有服务名,换成公共 DNS 并不能补出这些记录。
示例中的 valid=10s 覆盖 DNS 答案的缓存有效期,resolver_timeout 5s 限制解析操作的超时时间。后端启动后不一定在十秒内恢复访问:DNS 错误重试、地址是否已出现以及应用是否就绪,都影响实际恢复。
保留原来的转发路径和安全配置
示例的 proxy_pass http://optional_backend; 没有附带 URI。在没有其他 rewrite 改写请求的情况下,它会保留请求的路径和查询参数。迁移已有虚拟主机时,原来的路径处理仍需单独核对。
不要为了推迟解析,随手把 upstream 换成变量,再把原来的路径拼到变量后面。带变量的 proxy_pass 在指定 URI 时有不同的替换规则,可能把不同请求都转发成同一个固定路径。旧版开源 Nginx 若暂时不能升级,可以评估变量方案,但必须同时验证 URI、重定向和原有代理行为;本篇配置不适用于那个分支。
上面的 HTTP 虚拟主机仅用于说明解析关系。已有站点的 HTTPS、认证、访问控制、代理请求头和超时设置应当保留。/proxy-health 是示例中的代理自身检查路径,只能证明 Nginx 能响应,不能据此判断后端正常;应按现有访问控制安排这个路径,避免覆盖业务已有路由。
如果版本不支持 resolve,配置检查可能直接报参数无效。需要升级时,选用满足版本要求且仍受支持的发行版本,在本地用完整现有配置验证。不要仅为满足最低版本数字,就把生产镜像固定回某个旧发布版本。
验证缺席后端不会拖停其他站点
先保存现有配置和正在使用的镜像版本,再在本地校验环境测试后端存在、缺席和重新出现的情况。不要为验证这篇配置而停掉生产后端。
对调整后的完整配置执行:
nginx -t
配置检查通过后,若目标 Nginx 主进程仍在运行,可以用相同实例的控制方式重载:
nginx -s reload
如果容器已因加载失败退出,重载命令没有可用的主进程可以通知。修正它挂载的配置后,按原来的 Compose 项目和参数启动代理服务。例如,服务名为 proxy 时:
docker compose up -d --no-deps proxy
原部署使用了 -f、--env-file、项目名等参数的,仍需保留。--no-deps 避免这次操作顺带启动依赖服务;它不证明代理镜像或其他配置一定正确,也不应替代本地检查。
验收时分别观察以下结果:
- 后端名称暂时不存在时,Nginx 能启动并保持运行,代理自身检查路径和其他正常站点仍可访问。
- 请求缺席的后端仍会失败,常见表现是 502;如果配置了错误页或备用后端,具体响应可能不同。日志中的解析失败应局限于相应后端,不能把“代理进程没退出”当成全站业务恢复。
- 后端重新加入同一网络、DNS 返回地址且应用开始监听后,后续请求能够恢复,期间不需要为了更新这条地址而手动重载 Nginx。
- 原有的路径、查询参数、重定向、认证和其他站点行为没有改变。
如果后端回来后仍不通,沿着“名称能否解析—地址和端口是否正确—应用能否响应”继续缩小范围。不要只盯着 DNS 缓存等待,也不要用固定容器 IP 遮住服务名或网络配置错误。
回退前确认旧配置的启动条件
只修改配置时,恢复备份,先检查再重载;同时升级过镜像时,使用已保留的镜像版本和与之兼容的配置回退。回退不需要删除应用数据卷。
如果旧配置会在加载时解析这个后端,恢复它之前必须让后端名称重新可解析,否则旧配置仍无法通过检查。Nginx 对失败的重载会保留原来的工作进程和配置,但磁盘上那份错误配置依然会影响下一次重启。应修正磁盘配置并再次检查,不能因当前连接还能访问就留下失败版本。


