ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Nginx核心实战:反向代理、负载均衡与高并发架构解析

Nginx核心实战:反向代理、负载均衡与高并发架构解析 在网站架构里摸爬滚打这些年Nginx 几乎是绕不开的一个名字。不管你是在做个人博客、企业官网还是维护一套日活百万的分布式系统大概率都会跟它打交道。不少刚接触服务端的朋友问我Nginx 到底有什么用为什么大家都在用它配置文件里那些指令又是什么意思。这篇文章就从我的实操经验出发把 Nginx 的作用、应用场景、配置思路和踩坑记录一次讲清楚。我把这些年用 Nginx 做反向代理、负载均衡、静态资源服务、HTTPS 终结以及在 Docker 和 Kubernetes 环境里部署它的经验都整理了一遍。无论你是刚入门的新手还是已经写了好几年配置的老手这篇文章里应该都能找到一些能直接拿去用的东西。1. Nginx为什么能成为服务端“标配”1.1 先搞清楚Nginx到底是个什么东西Nginx 是一个高性能的 HTTP 和反向代理服务器同时也是一个通用的 TCP/UDP 代理服务器。它最初由 Igor Sysoev 在 2004 年发布目的是解决 C10K 问题也就是单台服务器同时维持一万个连接的时代难题。那时候 Apache 还是主流但 Apache 的进程/线程模型在高并发场景下吃内存吃得很厉害一个连接一个进程的做法很快就把服务器资源耗尽了。Nginx 的架构核心是事件驱动和异步非阻塞 I/O。什么意思呢用一个生活化的类比来说传统的 Apache 就像一个饭店里一个服务员只管一桌客人客人不点菜他就一直在旁边站着等客人多了就得雇大量服务员工资开销巨大。而 Nginx 像一个眼观六路的超级服务员同时接待几十桌客人谁要点菜他立刻过去记下来然后继续服务下一桌等菜做好了再端过去。这样一来哪怕客人再多他一个人也撑得住。正因为这种设计Nginx 可以在非常低的资源占用下支撑极高的并发连接。我自己在一台 2 核 4G 的云服务器上用 Nginx 直接托管静态文件压测跑到四五万并发连接时CPU 和内存依然有大量余量。这个表现是同步阻塞模型的服务端很难做到的。除了性能Nginx 的另一大优势是模块化设计。它的核心很小大量功能通过模块扩展比如 Gzip 压缩、SSL 终结、rewrite 重写、限流限速、日志切割等。你可以按需编译模块也可以用发行版自带的动态模块机制随时加载。这种“小核心、多模块”的思路让 Nginx 既能保持轻量又能覆盖非常广泛的业务场景。1.2 Nginx解决了什么核心问题以我个人的理解Nginx 解决的第一个核心问题是“高并发下连接数扛不住”。一个站点流量上来之后最先崩溃的往往是 Web 服务本身而不是数据库或业务逻辑。Nginx 作为最前面的一层入口用极低的内存开销把大量连接先接住再按需转给后端应用这就给了后端极大的缓冲空间。第二个问题是“业务服务器的安全暴露面过大”。如果没有反向代理应用服务只能直接把端口暴露给公网比如 Java 的 Tomcat 8080、Python 的 Gunicorn 8000、Node.js 的 3000。这些端口一旦暴露不仅容易被扫描攻击而且应用本身的弱点会直接面对外部流量。Nginx 挡在前面之后外部流量只能触达 80/443 端口真正的业务端口可以只监听内网或者在防火墙层面直接封掉安全等级完全不一样。第三个问题是“静态资源和动态请求的混杂处理”。如果把图片、CSS、JavaScript 这些静态文件都交给后端框架处理性能会非常差。因为每来一个静态文件请求都要走一遍框架的中间件、路由、权限校验纯属浪费 CPU。Nginx 处理静态文件走的是高效的内核级 sendfile 路径几行配置就能扛住极高的 QPS还能结合浏览器缓存减少重复传输。第四个问题也是近些年特别重要的就是“多应用、多域名的统一入口”。公司内部可能有几十个应用每个应用端口不同外部用户不可能去记端口号也不应该直接访问到内部端口。用 Nginx 做一层虚拟主机或反向代理按域名、按路径把请求分发到不同应用是运维里最基础也最重要的能力。2. Nginx最常被用到的五大核心作用2.1 反向代理Nginx最经典的身份反向代理是 Nginx 使用率最高的场景之一。所谓反向代理是相对于正向代理而言的。正向代理是客户端主动配置代理地址通过代理去访问目标服务器比如公司内网上外网你需要在浏览器里设置一个代理 IP这就是正向代理。反向代理则相反客户端不需要任何配置它只知道访问某个域名但实际上请求被 Nginx 转发到了后端的某台应用服务器。我举一个最常见的例子。你的后端服务运行在本机 127.0.0.1:8000用户直接访问这个地址显然是行不通的。于是你用 Nginx 监听 80 端口把 /api/ 路径的请求全部 proxy_pass 给 8000 端口server { listen 80; server_name api.example.com; location /api/ { proxy_pass http://127.0.0.1:8000; 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; } }这里有几个关键点要特别说一下。proxy_set_header Host $host是为了让后端应用拿到的 Host 头是浏览器里输入的域名而不是后端地址很多框架的 URL 生成和 Cookie 处理依赖这个头。X-Real-IP和X-Forwarded-For是为了让后端能记录到客户端真实 IP否则所有请求的来源都会变成 Nginx 服务器的内网 IP日志和风控都会失真的。我见过不少新手漏掉这几个 header排查了半天问题才意识到是转发头信息丢了。另一个值得注意的地方是proxy_pass结尾的斜杠。proxy_pass http://127.0.0.1:8000;不带路径时Nginx 会把原始的 URI 原样转发如果写成proxy_pass http://127.0.0.1:8000/;则会把location匹配的前缀去掉再拼接。这个差异非常容易踩坑具体行为建议在实际环境里多测试几种情况理解透它的拼接规则。2.2 负载均衡把流量均匀分给后端单台应用服务器总有容量上限瓶颈一出现最直接的办法就是加机器。但加完机器之后流量怎么分这里就要用到 Nginx 的 upstream 负载均衡模块。upstream定义了一个后端服务器组Nginx 会在这些服务器之间按策略分发请求。常用的策略有四种轮询默认策略每个请求按时间顺序逐一分配到不同后端服务器。如果某个后端挂了会自动将其剔除。权重通过weight参数指定每个后端被访问的概率权重越大被分配的请求越多。适合后端机器配置不均衡的集群。ip_hash按客户端 IP 的哈希结果分配同一个 IP 的请求固定打到同一台后端。适合需要会话保持Session 粘滞的场景。fair按后端响应时间来分配响应时间短的优先分配。这是一个第三方模块需要额外编译安装。下面是一个带权重的配置示例upstream backend_servers { server 192.168.1.10:8080 weight3; server 192.168.1.11:8080 weight1; server 192.168.1.12:8080 backup; } server { listen 80; server_name example.com; location / { proxy_pass http://backend_servers; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }backup参数很实用它表示这台服务器是备用节点平时不参与流量分配只有其他节点全部不可用时才启用。我用这个方式做过不少高可用方案效果不错比单纯把所有机器都塞进 upstream 要更可控。这里要提醒一句如果后端是无状态服务轮询就够了但如果是传统的有状态应用比如带本地 Session 的 PHP 或 Java 应用就最好用 ip_hash 或引入 Redis 共享 Session否则用户第一次请求落在 A 机器第二次落在 B 机器登录态就丢了。2.3 静态资源服务与动静分离Nginx 处理静态资源的性能极佳几乎是我首选的静态文件服务器。配置也非常简单server { listen 80; server_name static.example.com; root /data/www; index index.html; location /assets/ { expires 30d; add_header Cache-Control public, immutable; } location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|zip|tar)$ { expires 7d; add_header Cache-Control public; access_log off; } }这里expires 30d是给匹配到的路径附加Cache-Control和Expires响应头让浏览器主动缓存静态文件减少重复请求。access_log off可以关闭静态资源的访问日志因为这些资源请求量巨大而且多为随机参数记录日志的意义不大还会白白消耗磁盘 I/O。在 Web 项目中动静分离是一个非常重要的优化手段。动态请求交给后端框架处理静态请求直接由 Nginx 返回。实际效果非常明显我以前给一个内容站做优化静态资源的响应时间从平均 200ms 降到了 10ms 以内整个页面的加载速度提升了一个数量级。2.4 HTTPS终结、缓存与安全策略现在全网都在推 HTTPS证书的安装和续期是运维日常。Nginx 中配置 HTTPS 的标准做法是这样server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.pem; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; add_header Strict-Transport-Security max-age31536000; includeSubDomains always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; location / { proxy_pass http://backend_servers; } }ssl_certificate是证书文件ssl_certificate_key是私钥文件。这两个文件的路径要确保 Nginx 进程有读取权限私钥文件建议把权限设为 600只允许 root 读取。TLS 版本建议至少保留 TLSv1.2 和 TLSv1.3更老版本的 TLSv1.0/1.1 存在已知安全漏洞不建议在生产环境中使用。所谓 https 终结是说 Nginx 负责处理客户端与服务器之间的 TLS 加密解密而后端服务器之间走的是明文 HTTP。这样做的最大好处是证书只需要部署在一层入口上后端应用不用各自处理加密减轻了后端的计算压力。但要注意这也意味着内网链路是明文传输的如果对安全性要求极高后端之间也建议加 TLS。add_header那几行是安全响应头的标配。HSTS 告诉浏览器以后必须用 HTTPS 访问X-Content-Type-Options防止浏览器对资源进行 MIME 类型嗅探X-Frame-Options防止页面被 iframe 嵌入降低点击劫持的风险。这些安全头在不影响用户体验的前提下能给站点加上不少安全分。2.5 限流、压缩、重写等“隐藏技能”除了上面几个主要作用Nginx 还有一堆非常实用的小功能用好了能省不少事。限流是应对突发流量的重要手段。Nginx 的limit_req模块可以实现基于漏桶算法的请求速率限制limit_req_zone $binary_remote_addr zonemylimit:10m rate10r/s; server { location /api/ { limit_req zonemylimit burst20 nodelay; proxy_pass http://backend_servers; } }这里的rate10r/s表示平均每秒最多处理 10 个请求burst20允许瞬间最多有 20 个请求排队nodelay表示排队中的请求不延迟直接放行但超过 burst 的请求会直接返回 503。这个配置在做 API 网关、防爬虫、保护登录接口时非常有用。Gzip 压缩是前端性能优化的必选项。对文本类资源HTML、CSS、JS、JSON开启压缩后传输体积能减少 60% 到 80%gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svgxml; gzip_min_length 1024; gzip_comp_level 6;gzip_min_length 1024表示小于 1KB 的文件不压缩因为压缩小文件不仅省不了多少流量反而会消耗 CPU。gzip_comp_level是压缩级别1 到 9 之间我一般推荐 6也是性能和压缩率比较均衡的一个档位。rewrite 重写规则可以用来做 URL 规范化、强制跳转、临时维护页面等。最常见的用途就是 HTTP 跳转 HTTPSserver { listen 80; server_name example.com; return 301 https://$host$request_uri; }还有临时维护页面我常用的是把某个路径的所有请求重写到维护页location / { if (-f /data/www/maintenance) { return 503; } proxy_pass http://backend_servers; } error_page 503 maintenance; location maintenance { root /data/www; rewrite ^(.*)$ /maintenance.html break; }这个方案的好处是要进入维护模式时只需要在后端服务器上创建/data/www/maintenance文件不需要改配置重启 Nginx维护结束后删除该文件服务立即恢复。3. 典型应用场景与架构选型3.1 单机多站点虚拟主机即使只有一台服务器Nginx 的虚拟主机功能也可以让你托管多个域名、多个站点相互之间完全隔离。我以前在一台便宜的小服务器上同时跑了三个个人项目、一个博客和一个 API 服务每个项目一个独立的 server 块互不干扰。用 Nginx 实现多站点的方式是在/etc/nginx/conf.d/下创建多个配置文件或者创建一个sites-available目录并在sites-enabled里做符号链接。我个人更推荐后者因为可以方便地启用和禁用某个站点不用急着删配置文件。每个配置文件就是一个 server 块标题可以理解为“一个域名对应一份配置”。例如/etc/nginx/conf.d/blog.confserver { listen 80; server_name blog.example.com; root /var/www/blog; index index.html; access_log /var/log/nginx/blog.access.log; }server_name决定哪个请求会命中这个 server 块。如果请求的 Host 头是blog.example.comNginx 就会进入这个位置。访问日志独立存放排错时非常方便。3.2 集群入口网关与微服务网关当后端从单体拆成微服务之后Nginx 就扮演了南向流量的统一入口网关。每个前端应用只需要知道 Nginx 的地址由 Nginx 根据 URL 路径或 Host 头把请求转发到不同的微服务。一个典型的按路径分发的配置长这样server { listen 80; server_name api.example.com; location /user/ { proxy_pass http://user_service:8080; } location /order/ { proxy_pass http://order_service:8081; } location /product/ { proxy_pass http://product_service:8082; } }这种架构的好处是前端不需要关心后端服务的地址变化后端无论怎么拆分、扩容前端始终只需要请求 Nginx。同时 Nginx 可以统一处理跨域、请求日志、限流、鉴权等横切逻辑让微服务本身只专注于业务。不过要提醒一句如果微服务数量非常多、路由规则极其复杂Nginx 的静态配置管理起来会很吃力。这时可以考虑引入专业的 API 网关如 Kong、APISIX或 Kubernetes 的 Ingress Controller它们本质上是 Nginx 的“增强版”但提供了动态配置、服务发现等能力。3.3 Docker部署Nginx并挂载多个项目目录Docker 让 Nginx 的部署变得极其方便尤其是你需要在同一台机器上挂载多个前端项目时。我常用的方式是把 Nginx 配置和项目目录都通过 volume 挂载进容器这样改配置或者更新代码都不需要重新构建镜像。一个 docker-compose.yml 的示例version: 3.8 services: nginx: image: nginx:1.27-alpine container_name: web_nginx ports: - 80:80 - 443:443 volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./www/blog:/usr/share/nginx/html/blog:ro - ./www/admin:/usr/share/nginx/html/admin:ro - ./ssl:/etc/nginx/ssl:ro - ./logs:/var/log/nginx restart: unless-stopped这里说一下关键点配置文件和站点目录都是只读挂载:ro日志目录是读写挂载。这样既能保证容器内进程无法修改配置又能把日志持久化到宿主机上方便分析。这种方式的容器启动命令是docker-compose up -d如果改了配置文件需要重载不用重启整个容器只需执行docker exec web_nginx nginx -t docker exec web_nginx nginx -s reload这里nginx -t是先测试配置文件语法是否正确如果配置文件写错了nginx -t会直接报错避免把错误的配置加载进生产环境。测试通过后再「平滑重载」Nginx 会重新载入配置并重启 worker 进程期间已建立的连接不会断开这也是 Nginx 运维里最常用的操作。3.4 容器编排平台中的Ingress角色在 Kubernetes 集群里Nginx 同样扮演着重要角色最常见的形式是 Nginx Ingress Controller。它本质上是把 Nginx 做成了一个运行在集群内的控制器通过监听 Ingress 资源对象的变动自动生成并更新 Nginx 的配置。我最初从裸机 Nginx 迁移到 Kubernetes 时最大的感受是不再需要 SSH 进机器手动改配置文件了。你要开放一个域名路径只需要写一个 Ingress 资源apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress spec: rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: app-service port: number: 80Ingress Controller 会自动把这套规则翻译成对应的 Nginx 配置并加载到其内部的 Nginx 实例里。这样你既享受到了 Nginx 的高性能又获得了 Kubernetes 的声明式管理能力。如果集群里的服务量太大还可以启用 Ingress Controller 的自动扩缩容。对于 MySQL 这类有状态服务Nginx 的作用相对薄弱一些但依然可以作为数据库连接代理或管理后台的前置入口。比如把 MySQL 管理工具部署在 Pod 内通过 Nginx Ingress 暴露给内网用户同时做好 TLS 和鉴权。3.5 流媒体、WebSocket等特殊协议场景Nginx 不止支持 HTTP还支持丰富的协议扩展。在流媒体场景下nginx-rtmp-module是一个非常经典的模块可以接收 RTMP 推流同时以 HLS 或 DASH 的形式分发拉流。很多中小型直播平台或视频站早期就是靠它撑起来的。RTMP 推流配置示例rtmp { server { listen 1935; chunk_size 4096; application live { live on; record off; hls on; hls_path /data/hls; hls_fragment 5s; hls_playlist_length 30s; } } }配置好之后推流端往rtmp://your-server-ip/live/stream_name推流播放端可以通过 HLS 地址http://your-server-ip/hls/stream_name.m3u8观看直播。延迟取决于 HLS 的分片长度hls_fragment 5s差不多能把端到端延迟控制在 10 到 15 秒左右。WebSocket 是现代 Web 应用的好帮手聊天、在线协作、实时通知都离不开它。Nginx 对 WebSocket 的支持也是开箱即用的关键是配置好 Upgrade 相关的 headerlocation /ws/ { proxy_pass http://websocket_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; }proxy_read_timeout一定要设置得足够长WebSocket 连接是长连接如果超时时间太短Nginx 会主动断开连接。默认的 60s 在日常使用中绝对不够我一般设为 3600s 起步。4. 配置文件核心要点拆解4.1 nginx.conf整体结构这些配置项都是什么意思新手看到一份 nginx.conf 往往一头雾水但拆开来看其实非常清晰。Nginx 的配置是层次化的从外层到内层分别是main、events、http、server、location。main是全局配置影响 Nginx 主进程本身比如user、worker_processes、error_log、pid。worker_processes我通常设为 CPU 核心数可以通过auto让 Nginx 自动检测。一个常见的误区是以为 worker 开得越多越好实际上线程切换的开销反而会拖慢性能。events块配置事件驱动模块的工作模式核心参数是worker_connections表示每个 worker 进程能同时打开的最大连接数。这个参数和worker_processes的乘积就是整个 Nginx 可以维持的最大连接数上限。http块是核心配置区里面包含 MIME 类型映射、访问日志格式、Gzip 压缩、负载均衡 upstream、以及多个server块等。server块相当于一个虚拟主机对应一个域名或一组监听端口的集合。location则定义了特定路径的处理规则是配置中最灵活也最容易出错的地方。我用一个简单的层级图来理解http 块是城市server 块是街区location 块是具体的门店。请求进来后Nginx 先看 http 顶层配置再根据 Host 头匹配 server最后根据 URI 匹配 location。下面是每个指令的作用速查表方便大家日常查阅指令所在层级作用usermain指定 Nginx worker 进程的运行用户worker_processesmainworker 进程数量通常设为 CPU 核数eventsevents连接数、事件模型等配置worker_connectionsevents单个 worker 能同时处理的最大连接数httphttpHTTP 协议的全局配置区include任意引入其他配置文件拆分配置时非常好用upstreamhttp定义负载均衡的后端服务器组serverhttp定义一个虚拟主机域名/端口级别listenserver监听端口和地址server_nameserver匹配请求的域名rootserver/location设置资源文件的根目录locationserver定义 URI 路径的处理规则proxy_passlocation反向代理转发目标地址fastcgi_passlocation转发给 FastCGI 服务如 PHP-FPMexpireslocation设置响应头中的缓存过期时间rewritelocation/serverURL 重写规则returnlocation/server直接返回状态码或重定向gziphttp/server/location开启响应内容压缩add_headerserver/location添加自定义响应头4.2 一份可直接参考的server配置说再多不如直接给一份完整的配置模板。这是我个人比较常用的一套生产环境配置做了反向代理、负载均衡、HTTPS、静态资源缓存、跳转和优雅重载的多重覆盖# /etc/nginx/conf.d/example.conf # 负载均衡组 upstream app_cluster { least_conn; server 192.168.1.10:8080 max_fails3 fail_timeout30s; server 192.168.1.11:8080 max_fails3 fail_timeout30s; keepalive 32; } # HTTP 强制跳转 HTTPS server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri; } # HTTPS 主服务 server { listen 443 ssl http2; server_name example.com www.example.com; # 证书配置 ssl_certificate /etc/nginx/ssl/example.com.pem; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 日志 access_log /var/log/nginx/example.access.log; error_log /var/log/nginx/example.error.log; # 安全响应头 add_header Strict-Transport-Security max-age31536000; includeSubDomains always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; # 静态资源缓存 location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2?|svg)$ { expires 30d; add_header Cache-Control public, immutable; access_log off; } # 后端动态请求 location / { proxy_pass http://app_cluster; proxy_http_version 1.1; 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; proxy_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s; } }这个配置里我用了least_conn负载均衡算法它会优先把请求发给当前活跃连接数最少的后端比简单的轮询在长请求场景下表现更均衡。keepalive 32让 Nginx 与后端之间保持长连接避免每次请求都重新建连能显著降低后端的握手开销。max_fails3 fail_timeout30s是健康检查的简易方案。30 秒内连续失败 3 次就把该后端标记为不可用30 秒后再重新尝试。这个机制能有效把故障节点从集群中摘除不会把请求打到已经挂掉的服务上。4.3 运维常用命令与平滑切换Nginx 的运维命令不多但每一条都是高频使用的我整理在这里方便大家查阅。启动 Nginxsystemctl start nginx如果是使用 Linux 安装包直接部署的也可以用/usr/sbin/nginx停止 Nginxnginx -s stop # 快速停止立即退出 nginx -s quit # 优雅停止等待当前请求处理完再退出重新加载配置平滑重载nginx -s reload检查配置文件语法nginx -t如果显示syntax is ok并且test is successful就说明配置没有语法问题。我个人的习惯是改完任何配置先nginx -t再nginx -s reload两步缺一不可。就算配置文件写得再熟练也会有打错字母的时候nginx -t就是最后一层保险。对于系统服务方式还有一组方便的管理命令systemctl reload nginx systemctl restart nginx systemctl status nginx需要特别注意的是restart和reload完全是两回事。restart是重启进程所有现有的连接都会断开这在生产环境里会造成短暂的请求中断reload是平滑重载仅重新读取配置并循环重启 worker 进程已建立的连接不会中断。我遇到过不少新手在发版时误用 restart 导致线上短暂不可用建议养成只 reload 不 restart 的习惯除非是 Nginx 二进制升级或内核模块变更。4.4 在Windows上使用Nginx的特殊问题虽然 Nginx 主要部署在 Linux 服务器上但仍有一些开发者在 Windows 环境中需要用它做本地代理。Windows 版 Nginx 的使用方式基本相同但有几个特殊点值得注意。Windows 版 Nginx 不支持fork()系统调用所以worker_processes必须设置为1否则会有较严重的性能问题。另外Windows 版的信号机制和 Unix 不同无法通过nginx -s reload直接进行平滑重载你需要手动执行nginx -s stop再nginx启动或者使用 Windows 服务管理工具来实现更优雅的重启。最关键的一点是Windows 版 Nginx 对路径的处理方式和 Linux 有差异。配置文件里的ssl_certificate、root等路径需要使用绝对路径比如C:/nginx/ssl/cert.pem斜杠要用正斜杠而不是反斜杠否则解析会出错。如果只是本地开发联调建议直接用 WSL 安装 Linux 版 Nginx体验更接近生产环境。5. 常见问题与排查技巧实录5.1 经典错误码排查思路Nginx 返回的 HTTP 错误码是排查问题的第一手线索这里整理了一份我平时最常用的错误码排查对照表。404 Not Found。看到这个错误码优先检查 location 匹配是否命中、root 路径是否正确、静态文件是否真实存在。如果后端接口返回 404则需要检查 proxy_pass 的目标路径是否拼接正确特别是前面提到的 location 前缀和 proxy_pass 末尾斜杠的组合规则。502 Bad Gateway。这个错误码表明 Nginx 无法与后端服务器建立有效连接。常见原因有后端服务没有启动、端口写错、防火墙拦截、后端服务主动拒绝连接。排查顺序是先确认后端进程是否存活再确认端口监听是否正常最后看 Nginx 和后端之间的网络是否通。504 Gateway Timeout。Nginx 等后端响应超时了。先看proxy_read_timeout设置是否过短再看后端本身是不是真的处理缓慢。我一个老项目出现过类似问题当时后端有个导出功能耗时超过 3 分钟Nginx 默认的 60s 直接掐断最后调大超时时间才解决。403 Forbidden。这个错误码通常和权限有关。Nginx worker 进程对文件或目录没有读权限或 Nginx 的allow/deny规则拒绝了请求。检查文件权限时要注意不只是文件本身从根目录到文件的每一级目录都需要有执行权限。413 Request Entity Too Large。这个错误码出现在前端上传大文件时是client_max_body_size没有设置。默认值是 1m做文件上传服务时务必要调大我一般设为 20m。499 Client Closed Request。这个错误码是 Nginx 自己的记录码不是上游返回的。意思是客户端在 Nginx 还没拿到后端响应时就主动关闭了连接。常见于用户端网络差、请求超时、或者前端组件提前取消请求等场景判断时要结合业务特征具体分析。下面用表格汇总一下状态码含义主要排查方向404找不到资源location 规则、root 路径、proxy_pass 拼接502无法连到后端后端服务状态、监听端口、防火墙、网络504后端响应超时proxy_read_timeout、后端实际性能403禁止访问文件权限、allow/deny 规则、目录层级权限413请求体过大client_max_body_size 设置499客户端提前断开客户端网络、请求超时、前端行为5.2 证书与HTTPS相关坑证书相关的问题排查起来特别容易绕圈因为浏览器报错信息往往不够直接。我遇到过最典型的三个坑。第一个是证书格式不对。Nginx 需要的是 PEM 格式的证书也就是 Base64 编码、以-----BEGIN CERTIFICATE-----开头的文本文件。如果你拿到的是.pfx或.cer需要先用 OpenSSL 转成 PEM 格式openssl pkcs12 -in cert.pfx -clcerts -nokeys -out cert.pem openssl pkcs12 -in cert.pfx -nocerts -nodes -out key.pem第二个是证书链不完整。有些证书厂商会发给你三个文件服务器证书、中间证书、根证书。如果你只装了服务器证书浏览器可能报“证书链不完整”。解决方法是把服务器证书和中间证书拼接到同一个文件里cat server.pem intermediate.pem fullchain.pem第三个是私钥和证书不匹配。这种问题在浏览器里表现为 SSL 握手失败。排查方式是用 OpenSSL 对比两者的公钥指纹openssl x509 -noout -modulus -in cert.pem | openssl md5 openssl rsa -noout -modulus -in key.pem | openssl md5如果两个指纹一致说明证书和私钥是匹配的如果不一致说明其中某个文件选错了。5.3 性能和连接数问题Nginx 性能问题排查的核心是两个指标文件描述符file descriptor上限和连接数统计。Linux 系统对每个进程能打开的文件数有限制Nginx 的连接数本质上也是通过文件描述符实现的。如果 Nginx 报too many open files说明文件描述符不够用。需要在 nginx.conf 的 main 块里增加worker_rlimit_nofile 65535;同时把系统级的limits.conf里对应的用户 nofile 也调大。worker_connections和worker_rlimit_nofile之间的关系是worker_connections的设置上限不能超过worker_rlimit_nofile否则配置加载时会报错。统计连接数用这个命令ss -s查看 Nginx 进程实际打开的文件数ls /proc/$(pgrep -f nginx: worker)/fd | wc -l如果发现连接数已经逼近上限优先考虑调整worker_processes和worker_connections或者优化后端的响应时间。还有一个小但容易被忽略的性能点Nginx 的 access_log 如果没有开启缓冲每次请求都会立即写磁盘高并发下磁盘 I/O 会成为瓶颈。建议开启日志缓冲access_log /var/log/nginx/access.log main buffer32k flush5s;buffer32k表示攒到 32KB 再写入磁盘flush5s表示最多延迟 5 秒必须落盘。实测这个配置在高并发下能明显降低磁盘 I/O但不适合对日志实时性要求极高的审计场景。5.4 我的踩坑备忘清单最后分享一份我这些年踩过的坑的备忘录很多问题排查了大半天才发现是低级错误写出来希望能帮大家少走弯路。配置指令后漏写分号。Nginx 配置每条指令结束必须有分号漏了分号 Nginx 会把下一行当成参数解析轻则报错重则加载出完全不同的配置。我现在每次nginx -t之前都会目测一遍分号。root 与 alias 用混。root会把 location 的路径拼接到 root 后面而alias则是把 location 匹配到的前缀直接替换成 alias 指定的路径。比如location /img/ { root /var/www; # 实际文件路径 /var/www/img/ } location /img/ { alias /var/www/; # 实际文件路径 /var/www/ }两者行为完全不同用错会导致 404。这大概是 Nginx 配置里最容易踩的坑之一没有例外。proxy_pass 带 URI 时的拼接陷阱。前面提过proxy_pass http://backend;与proxy_pass http://backend/;的区别。简单说proxy_pass带上 URI哪怕只是/Nginx 在转发时就会替换掉 location 匹配到的部分这个行为在新手眼中非常难以预料。我的建议是除非你确定自己在做什么否则 proxy_pass 一律不写末尾的/。worker_processes 设得太大。我见过有同事把worker_processes设为 64结果内存消耗陡增性能反而下降。这个参数不是越多越好最佳值是 CPU 核心数或者干脆写auto。忘记为虚拟主机开独立日志。如果多个 server 共用默认的 access.log出问题时根本分不清流量来源。我习惯在每个 server 里都指定access_log和error_log路径哪怕是个小站点也要有独立日志文件。Docker 容器里的时区问题。Nginx 默认使用 UTC 时间日志里的时间戳比北京时间晚 8 小时。在 Docker Compose 里我通常是这样解决的environment: - TZAsia/Shanghai没加这个配置之前我盯着日志里的时间排查了半天还以为是服务时钟出了问题最后发现是容器时区。证书路径权限问题。Nginx 主进程通常以 root 启动但 worker 进程以普通用户运行比如nginx用户。如果证书或私钥文件放在 root 才可读的目录里reload 时经常出现SSL_CTX_use_PrivateKey_file failed之类的错误。解决办法是确保证书目录的权限对 nginx 用户可读比如chown -R root:nginx /etc/nginx/ssl chmod 750 /etc/nginx/ssl chmod 640 /etc/nginx/ssl/example.com.key提示以上配置和示例来自我在多个环境中的实践积累不同系统版本、不同业务场景下的最佳实践可能会略有差异。动手配置时建议先在测试环境验证确认无误后再应用到生产环境。结尾写了这么多最后分享一点个人的感受吧。Nginx 是我觉得学习性价比最高的服务端组件之一它本身不复杂配置文件虽然有几百个指令但日常真正高频使用的也就二三十个。只要理解了它“事件驱动、模块化、按请求匹配 server 和 location”的基本模型遇到问题时的排查效率会高出很多。Nginx 的生态也在不断进化从最基础的静态服务器到反代、负载均衡再到 Kubernetes Ingress Controller、OpenResty 这类扩展平台始终保持着极高的活跃度。但不管外围怎么变核心思想没变做最轻量、最快速、最可靠的一层流量入口。如果你正在学 Nginx我建议不要只看文档直接搭个环境把一个真实的小项目通过 Nginx 暴露出来然后尝试在后面加两台后端服务做负载均衡再配一份 HTTPS 证书。把这个流程完整走一遍比看十遍教程都管用。遇到问题也不要慌多查错误日志多验证配置语法经验都是一次次踩坑累积出来的。
返回列表