LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

用了3年Nginx,这些坑我全踩过(基础篇)

admin
2026年3月27日 15:59 本文热度 50

本文是「Nginx Docker 避坑」系列第一篇,本系列共四篇

后续篇目:反向代理 502/504 排查 → SSL 与 WebSocket → 生产运维与镜像管理

我在微信群里看到同样的问题,少说出现了几十次。

有人说 Nginx 里写了 upstream,容器跑起来了,就是 502。有人说端口映射了 80,访问死活不通。还有人说昨晚弄到凌晨两点,最后发现改了一行配置重启就好了,但不知道为什么。

这些问题,根子上都出在容器网络这一块。

今天这篇,把我踩过的那几个最坑的地方,完完整整梳理一遍。


先说最经典的那个误解

很多人第一次用 Docker 跑 Nginx 做反向代理,会这么写配置:

upstream backend {
    server localhost:3000;
}

然后发现,完全不通

curl 一下,连接拒绝。看日志,满屏 502。

为什么?

因为这里有一个很多人没意识到的基础概念:容器里的 localhost,指的是容器自己,不是你的宿主机。

Docker 每个容器都有独立的网络命名空间,就好比每个容器住在一个隔离的小房间里,localhost 就是这个小房间的内部地址。你的 Node.js 服务跑在宿主机上,对容器来说是"另一台机器",跟你从家里连公司服务器没区别。

这个认知如果没建立起来,后面踩再多坑也是白踩。


那怎么写才对?

根据你的部署场景,有三种解法,从将就到正规:

🔧 方法一:用宿主机的网关 IP(临时方案)

upstream backend {
    server 172.17.0.1:3000;
}

172.17.0.1 通常是 Docker bridge 网络的网关地址,也就是宿主机在这个虚拟网络里的 IP。大部分情况下这个地址是固定的,可以用,但不够优雅。

Docker 20.10 之后有更好的写法,启动容器时加一个参数:

docker run --add-host=host-gateway:host-gateway nginx

然后配置里写:

upstream backend {
    server host-gateway:3000;
}

🔧 方法二:用 host 网络模式(开发图省事用)

docker run --network=host nginx

这样容器直接共用宿主机的网络,localhost 就真的是宿主机了。图省事可以,生产环境不推荐,失去了容器隔离的意义。

✅ 方法三:把所有服务都容器化,走 Docker 内部网络(生产标准)

services:
  nginx:
    image: nginx:1.26.2-alpine
    networks:
      - app-network
  backend:
    image: your-node-app
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

然后 Nginx 配置直接用服务名:

upstream backend {
    server backend:3000;
}

这才是最干净的玩法。Docker 有内置 DNS,服务名就是 hostname,IP 变了它自己解析,你不需要关心。

能用方法三,就不要用方法一二。


容器 IP 不要写死,这不是建议

有人习惯先 docker inspect 看一下容器 IP,比如看到 172.17.0.3,然后写进配置文件里。

能跑,但迟早出问题。

容器一重启,IP 就变了。今天 172.17.0.3,重启后可能变成 172.17.0.5,然后你就开始找为什么好好的服务突然 502 了,翻半天才发现是 IP 变了。

在同一个 Docker 网络里,永远用服务名通信,不要用 IP。


一个细节:默认网络和自定义网络不一样

很多人不知道这个差别,但它很关键。

Docker 有一个默认的 bridge 网络,也叫 docker0。你直接 docker run 不加任何网络参数,容器就进了这个默认网络。

默认 bridge 里的容器,无法通过服务名互相解析,只能用 IP。

只有自定义 bridge 网络才支持 DNS 服务发现,才能用服务名通信。

Docker Compose 之所以能直接用服务名,就是因为 Compose 默认帮你创建了一个自定义 bridge。但你如果手动 docker run 放进默认网络,就享受不到这个特性。

结论很简单:生产环境别用默认网络,自己建一个。

docker network create app-network

`depends_on` 不是你想的那样

