ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Docker 部署 Nginx 反向代理实战:从安装到配置避坑全解析

2026/9/20 11:21:04 拓冰建站 浏览量
Docker 部署 Nginx 反向代理实战:从安装到配置避坑全解析 1. 为什么我推荐用 Docker 跑 nginx 反向代理先聊个现实问题。很多朋友在自己的服务器上装 nginx第一反应是apt install nginx或者直接下载编译包然后在系统目录里改配置改坏了还得小心翼翼备份恢复。这种传统方式不是不行只是当你需要在同一台机器上跑好几个 Web 服务、或者需要快速迁移环境的时候系统级安装的那套玩法就会开始拖后腿。这篇文章的主角是 Docker 加 nginx 反向代理。简单说就是通过 Docker 把 nginx 跑在一个独立容器里再用它把外部请求转发给后端不同的应用服务。它的组合价值在于nginx 负责流量入口和路由规则Docker 负责环境隔离和快速部署。你不需要在自己电脑或服务器上装一堆依赖也不需要担心把系统环境弄乱所有东西都以容器方式管理换机器、备份、回滚都变得非常直接。这篇文章适合谁看适合对 Docker 有一定了解、但还没真正把 nginx 反代跑起来的人也适合那些用传统方式装过 nginx、但想换成容器化方案的人。我会从安装 Docker 开始一路讲到 nginx 反向代理的完整配置中间会把为什么这样做、有哪些容易踩的坑一起讲清楚。2. 整体方案的设计思路2.1 用容器跑 nginx 解决了什么问题在动手之前我想先花一点时间把设计思路理清楚因为很多人装完了 Docker、拉完了 nginx 镜像却不知道这套组合到底解决了自己什么问题。首先环境隔离是容器方案最大的价值。以前我在服务器上直接装 nginx用的是系统自带的 OpenSSL、PCRE 等库一旦系统升级或者别的软件改了依赖nginx 可能就跟着出问题。用 Docker 跑 nginx镜像里自带一整套运行时环境和宿主机完全隔离。这意味着你在 A 机器上跑得好好的配置迁到 B 机器上只要同样装好 Docker、同样拉取镜像行为就是一致的不会因为系统版本差异导致各种奇怪故障。其次版本切换成本极低。传统方式下想从 nginx 1.18 升到 1.24得折腾编译参数、配置文件兼容性用 Docker 只需要改一下镜像 tag启动一个新容器替换旧容器就行。我实际体验下来整个升级过程不超过两分钟而且可以随时回滚到旧版本。还有一个容易被忽略的点——配置管理更清晰。传统安装方式下nginx 配置分散在/etc/nginx/nginx.conf、/etc/nginx/conf.d/以及各种软链接之间。用容器方案我把配置文件统一放到宿主机的一个目录里再用挂载方式把它映射进容器。这样配置文件归配置文件、日志归日志结构非常直观。2.2 为什么选择挂载目录而不是改容器内部文件这里有一个新手最容易纠结的地方nginx 容器启动后默认配置文件在容器内部的/etc/nginx/目录下。那我是直接docker exec进容器里改文件还是把文件挂载进去我的建议是永远用挂载不要直接改容器内部文件。原因有三点第一容器是一个临时性资源删除重建是常态。你直接进容器改文件容器一旦被删所有改动全部丢失。而挂载到宿主机的目录即使容器没了配置还在重启一个新容器就能恢复。第二直接进容器改文件不方便排查问题。容器内默认没有 vi/vim甚至没有 bash 也不是不可能你连个编辑器都找不到。与其在容器里折腾不如在宿主机上用自己的编辑器改完配置然后重启容器加载。第三挂载目录天然就是一个备份。我把所有服务的配置都放在/opt/docker/nginx/conf.d/这种结构下每次改动前复制一份出问题马上能回滚。打个比方容器就像一辆临时租赁的车你可以在里面开但别把个人物品长期留在车里。真正的行李应该放在自己家里宿主机目录要用的时候挂载进去。2.3 反向代理的核心逻辑先理解再配置很多人一上来就抄配置proxy_pass http://localhost:8080;这种代码背得滚瓜烂熟但遇到实际场景还是会懵为什么有时候加斜杠、有时候不加为什么proxy_set_header要写那一堆东西先理清一个基础概念反向代理就是替客户端去访问后端服务再把结果返回给客户端。nginx 在这里扮演的是一个“中介”角色。客户端只认识 nginx 的地址和端口完全不知道背后其实有一堆服务在分别处理请求。所以反向代理配置的核心就两件事监听谁nginx 监听哪个端口匹配哪个域名或 URL 前缀。转发给谁命中规则后请求被转发到哪个后端地址。这两件事搞清楚之后剩下的诸如proxy_set_header传递真实 IP、proxy_pass结尾斜杠对路由的影响都是在围绕这两件事做细节打磨。这篇文章后面的配置演示也会从这两个角度去拆解。3. Docker 环境准备3.1 Windows 场景下的 Docker Desktop 安装如果你手头只有 Windows 电脑想先本地体验一把那 Docker Desktop 是最省事的选择。下载地址在 Docker 官网装的时候保持默认选项就行。需要注意的一点是Docker Desktop 在 Windows 上依赖 Hyper-V 或者 WSL 2 后端安装过程中如果提示你启用相关功能照着操作然后重启即可。装完以后打开命令行敲一下docker --version看到类似Docker version 24.0.7的输出就说明安装成功了。这里还有一个很容易被忽略的步骤——在 Docker Desktop 的设置里把镜像源换成国内源。不换的话后面拉取 nginx 镜像时你可能要等很久。Docker Desktop 本身自带一个图形化界面可以看到容器列表、日志、资源占用本地开发用起来非常舒服。不过我得提醒一句Windows 版 Docker 跑 Linux 容器本质上是靠一个轻量级的 Linux 虚拟机在幕后撑着所以在 Windows 上做实验没问题真要部署到服务器上还是建议直接用 Linux 服务器。3.2 Linux 服务器安装 DockerUbuntu 为例服务器部署我推荐使用 Ubuntu 系统安装过程也最省心。我踩过 CentOS 上各种源、依赖的坑后来统一换到 Ubuntu 之后Docker 安装基本就是复制粘贴几条命令的事。先更新包索引sudo apt update然后安装依赖包sudo apt install -y apt-transport-https ca-certificates curl software-properties-common添加 Docker 官方 GPG 密钥和仓库curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null更新索引并安装 Dockersudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io安装完成后设置 Docker 服务开机自启并验证sudo systemctl enable docker sudo systemctl start docker sudo docker run hello-world如果最后一条命令能输出一段欢迎信息说明 Docker 已经跑起来了。为了让当前用户不用每次都加sudo你可以把用户加入 docker 组sudo usermod -aG docker $USER newgrp docker3.3 镜像源配置解决下载慢的问题这个坑几乎人人都会遇到。默认情况下Docker 从 Docker Hub 拉取镜像但在国内网络环境下速度非常慢动不动就超时。我的做法是给 Docker 配置国内镜像加速器。在 Linux 上编辑/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com, https://docker.nju.edu.cn ] }保存后重启 Docker 服务sudo systemctl daemon-reload sudo systemctl restart docker之后拉取镜像的速度会有明显改善。镜像源的选择其实不用太纠结找两三个主流加速地址轮询就行。多配置几个的好处是某个源临时挂了Docker 会自动尝试下一个。另外提醒一句如果你在公司的内网环境可能还需要配置代理或者使用公司内部镜像仓库这个要看具体环境。Windows 上操作更简单Docker Desktop 的设置界面里有 Registry mirrors 选项直接填入上面的地址保存重启即可。4. nginx 容器部署全流程4.1 拉取 nginx 镜像并理解 tag 含义镜像源配置好了之后拉取 nginx 镜像就非常快了。先搜索一下有哪些版本可选docker search nginx不过docker search只能看到镜像的基本信息要看具体有哪些 tag我一般是直接去 Docker Hub 网页上查看或者用docker pull拉取时经常用到的几个稳定版本nginx:latest最新稳定版开发学习用可以生产环境建议指定具体版本号。nginx:1.24某个具体大版本的最新补丁版。nginx:1.24.0完全固定的版本号。nginx:alpine基于 Alpine Linux 的精简版镜像体积小很多但用的库可能不同。我实际项目中的选择标准其实很简单生产环境必须指定具体版本号。比如nginx:1.24.0因为这样可以保证你回滚的时候拉到的镜像和之前完全一致不会出现“我明明没改配置升级后行为却变了”的问题。拉取镜像docker pull nginx:1.24.0查看已拉取的镜像docker images输出列表里能看到镜像名、tag、镜像 ID、创建时间和大小。4.2 创建配置目录结构启动容器之前先把宿主机的目录结构准备好。我一直强调容器里不存放任何配置和日志全部通过挂载映射出来。我习惯的目录结构是这样的/opt/docker/nginx/ ├── conf.d/ # 各站点反向代理配置 ├── nginx.conf # nginx 主配置 ├── certs/ # SSL 证书 ├── logs/ # 访问日志和错误日志 └── html/ # 静态页面创建目录mkdir -p /opt/docker/nginx/{conf.d,certs,logs,html}这里有一个小技巧如果你还不确定 nginx 默认配置长什么样可以先用下面这条命令启动一个临时容器把默认配置拷贝出来作为基础docker run --rm --name nginx-temp -d nginx:1.24.0 docker cp nginx-temp:/etc/nginx/nginx.conf /opt/docker/nginx/nginx.conf docker cp nginx-temp:/etc/nginx/conf.d /opt/docker/nginx/conf.d docker stop nginx-temp这样做的意义在于你能拿到一份和镜像版本完全匹配的默认配置很多人在网上抄了一个新版的配置用在旧版镜像上就会报各种指令不存在的错。用镜像自带的配置作为起点很大程度上可以避免这类问题。4.3 启动第一个 nginx 容器目录准备好之后执行启动命令。我先把最简单的启动流程演示一遍先不挂载配置文件确保容器能跑起来docker run -d --name nginx -p 80:80 nginx:1.24.0这条命令的意思是用nginx:1.24.0镜像启动一个后台运行的容器容器名是nginx把宿主机的 80 端口映射到容器内的 80 端口。启动成功后访问http://你的服务器IP如果看到 nginx 的欢迎页面说明容器已经正常运行了。然后我们停止并删除这个临时容器用挂载方式重新启动docker stop nginx docker rm nginx重新创建一个带目录挂载的容器docker run -d \ --name nginx \ -p 80:80 \ -p 443:443 \ -v /opt/docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /opt/docker/nginx/conf.d:/etc/nginx/conf.d:ro \ -v /opt/docker/nginx/certs:/etc/nginx/certs:ro \ -v /opt/docker/nginx/logs:/var/log/nginx \ -v /opt/docker/nginx/html:/usr/share/nginx/html \ --restartalways \ nginx:1.24.0这里每个参数都有自己的目的-p 80:80和-p 443:443同时映射 HTTP 和 HTTPS 的默认端口。如果你服务器上 80 端口已被占用比如还在跑系统自带的 nginx要么先停掉那个服务要么把宿主机的端口换成 8080。-v参数把宿主机目录挂载到容器内目录。注意配置文件的挂载我加了:ro后缀意思是容器内部只能读不能写防止在容器里不小心改到配置。--restartalways当 Docker 服务重启或者容器意外退出时自动拉起容器。服务器配置这个参数非常有用否则机器重启后你得手动启动容器。挂载配置启动后如果没报错依然能正常访问 nginx 欢迎页那就说明整个目录结构已经生效了。4.4 容器管理常用命令补充在继续配置反向代理之前先把容器日常管理用到的命令过一遍。这些是我平时使用频率最高的一组操作# 查看运行中的容器 docker ps # 查看所有容器包括已停止的 docker ps -a # 查看容器日志 docker logs nginx # 进入容器内部的 bash docker exec -it nginx bash # 重启容器 docker restart nginx # 停止、启动、删除容器 docker stop nginx docker start nginx docker rm nginx有人会问配置改了之后到底是docker exec去改容器里的文件还是修改宿主机挂载目录的文件答案很明确——修改宿主机挂载目录里的文件然后用docker restart nginx或者docker exec nginx nginx -s reload重新加载配置。用nginx -s reload的好处是不中断服务只是重新加载配置。对于配置调整比较频繁的场景用 reload 更稳妥。不过要注意如果配置语法有错误reload 会失败并保留旧配置继续运行相当于有了一层安全保护。5. 反向代理配置实战5.1 理解 nginx 配置文件的层级结构到了这一步容器已经正常启动接下来进入正题——反向代理配置。我直接以一个实际的场景为例服务器上跑了一个前端页面端口 3000和一个后端 API 服务端口 8080希望通过 nginx 统一对外提供访问。先把 nginx 主配置nginx.conf的关键结构理解清楚。nginx 配置是分块的events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # 引入 conf.d 下所有 .conf 文件 include /etc/nginx/conf.d/*.conf; }这段配置里events块处理的是连接相关的通用设置http块包含所有 HTTP 服务的配置。include /etc/nginx/conf.d/*.conf是重点——它会把/etc/nginx/conf.d/目录下所有.conf文件加载进来。在我们的挂载结构里这个目录就是宿主机上的/opt/docker/nginx/conf.d/。所以实际配置站点规则时不要去改nginx.conf的主结构而是在conf.d目录新建文件就行。5.2 第一个反向代理配置转发给单台后端服务现在我在conf.d目录下新建一个配置文件就叫default.confvim /opt/docker/nginx/conf.d/default.conf内容如下server { listen 80; server_name example.com; # 前端页面 location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 后端 API location /api/ { 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; } }这段配置做了两件事。第一件事所有访问example.com根路径的请求会转发到宿主机的 3000 端口也就是前端页面服务。第二件事所有以/api/开头的请求转发到 8080 端口也就是后端 API 服务。保存文件后先测试一下配置语法是否正确docker exec nginx nginx -t如果输出syntax is ok和test is successful说明配置没有问题。然后重新加载配置docker exec nginx nginx -s reload先解释一下几个关键配置项的作用因为它们直接影响转发链路是否正常工作proxy_pass http://127.0.0.1:3000;这是转发的目标地址。注意在容器内部127.0.0.1指向的是容器自身而不是宿主机。那为什么这里能访问到宿主机的服务因为 nginx 容器默认的网络模式是bridge在这种模式下容器内可以通过宿主机在 Docker 网桥上的 IP通常是172.17.0.1访问宿主机也可以通过host.docker.internal访问宿主机。实际上nginx 官方镜像在编译时加入了host.docker.internal的支持所以这里的127.0.0.1需要特别注意——如果你在宿主机上直接运行 nginx这个写法没问题但在容器里它会去找容器自身找不到就会 502。这是我踩过的一个大坑后面在常见问题部分会详细展开。proxy_set_header Host $host这个非常关键。如果不设置这个 Header后端服务收到请求时拿到的 Host 是127.0.0.1:3000它就无法判断客户端到底是用什么域名访问的服务。对于需要判断域名做路由的 Spring Cloud Gateway 这类服务来说这个 Header 丢失会直接导致路由失败。proxy_set_header X-Real-IP $remote_addr传递客户端的真实 IP。因为经过 nginx 转发后后端服务看到的连接来源是 nginx 的 IP。如果不显式传递这个 Header后端服务记日志、做访问控制时拿到的都是 nginx 的地址这对业务来说往往是不可接受的。X-Forwarded-For如果请求本身带了这个 Header比如经过了多层代理这里会在原有值后面追加当前客户端的 IP。X-Forwarded-Proto标识客户端原始请求的协议是 HTTP 还是 HTTPS。后端服务在生成重定向链接时经常需要用到这个信息否则明明客户端走的是 HTTPS后端却返回 HTTP 链接。5.3 配置多个站点用不同域名区分服务实际场景里一台服务器往往不只有一个服务。比如我自己的服务器上同时跑着博客、一个 API 服务、还有一个小型管理后台。如果都用同一个 IP 不同端口对外不仅记不住而且很多服务默认只监听 80/443 端口不太方便。最佳实践是一个server块对应一个域名让 nginx 根据请求的 Host 头来区分路由到哪个后端。还是以刚才的default.conf为例假设我现在要新增一个 admin 服务域名是admin.example.com后端跑在 8081 端口。我在conf.d目录下新建一个文件admin.confserver { listen 80; server_name admin.example.com; location / { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }保存后测试并 reloaddocker exec nginx nginx -t docker exec nginx nginx -s reload这样访问example.com时进入 3000 端口的前端服务访问admin.example.com时进入 8081 端口的管理后台服务。尼ginx 在收到请求后会根据请求头中的 Host 字段自动匹配对应的server块。这里有一个特别容易出问题的细节如果server_name匹配不上任何一个 server 块nginx 会使用默认 server第一个 server 块或者显式标记default_server的那个来处理请求。我遇到过这样的情况配置了两个 server 块结果域名 A 和域名 B 访问到的都是同一个服务的页面。排查了半天发现是两个 server 块的server_name写错了其中一个拼写错误导致所有请求都落到了默认 server 上。建议每次配置完用curl -H Host: admin.example.com http://你的IP测试一下是否命中了正确的 server 块。5.4 带路径前缀的反向代理注意 proxy_pass 的坑再讲一个非常容易掉进去的坑——location后面带路径前缀时proxy_pass的写法对最后转发出去的 URL 影响很大。还是以/api/为例假设后端的真实接口地址是http://127.0.0.1:8080/users而前端请求的是http://example.com/api/users。我们期望 nginx 能把/api前缀去掉把请求转发到/users。有两种写法结果完全不同写法一不带路径:location /api/ { proxy_pass http://127.0.0.1:8080; }这种写法会保留完整的 URI也就是说请求/api/users会原样转发到http://127.0.0.1:8080/api/users。写法二带路径:location /api/ { proxy_pass http://127.0.0.1:8080/; }注意proxy_pass结尾多了一个/。这种情况下nginx 会用location匹配到的前缀之外的剩余部分去拼接目标地址。也就是说请求/api/users会被转发到http://127.0.0.1:8080/users前缀/api被吃掉了。这两种写法没有绝对的对错取决于你的后端服务是否自带/api前缀。如果前端通过 nginx 访问后端 API后端路由本身已经带/api那用写法一如果后端暴露的接口不带前缀只有前端通过 nginx 路径来区分那就用写法二。我刚才在很多项目里看到团队因为这个问题来回折腾要么后端路由加前缀要么 nginx 去掉前缀最后前端又改接口路径整个链路改得一团糟。我的建议是选一种方案定下来然后所有服务都遵循同样的规则。比如统一约定“nginx 层负责路由前缀后端不感知前缀”那么所有反向代理都用proxy_pass http://127.0.0.1:8080/;加斜杠这种写法。5.5 再加一层WebSocket 的代理配置现在不少运维场景会用到 WebSocket比如实时通知、在线聊天。nginx 代理 WebSocket 的配置和普通 HTTP 代理略有不同因为 WebSocket 协议在握手阶段需要发送Upgrade和Connection两个 Header。配置方法是在proxy_set_header中额外加入两行location /ws/ { proxy_pass http://127.0.0.1:8080/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; }proxy_http_version 1.1是必须的因为 HTTP/1.0 不支持 WebSocket 升级。如果你只是搭了个普通 HTTP 反向代理不用纠结这个问题但只要后端涉及 WebSocket记得把这三行加上。5.6 HTTPS 反向代理配置要点现在是 2024 年HTTPS 已经是基本要求。配置 HTTPS 反向代理说难不难说简单也容易漏东西。首先准备证书文件。我用的是 Lets Encrypt 的免费证书证书文件一般分为三个fullchain.pem证书链和privkey.pem私钥。把这两个文件放到之前挂载的/opt/docker/nginx/certs/目录下。然后在 server 块中增加 HTTPS 监听server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }同时把原来的 HTTP 80 端口配置改为跳转到 HTTPSserver { listen 80; server_name example.com; return 301 https://$host$request_uri; }这里注意一个容易犯错的地方ssl_certificate的路径是容器内部的路径不是宿主机的路径。因为我们挂载的是/opt/docker/nginx/certs:/etc/nginx/certs所以配置里写的是/etc/nginx/certs/fullchain.pem。还有一个与容器相关的问题证书更新。Lets Encrypt 证书有效期是 90 天如果你用 certbot 在宿主机上自动更新证书容器内挂载的文件会自动同步更新因为是同一个目录但是 nginx 不会自动重新加载证书。我一般会写一个简单的定时任务每周检查一次证书如果证书有更新就执行docker exec nginx nginx -s reload。这一步不做的话证书过期前最后一周你会收到很多告警邮件。6. 常见问题与排查技巧6.1 容器内无法访问宿主机服务502 Bad Gateway)这是我在 Docker 加 nginx 反向代理里遇到的最常见问题。配置没问题、容器也正常启动访问时却返回 502。502 的含义是 nginx 无法连接到上游服务。原因很可能是proxy_pass里写的地址在容器内不可达。比如你在宿主机上跑了一个服务监听 8080 端口在proxy_pass里写http://127.0.0.1:8080容器启动后访问会 502。因为容器内的127.0.0.1指向容器自己宿主机上的服务它自然访问不到。解决方法有三种如果用--network host模式启动容器容器共享宿主机的网络栈127.0.0.1就能正常访问宿主机服务。但代价是容器没有独立网络端口映射相关参数就失效了。在proxy_pass中使用 Docker 网桥网关 IP一般是172.17.0.1。这个方法有效但 IP 可能会变不够稳定。最简单可靠的方案把后端服务也用 Docker 跑起来加入同一个自定义网桥网络用服务名互相访问。第三种方案是我目前最推荐的。创建一个自定义网络docker network create mynet启动后端服务时指定网络docker run -d --name backend --network mynet your-backend-image启动 nginx 时也加入同一网络docker run -d --name nginx --network mynet -p 80:80 -v ... nginx:1.24.0然后在 nginx 配置里直接写服务名location / { proxy_pass http://backend:8080; }nginx 会根据容器名自动解析到对应的容器 IP。这样做的优势是容器重新创建后 IP 变化了配置完全不用改。6.2 端口被占用很多朋友在自己的电脑上装了系统级 nginx再用 Docker 起一个 nginx 容器然后发现端口冲突。docker run -d --name nginx -p 80:80 nginx:1.24.0报错信息类似bind: address already in use。这是因为宿主机的 80 端口已经被系统 nginx 占用了。解决办法有两个。要么停掉系统 nginxsudo systemctl stop nginx要么换个宿主机端口映射docker run -d --name nginx -p 8080:80 nginx:1.24.0用第二种方式访问地址就变成了http://IP:8080。我建议系统里装过 nginx 的话直接把系统级 nginx 停用并设置开机不自启然后所有 Web 服务统一走 Docker 方案。否则每次开机系统 nginx 先占用 80 端口Docker nginx 再启动就会失败非常烦人。6.3 配置改了但没生效这种问题往往是最磨人的。修改了/opt/docker/nginx/conf.d/default.confreload 了配置访问还是老页面。先别怀疑 nginx 是不是坏了按以下顺序排查第一确认改的文件是不是真的被挂载进容器了。运行docker exec nginx cat /etc/nginx/conf.d/default.conf看看容器里的内容和宿主机上是否一致。不一致说明挂载路径有问题。第二确认配置语法正确docker exec nginx nginx -t语法不对reload 会失败但 nginx 还会用旧的配置继续跑所以页面看起来没变。第三确认浏览器缓存。有时候配置和容器里文件都改了但浏览器缓存的旧页面没刷新。用无痕窗口访问或者curl -I http://你的地址看一下响应头。还有一个细节include /etc/nginx/conf.d/*.conf只加载.conf结尾的文件。有些人新建了default文件没有加.conf后缀nginx 静默忽略没有任何报错。这种情况最容易让人懵。6.4 403 Forbidden 问题反向代理配置好了访问静态文件时返回 403。出现 403 通常是权限问题但 Docker 容器里的权限问题往往更隐蔽。一种典型原因是挂载的宿主机目录权限过高比如/opt/docker/nginx/html目录的所有者是 root权限是 755容器内 nginx 进程以 nginx 用户运行没有读取权限。排查时可以先看容器日志docker logs nginx日志里如果有Permission denied说明就是权限问题。解决方法是把宿主机目录权限调整一下sudo chmod -R 755 /opt/docker/nginx/html另一种情况是目录或文件名大小写不对。nginx 对location路径是严格区分大小写的如果页面文件是index.HTML而配置里写的是index.htmlnginx 就找不到对应文件。虽然 nginx 默认提供了try_files $uri $uri/ /index.html这类兜底策略但实际使用中还是尽量保持文件命名和配置一致。6.5 请求头里的 Host 异常配置了反向代理之后后端服务一直报错说拿到的 Host 不对。这种情况我遇到过很多次。比如前端应用调用后端 API 时后端服务做域名校验要求必须是api.example.com才能通过否则拒绝请求。如果 nginx 配置里没有设置proxy_set_header Host $host后端收到请求时 Host 可能就是127.0.0.1:8080自然校验不通过。解决办法很简单在location或server块加上proxy_set_header Host $host;如果后端有特殊需求也可以硬编码指定 Hostproxy_set_header Host api.example.com;6.6 反向代理性能调优配置跑通之后还有一个话题值得关注性能调优。nginx 默认配置其实非常适合做教学演示但生产环境压力上来之后最好做一些调整。在nginx.conf的events块中events { worker_connections 4096; }worker_connections表示每个 worker 进程可以同时维持的最大连接数。默认 1024 对于一般场景够用但如果并发量上来了可以适当调大。注意这个值受到系统文件描述符限制的影响所以也要检查一下宿主机上的ulimit。在http块中加入gzip on; gzip_types text/plain text/css application/json application/javascript; gzip_min_length 1024;启用 gzip 压缩可以显著降低传输体积尤其是 JSON 和 JavaScript 这种文本类资源。如果代理大文件下载或者音视频流媒体需要调大超时时间proxy_connect_timeout 60s; proxy_read_timeout 300s; proxy_send_timeout 300s;默认 60 秒的超时时间对于长请求可能不够。6.7 反向代理常见问题速查表为了方便之后快速排查我整理了一个表格这些是我在多个项目中实际遇到并解决的问题现象可能原因排查方法解决方法502 Bad Gateway代理地址无法访问docker logs nginx看日志确认后端服务启动、网络连通504 Gateway Timeout后端响应太慢查看后端日志调大 proxy_read_timeout403 Forbidden目录权限不足docker logs nginx看错误日志调整宿主机目录权限404 Not Found静态文件路径不对docker exec nginx ls查看容器内文件调整 root 路径配置改了没生效语法错误/未 reloaddocker exec nginx nginx -t修复语法后 reload域名访问到错误服务server_name 写错curl -H 测试修正 server_name反复跳转登录Cookie 域不对查看浏览器 Cookie设置 proxy_cookie_domain7. 写在最后的几个建议这篇文章从 Docker 安装、目录规划、nginx 容器部署讲到反向代理配置和常见问题排查基本上覆盖了一套可落地的完整流程。最后我想根据个人经验再分享几个建议。第一目录规划要趁早。如果你只是随便跑通一个 demo目录结构无所谓但如果你预计这台服务器上会跑多个服务、多个站点强烈建议一开始就把目录结构理清楚。我见过很多服务器conf.d 目录被各种命名混乱的文件塞满了.conf、.bak、.conf.old挤在一起维护成本极高。我的习惯是每个站点一个文件文件名与域名相关比如example.com.conf、admin.example.com.conf一目了然。第二日志一定要挂载出来。nginx 容器如果不挂载日志目录容器删除后日志就没了。排查线上问题的时候日志是最重要的线索。我见过有人排障排了半天最后发现容器的日志早就被轮转掉了。把/var/log/nginx挂载到宿主机目录配合 logrotate 做日志清理省心很多。第三始终保留一份旁路配置。每次大改之前把当前的conf.d目录备份一份命令就是cp -r一下的事。这个习惯救了我很多次有时候改着改着发现自己把规则逻辑搞反了直接回滚到之前一份备份就行不用靠记忆恢复。第四如果可能尽量把后端服务也容器化放到同一个 Docker 网络里。这样反向代理配置里直接用服务名访问网络层面的问题会少很多。当你有多台服务器、需要上 Docker Compose 或 Kubernetes 的时候这套以容器为单位的部署和管理方式也能平滑迁移。这几年用 Docker 跑 nginx 反向代理我最大的体会是Docker 本身并不难难的是把它和服务编排的思维真正融入到工作流里。希望这篇文章能帮你在起步阶段少走一些弯路。