
上个月给一台 aarch64 架构的纯内网服务器部署 Nginx前前后后折腾了两天。中间踩了依赖包缺失、配置文件路径权限、SSL 证书握手失败这些坑回头一看每个问题的答案其实都写在 Nginx 的进程模型和配置语义里。于是有了这篇——从一个真实运维视角出发把 Nginx 从安装、配置、调优到排错完整过一遍。如果你是刚接触它的小白这篇可以当入门手册如果你已经用了很久但一直停留在“改改配置 reload 一下就走”的水平这篇能帮你把背后的原理补齐。我会尽量把每一步为什么要这么做讲清楚而不只是丢给你一堆命令。1. Nginx到底解决了什么问题从C10K时代说到今天的进程模型1.1 一个经常被低估的定位热搜词里出现“nginx详细讲解”“nginx面试题”说明大多数人都想系统搞懂这个东西但它在一线项目里的真实身份往往被低估了。Nginx 本质上是一个 Web 服务器但日常生产里它更多被当作反向代理、负载均衡器、静态资源服务器和 HTTPS 终结节点来用。用一句话概括我的理解它是一个用事件驱动模型把高并发扛下来的 HTTP 基础设施。它不是业务服务器而是流量入口。所有请求先到它这里再由它决定是直接返回静态文件还是转发给后面某个 Tomcat、Node.js、PHP-FPM 进程。很多人以为 Nginx 只是“一个能跑网站的软件”实际上它是整个后端架构里最靠近客户端的那一层也是你最应该花时间去理解的一个组件。它的进程模型、配置语法、排错方式几乎决定了你后面所有服务部署的体验。1.2 高并发的核心秘密事件循环与非阻塞IO传统 Web 服务器处理并发的方式很直接来一个连接就开一个进程或线程去伺候连接数一多内存和上下文切换开销直接爆炸。这也是十几年前困扰整个互联网的 C10K 问题——单机并发一万个连接就顶不住了。Nginx 的做法完全不同。它启动后只有一个 master 进程负责管理真正干活的是若干个 worker 进程。每个 worker 内部跑的是一个事件循环配合非阻塞 I/O一个 worker 可以同时盯着成千上万个连接。某个连接的请求还没读完worker 不会干等而是转头去处理其他已经就绪的事件等这个连接的 I/O 准备好了再回来接着处理。用一个生活化的类比传统模型就像餐厅里每来一桌客人就派一个专职服务员全程跟着客流量一大服务员数量就得跟着爆炸Nginx 像一个传菜流水线上的服务员一个人同时盯着几十桌哪桌菜好了就过去端没好的就先忙别的。这就是 Nginx 能在普通服务器上轻松扛住数万并发的原因。理解了这个模型后面所有 worker 数量、并发连接数的计算你才能看得懂。1.3 它日常到底在扛哪些活结合我自己的部署经验Nginx 在日常项目中承担这些角色静态资源服务图片、HTML、CSS、JS 直接由 Nginx 返回不经过后端。反向代理动态请求转发给 Tomcat、Spring Boot、Node.js 等后端进程。负载均衡通过 upstream 把流量分摊到多台后端服务器。HTTPS 终结统一处理 TLS 证书和加密握手后端只跑明文 HTTP。动静分离静态资源本地响应动态请求转后端充分压榨性能。限流与访问控制按 IP、按连接数限制请求频率保护后端。后面我会按这个脉络把每个角色的实际配置和坑位讲透。2. 安装没你想的那么简单Windows、Linux、纯内网离线三种实战2.1 Windows解压即用但坑在路径和启动方式Windows 下安装 Nginx 是最让人放松警惕的场景。从官网下载 Windows 版本得到一个 zip 压缩包解压完就能跑。但我第一次用 Windows 版时就被坑了一下解压后直接双击 nginx.exe窗口一闪而过我以为启动失败了实际上它已经在后台运行只是没有任何界面提示。正确的操作是打开命令行切到解压目录执行start nginx启动用nginx -s stop停止nginx -s reload重载配置。启动后浏览器访问 http://localhost看到“Welcome to nginx!”就说明成功了。这里有两个必须注意的点。第一解压路径不要放在带中文或空格的目录下Nginx 在 Windows 下对路径解析比较脆弱后面配日志、配 SSL 证书时容易出莫名其妙的问题。第二Windows 版通常只建议用于本地开发测试生产环境最好用 Linux。原因很简单Windows 文件系统、事件模型和 Linux 差异很大Nginx 的核心性能优势在 Windows 上发挥不出来。2.2 Linux包管理器是最快路径但版本要留个心眼Linux 下安装 Nginx 的主流方式是包管理器。Ubuntu/Debian 系执行sudo apt update sudo apt install nginxCentOS/RHEL 系执行sudo yum install nginx装完直接systemctl start nginx就能跑。包管理器的好处是依赖自动解决、开机自启、systemd 管理都帮你弄好了。但我建议你立刻执行nginx -V看一下编译参数和版本。很多发行版自带的 Nginx 版本偏旧官方文档里新出的指令在这个版本上可能根本不存在比如http2指令的写法在不同版本之间就不一样。如果公司有内网软件镜像站或者你习惯用国内软件镜像源那就在系统 apt/yum 源里把地址换成镜像站。这一步在纯内网环境尤其重要提前把源配好后面安装任何软件都省心。2.3 纯内网、离线、aarch64 架构真正的硬骨头如果你和我一样遇到的是 aarch64 架构加纯内网环境安装就完全不是一条命令能解决的事了。核心思路只有一条提前在有外网的机器上把所有需要的安装包和依赖都下载好然后想办法拷进内网。源码编译是这种环境下最稳妥的方案。一个典型的 Nginx 编译依赖通常包括 gcc、make、PCRE 库支持正则、zlib 库支持压缩、OpenSSL 库支持 HTTPS。在 aarch64 的麒麟系统上我建议先确认系统是 CentOS 系还是 Debian 系然后找对应架构的依赖包。麒麟系统不同版本差异很大有的基于 CentOS 生态有的基于 Ubuntu 生态认准了再动手。具体操作时我会先在有外网的机器上把依赖包装成 deb 或 rpm 文件拷入内网后用dpkg -i或rpm -ivh按顺序安装。依赖关系比较复杂的时候用yum localinstall或apt install ./xxx.deb能自动解析已下载的本地包比手动一个个装省事得多。离线编译 Nginx 时可以多用静态编译选项。把 PCRE、zlib、OpenSSL 以静态方式编进去运行时就不依赖系统的动态库版本减少了内网机器上“缺这个库缺那个库”的概率。缺点是二进制文件会大一些但对内网环境来说稳定压倒一切。2.4 安装完的第一件事nginx -t 和进程检查不管哪种方式装完我强烈建议你做三件事执行nginx -t它会检查所有配置文件的语法。看到 “syntax is ok” 和 “test is successful” 才说明配置没写崩。执行ps -ef | grep nginx正常情况下能看到一个 master 进程和多个 worker 进程。打开浏览器访问一次确认 HTTP 服务真正可用了。很多新手一上来就改配置改完直接nginx -s reload结果 reload 失败还把原来的服务弄挂了。我现在的习惯永远是改任何配置之前先cp nginx.conf nginx.conf.bak改完之后先nginx -t再 reload。这个习惯帮我避开了至少十次线上事故。3. 反向代理、负载均衡、虚拟主机一份配置吃下日常99%请求3.1 反向代理到底“反”在哪兼答“nginx支持jsp吗”很多人听“反向代理”这四个字就犯晕我的理解方式很简单。正向代理是客户端主动配置一个代理由代理代表你访问目标服务器目标服务器只知道代理的存在不知道真实客户端。反向代理则相反它站在服务器前面客户端访问它时它把请求转发给后面的某台真实服务器客户端根本不知道后端的拓扑。Nginx 最常见的业务形态就是反向代理。这时候你自然会遇到一个问题Nginx 本身支持 JSP 吗答案是不直接支持。JSP 需要 Tomcat 这类 Servlet 容器才能解析Nginx 不内置这些。但通过反向代理Nginx 可以把所有.jsp请求或者某个路径前缀下的请求转发给 Tomcat由 Tomcat 处理完再交回 Nginx。用户无感知业务却完整跑起来了。后端部署时运维需要你提供的信息其实很固定域名是什么、监听哪个端口、后端服务地址是多少IP 加端口、哪些路径要转后端、是否需要支持 WebSocket、HTTPS 证书是否已经准备好。把这些信息列清楚Nginx 配置基本就完成一半了。3.2 配置server块主域名、二级域名与匹配规则一个 Nginx 配置里server块就是一台“虚拟主机”。每个server块绑定一个或多个域名对应一组监听配置。热搜词里“nginx 配置主域名和二级域名”是高频问题核心就在server_name上。匹配优先级是这样的精确匹配大于通配符匹配通配符匹配大于正则匹配。也就是说同时配置了www.example.com和*.example.com时请求www.example.com一定走精确匹配的那个块。我常用的主域名加二级域名配置长这样server { listen 80; server_name example.com www.example.com; # 主站相关配置 } server { listen 80; server_name blog.example.com; # 二级域名转发到另一套后端或另一个静态目录 location / { proxy_pass http://blog_backend; } }这里最容易踩的坑其实不在 Nginx而在 DNS。很多人配好server_name后访问二级域名还是 502 或 404最后发现是 DNS 解析没把blog.example.com指向这台服务器。测试阶段最简单的办法是改本机 hosts 文件把域名临时解析到服务器 IP确认 Nginx 配置没问题后再去改正式 DNS。3.3 upstream负载均衡让流量分摊到多台后端当后端服务不止一台时upstream就该登场了。它定义一个后端服务器组location里通过proxy_pass指向这个组Nginx 会在组内做负载均衡。upstream backend_pool { server 192.168.1.10:8080 weight2; server 192.168.1.11:8080 weight1; server 192.168.1.12:8080 backup; } location /api/ { proxy_pass http://backend_pool; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }默认策略是轮询每台服务器轮流接请求。weight参数能加大某台机器的权重适合性能不均衡的集群。ip_hash策略会让同一个 IP 的请求始终落到同一台后端适合需要会话保持的场景。least_conn会把新请求分给当前活跃连接最少的后端适合长请求占比较高的业务。健康检查方面Nginx 自带的被动检查通过max_fails和fail_timeout控制请求连续失败几次就把这台机器暂时踢出组过一段时间再重新尝试。对大部分中小业务来说这套配置已经够用。我实测下来proxy_set_header Host $host这行非常关键。不加它后端收到的 Host 会是 upstream 的 IP导致很多基于域名的后端服务直接返回 404。3.4 动静分离与root/alias的经典区别静态资源由 Nginx 直接返回、动态请求转发后端的动静分离是 Nginx 性能优势的最大体现。配置上要注意root和alias的区别这是我见过面试被问、实战中同事也经常搞混的一组指令。root会把整个 location 的完整路径拼到根目录后面。比如location /static/ { root /data/www; }请求/static/a.png时Nginx 找的是/data/www/static/a.png。注意location 里的/static/被保留了下来。alias则不同它会把 location 匹配到的部分整体替换掉。比如location /static/ { alias /data/files/; }请求/static/a.png时Nginx 找的是/data/files/a.png/static/被替换成了/data/files/。如果配置成alias /data/files少一个结尾斜杠路径拼接很可能出错。动静分离配置的一个典型形态是location /static/走本地目录并设置强缓存其他所有请求proxy_pass给后端。这样图片、CSS、JS 完全不经后端后端压力能降一大截。4. HTTPS证书配置以及那个让我排了一夜的SSL报错4.1 拿到证书后Nginx里到底要配什么在 Godaddy、win-acem 这类证书服务平台申请的证书下载时一般有 Nginx 类型选项。所谓 Nginx 类型本质上就是 PEM 格式的证书文件和 Key 私钥文件。拿到两个文件后在 Nginx 里的配置其实很固定server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/example.com/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; }最容易出问题的不是ssl_certificate而是证书链不完整。一张正规的证书通常由站点证书、中间证书、根证书组成。浏览器要验证站点证书的信任链就必须拿到中间证书。很多平台下载的文件里包含了完整链fullchain如果你的证书文件只有站点证书那一段就必须把中间证书手动拼接进去否则浏览器会报警告部分客户端甚至直接拒绝连接。拼接的方式很简单用编辑器把站点证书内容和中间证书内容按顺序放到同一个文本文件里站点证书在前中间证书在后然后让ssl_certificate指向这个合并后的文件即可。4.2 排错记录no required ssl certificate was sent有一次配置双向 TLS 时我连续排了一夜最后定位到一个把我绕晕很久的报错“400 No required SSL certificate was sent”。先说明白这个报错和服务器证书缺失完全是两回事。它出现在你配置了双向 TLS 的情况下——也就是 Nginx 要求客户端必须出示客户端证书进行身份验证。如果客户端没有安装或没有正确携带客户端证书Nginx 在握手阶段就会直接返回 400。配置里触发这个行为的是这两个指令ssl_verify_client on; ssl_client_certificate /etc/nginx/ssl/ca.crt;排查链路建议这样走先看错误日志确认报错发生在 TLS 握手阶段还是 HTTP 请求阶段。打开配置文件搜索ssl_verify_client确认是否开启了双向认证。如果业务根本不需要双向认证把ssl_verify_client on改成off或者直接删掉问题立刻解决。如果确实需要双向认证那就去检查客户端证书是否已正确安装到发起请求的机器上以及该证书是否由ssl_client_certificate指定的 CA 签发。还有一个容易踩的隐蔽点很多自动化监控工具、健康检查探针不会携带客户端证书一旦你的服务开启双向认证探针请求就会返回 400导致监控误报服务宕机。我当时就因为这个被半夜叫起来过。解决方法是给探针单独开一个不需要客户端证书的 server 块或者给探针配上证书再访问。用openssl s_client -connect 域名:443可以手动查看握手过程中的证书发送情况输出里会明确显示是否发送了客户端证书这是定位这类问题最直接的武器。4.3 证书续期、多域名证书和会话复用证书到期引发的故障我见过太多基本都是没有提前配置续期。使用 certbot 的话可以加一条定时任务自动续期certbot renew --nginx --quiet如果证书是从服务商平台手动下载的那就得记得在到期前重新申请、替换文件、reload。多域名场景建议直接申请 SAN 证书或者泛域名证书*.example.com一张证书覆盖主域名和所有二级域名省去后期为每个子域单独维护证书的麻烦。最后提醒一句TLS 握手本身开销不小高并发场景下一定要开会话缓存。ssl_session_cache shared:SSL:10m配好后同一个客户端在一定时间内重连可以复用会话避免重新走完整的签名验证流程。这个配置在性能测试里实测能明显降低 CPU 消耗。5. 平滑升级与进程管理换版本不用停业务5.1 先搞懂master和worker各干什么Nginx 启动后进程模型是清晰的两层。master 进程负责全局管理读取配置、fork worker、监听信号、平滑升级。worker 进程负责实际干活每个 worker 运行一个事件循环处理连接和请求。为什么不让一个 master 直接处理请求因为如果 worker 崩溃直接导致整个 Nginx 挂掉太脆弱。分进程之后某个 worker 出了严重问题master 还能把它重启而且多 worker 可以充分利用多核 CPU每个 worker 绑定一个或多个核心并行处理事件。你在系统里执行ps -ef | grep nginx时会看到master 的进程号往往很小worker 的进程号是后来 fork 出来的。理解了这个结构才能理解下面这些信号为什么能实现平滑升级。5.2 信号、reload与restart的区别Nginx 的运行控制本质上是向 master 进程发信号TERM、INT立即停止相当于强杀。QUIT优雅停止处理完当前请求再退出。HUP重载配置不停机。nginx -s reload发送的就是这个信号。USR2启动新的 master用于平滑升级。WINCH让旧 worker 优雅退出。生产环境里最常用的操作是 reload但很多人分不清 reload 和 restart 的差别。systemctl restart nginx是完整地停掉进程再重新启动期间会有一段服务不可用时间所有正在处理的请求都会被中断。reload 则是让 master 重新读取配置文件、fork 新 worker、优雅退掉旧 worker旧连接还能继续处理完。不过 reload 也不是绝对零影响。如果一个长连接一直不断开旧 worker 可能永远等不完HUP 就无法完成最后的回收。新版本 Nginx 提供了worker_shutdown_timeout指令超过指定时间强制关闭旧连接业务能接受短暂断连的话可以配一个合理值。我的建议是改配置用 reload升级二进制才用 restart 或平滑升级流程。5.3 平滑升级新版本的具体操作平滑升级的目标是替换 Nginx 二进制文件但不重启服务。完整流程如下准备好新版本 Nginx 的二进制文件编译完先nginx -V确认版本和模块。备份当前正在运行的二进制cp /usr/sbin/nginx /usr/sbin/nginx.old。用新二进制替换旧文件。向当前 master 发送 USR2 信号kill -USR2 $(cat /var/run/nginx.pid)。这时 Nginx 会启动一个新的 master新旧两个 master 同时存在。向旧 master 发送 WINCH 信号kill -WINCH $(cat /var/run/nginx.pid.oldbin)。旧 worker 开始优雅退出新 worker 接管新连接。确认新 master 运行正常后向旧 master 发送 QUIT把旧进程彻底关闭。这套流程我实测过多次能在完全不中断业务的情况下完成版本升级。但要注意几点新旧版本配置文件必须完全兼容否则新 master 可能启动失败升级前一定要把旧的二进制文件备份好万一新版本有问题还能用旧文件回滚。很多新手升级失败是因为直接systemctl restart nginx然后新版本启动报错服务直接断开。走平滑升级流程的话新旧并存期间如果发现新版本异常还能及时回滚这种安全感是 restart 给不了的。6. 高并发调优和createfile()报错的完整排查链路6.1 worker数、连接数、文件描述符高并发三件套真正压榨 Nginx 高并发性能先看这三个参数worker_processes auto; worker_connections 10240; worker_rlimit_nofile 65535;worker_processes设为 auto 会按照 CPU 核心数自动生成 worker 数量这是最简单也最合理的选择。worker_connections表示每个 worker 最多同时维护多少连接。Nginx 官方文档计算最大并发数的公式是worker_processes × worker_connections但如果你在做反向代理实际并发能力还要再除以 2因为一个客户端请求会占用一个前端连接和一个后端连接一个请求消耗两个连接额度。系统层面的限制同样不能忽略。查看当前进程能打开的文件描述符数量用ulimit -n如果这个值太小Nginx 连接数再多也白搭底层too many open files会直接限制你。修改/etc/security/limits.conf把 nofile 提高同时配上worker_rlimit_nofile让 worker 进程突破默认限制。内核参数方面我一般会调net.ipv4.ip_local_port_range扩大本地可用端口范围以及net.core.somaxconn提高 accept 队列长度。至于net.ipv4.tcp_tw_reuse不同内核版本默认策略不一样有些新内核默认就是开启相关处理逻辑的改之前先确认内核版本别拿着老教程盲目设置。6.2 那个让人头疼的[emerg] createfile()报错到底怎么查Windows 环境下最经典的启动失败就是类似这种报错nginx: [emerg] createfile() d:/phpstudy_pro/www/admin2.com/nginx.htaccess failed (2: The system cannot find the file specified)第一次遇到时我也很懵createfile 是什么为什么 Nginx 会去创建一个nginx.htaccess文件其实根源很简单——某个配置指令要求 Nginx 打开或创建一个文件但这个文件的路径不存在或者目录没有写权限。在 Windows 下Nginx 底层用的是 CreateFile 系统调用所以报错就表现为 createfile()。这条报错的路径是d:/phpstudy_pro/www/admin2.com/nginx.htaccess典型的 phpStudy 集成环境域名目录。我排查后发现是有个配置文件把error_log或access_log的路径写到了这个不存在的文件上。Nginx 启动时要按配置创建日志文件结果找不到对应目录直接启动失败。完整排查链路建议这样走完整阅读报错PS 后面的提示是(2: No such file or directory)还是(13: Permission denied)。前者是路径不存在后者是权限不够两个修法不一样。全局搜索报错里出现的文件名或路径看是哪个指令引用了它。检查该路径的父目录是否存在。如果目录不存在先创建目录如果目录存在但权限不足给 Nginx 进程授权。看是否把日志文件路径误配到了网站根目录下一个并不存在的文件名上。如果是改成 Nginx 默认的日志位置最省事error_log logs/error.log; access_log logs/access.log;改完先nginx -t验证再启动服务。这条经验也提醒我Windows 下配 Nginx路径书写一定要保守尽量用相对路径避免盘符加反斜杠混用避免中文目录避免在网站目录里创建 Nginx 自身要用的文件。6.3 用VSCode把Nginx配置规整起来少踩低级语法坑Nginx 配置语法有一个特点块结构靠花括号指令以分号结尾缩进纯粹为了好看语法检查只看括号和分号。但正因为如此配置一长就很容易出现花括号不匹配、分号丢失这类低级错误。我现在的习惯是用 VSCode 加 nginx-formatter 插件写完配置直接格式化缩进统一、层级清晰花括号配没配对一目了然。插件安装后选中配置内容右键选择格式化即可。更重要的是配置文件的组织方式。主配置nginx.conf只放全局配置和基本的 server 块其他业务配置全部拆到conf.d/目录下每个域名一个文件用include conf.d/*.conf;加载。这样排查问题时直接按域名找文件不用在一大坨配置里翻半天。维护规范方面我给自己定了几条死规矩每次改动前备份每次改完必须nginx -treload 必须在低峰期执行配置文件里永远写上注释标明这段配置是哪个业务的、负责人是谁。这些规矩看起来琐碎长期维护下来能省下的时间远超投入。6.4 把调优和排错经验变成面试里的加分项Nginx 是后端和运维面试的常客热搜词里就有“nginx面试题”。我梳理了几个高频问题结合前面的实战说下回答思路Nginx 和 Apache 的差异核心是进程模型Nginx 的事件驱动非阻塞模型在高并发下内存占用更低。反向代理和负载均衡的原理讲清客户端、Nginx、后端三者的关系以及 upstream 的几种策略。502、504、499 分别怎么排查502 是后端无响应或响应错误先看后端进程504 是后端处理超时调 proxy_read_timeout499 是客户端在等待期间主动断开重点看后端处理耗时和客户端行为。高并发参数怎么调把 worker_processes、worker_connections、文件描述符、内核参数这条链路讲完整。面试官最想听到的不是你背出的配置项而是解决过的真实问题。把你调优时算并发数的过程、排查 createfile 报错的链路、处理双向 TLS 误报的经过讲出来比列出二十个指令更有说服力。最后分享一个我个人的实战习惯每次接到一个 Nginx 相关的排障任务第一件事不是看业务代码而是先执行nginx -V确认版本和编译模块再查看错误日志最后一百行。版本决定哪些指令可用日志决定问题方向这两样对齐了百分之八十的问题已经能定位。Nginx 这个软件看起来简单真正用好的关键是把进程模型、配置语义和排错思路串成一条线。希望这篇能帮你少走一些弯路。