这个坑很多人栽进去过,我也是。

services:
  nginx:
    depends_on:
      - backend

看起来 Nginx 会等 backend 好了再启动,但实际上 depends_on只保证容器启动了,不保证服务就绪了

Node.js 应用可能还在连数据库,Java 应用可能热身要三十秒,容器是"起来了",但服务还没准备好接请求,这时候 Nginx 上线就遇到 upstream 不可用,照样出问题。

正确的写法是配合健康检查:

services:
  nginx:
    depends_on:
      backend:
        condition: service_healthy  # 等健康检查通过

  backend:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

start_period 给服务留足启动时间,retries 控制重试次数,这才是生产级的启动依赖写法。


端口映射那些让人迷糊的事

坑一:address already in use

Error starting userland proxylisten tcp4 0.0.0.0:80bindaddress already in use

每个人都见过这行报错。排查很简单:

sudo ss -tlnp | grep :80

看看是谁占了 80 端口。常见的是宿主机上直接装的 Nginx 或 Apache。

sudo systemctl stop nginx
sudo systemctl disable nginx  # 长期用 Docker,顺手禁掉自启

坑二:映射了端口,Nginx 里该写哪个

-p 8080:80 这个映射,很多人以为 Nginx 里要改成 listen 8080

错。

-p 8080:80 的意思是:把宿主机的 8080,映射到容器内部的 80。Nginx 在容器里,它根本感知不到宿主机用的什么端口,它只需要监听容器内部的 80:

server {
    listen 80;  # 写容器内部端口,和宿主机无关
}

坑三:只绑定了 IPv6

有时候你明明映射了端口,某些客户端访问就是不通。检查一下:

ss -tlnp | grep :80
# 如果只看到 :::80,没有 0.0.0.0:80,就是只绑了 IPv6

启动时明确指定 IPv4:

docker run -p 0.0.0.0:80:80 nginx

最后一个隐藏坑:upstream DNS 缓存

这个问题藏得比较深,不是所有人都会遇到,但遇到了会很困惑。

Nginx 启动时会把 upstream 里的服务名解析成 IP 并缓存下来。如果 backend 容器重启了,IP 变了,Nginx 不知道,还在用旧 IP,结果请求全 502 了。但你看 backend 服务是好的,看 Nginx 也在跑,就是不通,百思不得其解。

解决方式是加 resolver 指令,并且用变量传给 proxy_pass

http {
    resolver 127.0.0.11 valid=30s;  # Docker 内置 DNS

    server {
        set $backend_url http://backend:3000;

        location /api/ {
            proxy_pass $backend_url;  # 用变量,运行时动态解析
        }
    }
}

proxy_pass 接收变量时,Nginx 会在每次请求时重新解析 DNS,而不是启动时缓存一次。这个细节很多文章都没提,但在容器环境里相当重要。


快速排查命令

遇到问题先跑这几条,基本能定位:

# 容器在哪个网络?
docker network ls
docker inspect nginx-container | grep -A5 Networks

# 网络里有哪些容器?
docker network inspect app-network

# 从 Nginx 容器内部测试连通性(这步最关键)
docker exec -it nginx-container curl http://backend:3000/health

# 端口占用情况
sudo ss -tlnp | grep :80

# 容器端口映射
docker port nginx-container

总结一下

容器网络这块,记住三件事就够了:

① 容器里的 localhost 是容器自己,不是宿主机
② 生产用自定义 bridge 网络,不用默认 docker0
③ upstream 服务名不会自动更新,要用 resolver + 变量做动态解析

这三条搞清楚,80% 的"打不通"问题就不会发生了。


下一篇讲配置语法和反向代理。proxy_pass 末尾加不加斜杠,是两种完全不同的行为,这个坑我见过不止一百次了。

如果这篇对你有帮助,转发给还在踩这些坑的同事吧。


阅读原文:原文链接


该文章在 2026/3/27 16:19:11 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved