Docker 容器内 Nginx 与宿主 Nginx 端口冲突排查

    发布时间:2026-09-24 04:31 更新时间:2026-09-24 04:31 阅读量:0

    很多站长在服务器上先装了宿主 Nginx 跑主站,后来想把测试站或新业务放进 Docker,于是容器里也起了一个 Nginx。执行 docker run -p 80:80 时直接报错 bind: address already in use;或者改成 -p 8080:80 之后能起来,但浏览器访问域名还是进了宿主那个站点。这类问题不是 Docker 坏了,而是宿主的 80 端口、Docker 的 iptables 转发链、以及容器的网络模式三者叠加造成的。下面按排查顺序拆开讲。

    先分清是「谁占了端口」还是「映射没生效」

    第一步永远是确认宿主 80/443 上到底有谁在监听。不要只凭印象,用命令看实际结果:

    # 看宿主 80、443 的监听进程(需要 root 或 sudo)
    ss -lntp | grep -E ':80|:443'
    
    

    看容器里 Nginx 实际监听的端口

    docker exec -it my-nginx ss -lntp

    看某个容器的端口映射关系

    docker port my-nginx

    如果宿主上已经有 0.0.0.0:80 的 nginx 或 apache 在监听,那么 docker run -p 80:80 必然失败,这是内核层面的端口抢占,跟 Docker 无关。此时你有三条路:停掉宿主 Nginx 把 80 让给容器;或者容器不用 80,改用 8080 之类的高位端口,再由宿主 Nginx 反代过去;或者干脆用 network_mode: host 让容器直接共享宿主网络栈(后面单独说)。

    还有一种容易被忽略的情况:端口显示没人监听,但 Docker 仍报占用。这通常是上一次容器没清理干净,或者 docker-proxy 进程残留。可以查一下:

    docker ps -a | grep -i nginx
    ps -ef | grep docker-proxy | grep -v grep
    
    

    确认端口真的空闲

    ss -lntp | grep ':80 '

    确认空闲后重新 docker start 或 docker run 即可。若仍有残留,停掉对应容器再删除,不要直接 kill 系统进程。

    端口映射背后的 iptables 链路

    Docker 的 -p 映射并不是靠一个常驻守护进程完成转发的(虽然新版 Docker 也会起 docker-proxy 做用户态兜底),主线是往宿主 iptables 的 nat 表里写 DNAT 规则。理解这条链路,才能解释「端口放行了却访问不到」和「访问到了但不是预期站点」。

    当你写 -p 8080:80,Docker 大致会做两件事:在 DOCKER 链里加一条 DNAT,把发往宿主 8080 的流量改写到容器 IP 的 80;同时在 filter 表的 DOCKER 链里放行转发流量。可以用下面的命令查看:

    # 只看 nat 表里 Docker 相关规则
    iptables -t nat -L DOCKER -n --line-numbers
    
    

    看 filter 表放行规则

    iptables -L DOCKER -n --line-numbers

    看某容器 IP

    docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-nginx

    这里有两个常见坑。第一,如果服务器上还跑了 firewalld、ufw,或者你自己写过 iptables -P FORWARD DROP,可能会把 Docker 插入的转发规则覆盖掉,表现为「映射配了但外网访问不通」。排查时先把防火墙临时停掉验证一次,再决定放行规则,不要直接长期关闭防火墙。第二,Docker 的规则默认插在链首,如果你手工在 PREROUTING 里写了更靠前的 REDIRECT 或 DNAT,流量会先被你的规则截走,容器自然收不到。

    另外,-p 8080:80 默认绑定 0.0.0.0,等于把容器端口暴露在所有网卡上。只想本机反代使用,建议写成 -p 127.0.0.1:8080:80,避免被外部直接扫到,也少一层安全暴露面。这一步对「宿主 Nginx 反代容器」的架构尤其重要。

    network_mode 怎么选:bridge、host 与反代组合

    端口冲突的根源,很多时候是网络模式没选对。默认的 bridge 模式下,容器有独立 IP,宿主通过 -p 做 NAT,所以宿主 80 和容器 80 可以共存,只是不能同时映射到宿主同一个端口。这是最推荐的常规做法。

    如果业务对性能或来源 IP 敏感,有人会选 network_mode: host,容器直接使用宿主网络栈。此时容器里的 Nginx 若监听 80,就会和宿主 Nginx 抢同一个端口,必然冲突,而且 -p 参数会失效(Docker 会提示忽略)。用 host 模式前,务必确认宿主上没有其他进程占用该端口,并接受「容器端口即宿主端口」这个前提。

    更常见也更稳妥的架构,是让宿主 Nginx 做统一入口,容器 Nginx 只监听高位端口,由宿主反代:

    server {
        listen 80;
        server_name test.example.com;
    
        location / {
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

    对应容器启动命令写成 docker run -d --name my-nginx -p 127.0.0.1:8080:80 nginx。这样宿主 80 只被一个 Nginx 占用,证书、日志、限流都在宿主统一管理,容器只负责提供内容。是否加 X-Forwarded-Proto、如何取真实 IP,以你的 Nginx 版本和官方文档为准。

    如果确实要容器直接对外,也可以只让容器映射 80 并停掉宿主 Nginx,但要接受宿主上其他依赖 80 的服务(比如证书续期校验、监控探针)一起受影响。取舍原则很简单:同一台机器上,同一个端口同一时刻只能有一个主人,先决定谁做入口,再决定其他服务怎么挂上去。

    排查顺序建议固定下来:先用 ss -lntp 确认宿主端口占用,再用 docker port 和 docker inspect 确认映射与容器 IP,然后看 iptables 的 nat/filter 规则是否被覆盖,最后根据业务决定 bridge 加反代,还是 host 模式独占。把这几步跑一遍,绝大多数「容器里 Nginx 和宿主 Nginx 打架」的问题都能定位到具体一层,而不是靠反复重启容器碰运气。

    继续阅读

    📑 📅
    Nginx 与 CDN 回源 IP 不一致:XFF 取值顺序与日志字段 2026-09-24
    Linux负载高但CPU空闲:D状态进程与不可中断睡眠排查 2026-09-24
    Redis 主从切换后写入报 READONLY:三种拓扑的误配排查 2026-09-23
    PostgreSQL表膨胀与autovacuum不生效排查实战 2026-09-23
    rsyslog 与 journald 双写日志重复或丢失排查 2026-09-23
    Nginx 443 端口 SNI 分流:WebSocket 与普通请求共用配置 2026-09-24
    MySQL 8 大事务致 undo 膨胀与主从延迟:定位与拆分提交 2026-09-24
    Docker 容器内 systemctl 不可用:init 缺失与多进程管理的取舍 2026-09-24
    Nginx 反代后协议错乱:X-Forwarded-Proto 排查与修复 2026-09-24
    Linux 服务器 OOM Killer 日志怎么看:dmesg 与 journalctl 定位被杀进程 2026-09-25