Nginx从零到实战:反向代理、HTTPS与并发调优全指南

发布时间:2026/10/6 14:23:39
Nginx从零到实战:反向代理、HTTPS与并发调优全指南 我第一次正儿八经接触 Nginx是因为公司一台 1 核 1G 的服务器上挤了三个站点一个 Java 服务跑在 8080一个 Node 服务跑在 3000还有一个直接拿 Python 起了个 5000。用户访问的时候得记一堆端口号每次新同事入职都要被问一遍到底该访问哪个端口。后来我用了 Nginx把所有流量统一收口到 80 和 443再按域名分发到后面各个服务服务器瞬间清净了。从那一刻起我就觉得Nginx 是 Linux 运维和 Web 开发都绕不开的一块基石。这篇教程是个完整的从零到实战路径先讲清楚 Nginx 到底是干什么的然后带你装好、看懂配置文件再一步步实现多站点多域名、反向代理、HTTPS、并发调优、监控与安全加固。适合刚接触 Nginx 的新手也适合之前只会照抄配置、想系统补一遍原理的同学。我会把平时踩过的坑直接标出来尽量让你少走弯路。1. 先搞清楚 Nginx 的定位它到底是干什么的1.1 从一个端口打架的故事说起想象一下没有 Nginx 的日子。你有一个前端页面、一个后端接口、一个文件上传服务它们分别跑在 3000、8080、9000 端口。为了能让用户访问你得在云平台安全组里把这些端口全部放行还要把域名解析到服务器再祈祷每个服务都能稳定运行。一旦某个服务挂了用户直接看到连接拒绝体验非常糟糕。Nginx 改变的是这个格局它自己监听 80 和 443 这两个默认端口然后根据请求的域名、路径、参数把请求转给对应的后端服务。用户永远只访问一个地址具体是哪个进程在干活用户不关心前端也不关心。这就是 Nginx 学习的核心价值——它是所有 Web 流量的统一入口。这个思路用生活化的方式理解就是小区门口的快递柜。以前快递员挨家挨户敲门每个包裹都得送到对应楼层现在所有快递先放在快递柜用户自己去取。Nginx 就是那个快递柜后端各种服务就是楼里的住户。所有请求先到 Nginx再由它决定这个包裹该放到哪个柜子。1.2 三个身份Web 服务器、反向代理、负载均衡器Nginx 最常见的用法有三种。第一种是静态文件服务器。HTML、CSS、JS、图片这类静态资源Nginx 直接读磁盘返回效率极高比让 Node 或 Python 去处理静态文件要省资源得多。很多前端项目打包后就是一个 dist 目录扔给 Nginx 就能跑。第二种是反向代理。后端服务跑在内网端口Nginx 对外暴露统一入口把 /api 开头的请求转到 Java 或 Go 服务把 /upload 转到文件服务。这里要特别说明反向代理是服务端的转发行为客户端只认 Nginx后端服务对客户端完全不可见。这是 Nginx 入门到实战里最重要的一个概念。第三种是负载均衡。当同一个后端服务部署了多台实例时Nginx 通过 upstream 配置把请求分发到不同服务器按轮询、权重、IP 哈希等策略分配。这个能力让 Nginx 在微服务架构里仍然占据着入口网关的位置。1.3 Nginx 和 Apache、Tomcat 到底是什么关系很多新手会把 Nginx 和 Tomcat 搞混这里用一张表说清楚。软件定位擅长处理典型场景NginxWeb 服务器 / 反向代理静态文件、高并发转发、负载均衡网站统一入口、静态资源托管ApacheWeb 服务器模块丰富、配置灵活老牌虚拟主机、兼容性要求高的环境TomcatJava 应用服务器Servlet / JSP 动态请求Java Web 应用运行容器简单记忆Tomcat 是真正干活的人负责运行 Java 程序Nginx 是前台接待负责把用户领到对应的工位。Apache 和 Nginx 是同类产品但 Nginx 的并发模型更轻盈内存占用更低所以在云服务器普遍紧张的环境里更受欢迎。1.4 学 Nginx 的性价比我见过很多做前端、运维、甚至算法工程的同学都在学 Nginx原因很简单不管你在哪个岗位只要你的代码要部署到服务器上八九不离十都会遇到 Nginx。你不需要成为专家但至少得能看懂配置文件能自己加一个站点能处理为什么访问不了这类基础问题。这篇文章就是朝这个目标去的。2. 从零装好一个 Nginx安装方式、目录结构和第一根探针2.1 环境准备与安装方式选择先准备一台 Linux 服务器。我习惯用 Ubuntu 22.04 做演示Debian 系操作完全一致CentOS/RHEL 系把 apt 换成 yum、systemctl 用法相同即可。学习阶段一台 2 核 2G 的云主机或者虚拟机就够了Nginx 本身非常省资源。安装 Nginx 的方式主要有三种我列一下各自的适用场景安装方式优点缺点适合谁apt / yum 包管理器安装快、依赖自动处理、方便卸载版本更新慢新手学习和日常使用源码编译版本最新、可定制模块编译时间长、依赖麻烦需要特定模块或极致性能的人Docker 容器环境隔离、迁移方便网络和挂载配置有学习成本容器化部署、微服务环境新手我强烈建议先用包管理器装先把 Nginx 跑起来、把配置看明白之后再考虑源码编译定制模块的事。源码编译不是炫技而是你确实需要某个第三方模块时才值得折腾。2.2 用 apt 完成安装并验证在 Ubuntu 上安装非常简单sudo apt update sudo apt install nginx -y装完后验证一下版本和工作状态nginx -v sudo systemctl start nginx sudo systemctl status nginx看到 active (running) 就说明启动成功了。然后在浏览器访问服务器的公网 IP 或本机的 localhost应该能看到 Nginx 的默认欢迎页。如果你部署在云服务器上却打不开先去安全组确认 80 端口已放行——这个坑几乎每个新手都会踩一次。2.3 安装后的目录结构Nginx 装完最重要的目录是 /etc/nginx所有配置都在这里。/etc/nginx/ ├── nginx.conf # 主配置文件 ├── conf.d/ # 自定义配置目录很多发行版用 ├── sites-available/ # 站点配置Ubuntu 特有启用需要软链 ├── sites-enabled/ # 实际生效的站点配置 ├── html/ # 默认站点根目录 └── logs/ # 日志目录部分发行版在 /var/log/nginx这里有个容易让新手懵的地方Ubuntu 默认用 sites-available 和 sites-enabled 管理站点创建站点时要把配置文件放到 sites-available再用 ln -s 软链到 sites-enabled 才生效。而 CentOS 和很多 Docker 镜像直接用 conf.d 目录把 .conf 文件放进去就自动加载了。两种机制本质一样Nginx 在 http 块里包含这些目录下的所有配置。2.4 Docker 安装与一个经典挂载坑如果你喜欢用 Docker 整套环境也可以docker run -d --name nginx -p 80:80 nginx容器化部署干净利落但有一个坑我遇到过不止一次把宿主机的 conf.d 目录挂载进容器时如果宿主机目录是空的它会覆盖容器镜像里自带的 conf.d导致 Nginx 启动了但没有可用站点访问返回 404。另外宿主机上以 root 权限创建的文件容器内的 nginx worker 进程可能没有权限读取启动时直接报 permission denied。解决办法很简单挂载单个配置文件而不是整个目录挂载前用 stat 确认文件权限必要时 chown 给 101 号用户容器内 nginx 用户的 uid。这个坑在搜索记录里高频出现我特意拿出来说就是希望大家别在第一步就被卡住。2.5 第一根探针用 curl 确认服务正常浏览器能看到欢迎页说明安装成功。但很多服务器上没有图形界面我更习惯用 curl 来确认curl -I http://localhost输出里看到 HTTP/1.1 200 OK就说明 Nginx 在正常响应。之后所有配置改完我也会用 curl 检查这比打开浏览器快得多。把 curl 当作你的第一根探针后面的排错会轻松很多。3. 把配置文件的骨架吃透nginx.conf 的三大块与 location 匹配3.1 整体结构events、http、server、location打开 /etc/nginx/nginx.conf不要被密密麻麻的注释吓到。Nginx 的配置是嵌套结构核心骨架只有四层# 核心层进程设置 user nginx; worker_processes auto; # 事件层连接处理 events { worker_connections 1024; } # HTTP 层所有 HTTP 相关配置 http { include /etc/nginx/conf.d/*.conf; server { listen 80; server_name example.com; location / { root /var/www/html; index index.html; } } }你可以把 http 块理解成全局交换机server 块是交换机上划分的虚拟局域网location 块则是每个局域网里的分线盒。配置是层层嵌套的外层设置会被内层继承内层可以覆盖外层。理解这个嵌套关系后看任何 Nginx 配置都不会晕。3.2 server 块从端口和域名到站点根目录一个 server 块就是一台虚拟主机。它通过 listen 和 server_name 两个条件决定接收哪些请求。server { listen 80; server_name blog.example.com; root /var/www/blog; index index.html; }这里的逻辑是当请求到达服务器的 80 端口且 Host 头是 blog.example.com 时Nginx 把这个请求交给这个 server 块处理根目录指向 /var/www/blog默认首页是 index.html。server_name 支持精确域名、通配符*.example.com和正则表达式匹配优先级是精确匹配 以 * 开头的最长通配 正则 默认站点。3.3 location 匹配规则从零开始就要吃透location 是整个 Nginx 配置里最核心、也最容易写错的部分。它的作用是在同一个 server 下根据不同 URL 路径走不通的处理逻辑。比如 /api/ 反代到后端/static/ 读本地文件/favicon.ico 直接返回 204。location 的各种写法本质上是匹配前缀规则如下写法匹配方式优先级location /path精确匹配最高location ^~ /path普通前缀匹配命中后不再查正则高location ~ /path正则匹配区分大小写次之location ~* /path正则匹配不区分大小写次之location /path普通前缀匹配低location /兜底前缀匹配所有最低匹配流程是先看所有普通前缀记录最长的那个如果这个最长前缀是 ^~ 形式直接结束否则继续尝试正则一旦正则有匹配就用正则结果正则都没有再用之前记录的最长前缀。实际写配置时我最常用的组合是 location ^~ /api/ 来反代接口location ~* .(js|css|png)$ 来处理静态资源缓存location / 做最后的兜底。把这个优先级记牢配置就不会出现我明明写了规则怎么不生效的尴尬。3.4 root 和 alias 的区别try_files 的妙用root 和 alias 是另一个高频混淆点。简单说root 是拼接alias 是替换。location /static/ { root /var/www/project; # 请求 /static/a.png /var/www/project/static/a.png } location /static/ { alias /var/www/static/; # 请求 /static/a.png /var/www/static/a.png }root 会在根目录后加上 location 的路径alias 不会。新手最容易犯的错误是根目录明明是 /var/www/project静态文件在 project/static 下结果用了 alias 又写成 /var/www/project/static/导致路径多了一截。try_files 则是按顺序尝试多个路径找到一个存在的就返回。经典场景是 Vue/React 单页应用location / { root /var/www/spa; index index.html; try_files $uri $uri/ /index.html; }这个配置的意思是请求 /about 时先找真的 about 文件找不到就找 about 目录目录也没有就返回 index.html由前端路由接管页面。没有 try_files单页应用刷新后全是 404。3.5 改配置后先检查语法再优雅 reload改完 Nginx 配置一定要先做语法检查再让 Nginx 重新加载sudo nginx -t sudo systemctl reload nginxnginx -t 会告诉你配置文件有没有语法错误比如少了分号、路径不存在之类。systemctl reload 是优雅重载它会用新配置启动新的 worker 进程处理旧请求的 worker 等当前请求结束再退出期间服务不会中断。相比之下systemctl restart 是先停后启在线用户会瞬间断一下生产环境尽量少用。还有一个小细节reload 不会重新读取 worker_processes、worker_rlimit_nofile 这类全局生效的参数改这些需要 restart。我在一次压测前只改了 worker_processes 就 reload结果进程数没变白白排查了半天。这个坑值得记下来。4. 实战关卡一多站点、多端口、自定义域名开发环境就该这么配4.1 为什么开发环境也需要多站点很多人在本机写代码项目多了就靠端口区分一个项目 3000一个项目 8080机密的服务再多几个端口号连自己都记不住。更麻烦的是有些项目对 URL 有严格要求比如回调地址必须固定域名端口一变就出问题。用 Nginx 做多站点后每个项目对应一个独立的 server 块项目之间配置互不干扰日志也各自独立。开发环境的典型做法是让 Nginx 统一监听 80 或几个自定义端口通过自定义域名比如 project1.test、project2.test分发到不同后端。这套逻辑和线上几乎一模一样从开发到上线是平滑过渡的。4.2 本地 虚拟机多端口场景怎么组织搜索热词里高频出现的本地 虚拟机 多端口 nginx 开发环境多站点自定义域名配置我拆解一下。典型的拓扑是这样的你有一台虚拟机IP 192.168.56.101专门跑 Nginx 和各个后端服务宿主机通过浏览器访问虚拟机的 Nginx。虚拟机里开了多个服务端口比如 8081 是前端 A 的构建产物8082 是后端 B 的接口。这种场景下Nginx 的作用是对外暴露两个端口如 8081 和 8082每个端口对应一个 server_name再根据 Host 头分发到对应服务的实际情况。注意开发环境的多站点不一定要多个端口同一端口 不同域名也能实现但如果你同时需要对不同端口做验证多端口方案更直观。4.3 动手写两份虚拟主机配置假设我要配置两个站点blog.test 和 api.test。在 /etc/nginx/conf.d/ 下新建两个文件# /etc/nginx/conf.d/blog.conf server { listen 8081; server_name blog.test; root /srv/www/blog; index index.html; location / { try_files $uri $uri/ 404; } access_log /var/log/nginx/blog.access.log; }# /etc/nginx/conf.d/api.conf server { listen 8082; server_name api.test; 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; } access_log /var/log/nginx/api.access.log; }保存后执行 nginx -t 确认语法然后 reload。现在 Nginx 就有了两个虚拟主机blog.test 从本地目录读静态文件api.test 把请求转发给本机 8080 端口的后端服务。4.4 自定义域名hosts 文件怎么改要让 blog.test 这个域名在你电脑上生效需要修改 hosts 文件。Linux/macOS 改 /etc/hostsWindows 改 C:\Windows\System32\drivers\etc\hosts在文件末尾加一行192.168.56.101 blog.test 192.168.56.101 api.test如果是纯本机测试就写 127.0.0.1。改完后在浏览器访问 http://blog.test:8081 就能看到博客页面访问 http://api.test:8082 就能访问 API。这里有个关键点Nginx 的 server_name 是读取请求里的 Host 头来匹配的浏览器会根据 hosts 文件把域名解析到虚拟机 IP再把域名作为 Host 头发过去所以两端必须配齐。4.5 默认站点与验证技巧万一你访问的域名没有对应的 server 块Nginx 会使用 listen 端口上的 default_server。比如server { listen 80 default_server; server_name _; return 404; }加上这个配置所有没匹配到规则、或者直接拿 IP 访问的请求都会统一返回 404避免暴露错误页面。这在多站点环境里是个很好的兜底策略也是线上环境安全加固的常见手段。验证时除了浏览器我还会用 curl 指定 Host 头测试比改浏览器配置快很多curl -H Host: blog.test http://192.168.56.101:8081 -I看到 200 OK就说明虚拟主机已经生效了。5. 实战关卡二反向代理的正确打开方式几个经典案例5.1 反向代理到底解决了什么问题前面说过反向代理是服务器端的转发行为。客户端向 Nginx 发起请求Nginx 根据规则把请求转发给后端服务拿到响应后再返回给客户端。整个过程客户端完全感知不到后端的存在。反向代理带来的好处很实际第一后端服务不用暴露公网端口只需监听 127.0.0.1攻击面大幅减小第二可以在 Nginx 层统一做 HTTPS 终止后端服务不关心证书第三可以把多个后端服务整合成一套对外接口客户端只需要记一个域名。这正是 Nginx 成为网关之王的原因。用生活类比反向代理就是公司前台的总机。外面的人只需要记住公司总机号码拨过去之后由总机转接给对应的分机。分机号码是什么不重要而且外人也查不到分机号。5.2 最常用的反代配置proxy_pass 与路径拼接下面是我每天都在用的反代骨架server { listen 80; server_name api.example.com; 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; proxy_set_header X-Forwarded-Proto $scheme; } }理解这个配置的关键是 proxy_pass 的路径拼接规则。当 location 使用前缀匹配时proxy_pass 后是否带斜杠结果完全不同proxy_pass http://127.0.0.1:8080/;带斜杠请求 /api/user 会变成 /user/api/ 前缀被丢掉proxy_pass http://127.0.0.1:8080;不带斜杠请求 /api/user 会变成 /api/user前缀原样保留我见过太多人栽在这条规则上后端接口明明定义的是 /user结果 Nginx 转发过去变成了 /api/user返回 404。一定要根据后端实际路由来决定是否带斜杠。最简单的记忆方法想让后端看不见location 前缀就带斜杠想让后端看见完整路径就不带。5.3 给 Ollama 做一个带 API Key 校验的反代入口现在很多人会在本地服务器跑 Ollama 这类本地模型服务。Ollama 默认监听 127.0.0.1:11434只能本机访问如果你想在局域网内让多台设备共用这个模型服务或者让 Cherry Studio 这类桌面客户端连上去直接在 Nginx 里配一个反代入口就很方便。先说设计思路因为 Ollama 已经占用了 127.0.0.1:11434为避免端口冲突Nginx 监听 18080 端口反代到本机的 11434。同时加一道 API Key 校验避免局域网内任何人都能白嫖你的算力。# 定义 Authorization 校验变量 map $http_authorization $ollama_auth_ok { default 0; Bearer sk-your-secret-key 1; } server { listen 18080; server_name ollama.internal; # 校验失败直接 401 if ($ollama_auth_ok ! 1) { return 401; } location / { proxy_pass http://127.0.0.1:11434; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }配置生效后客户端访问 http://192.168.1.100:18080 时请求头里必须带上 Authorization: Bearer sk-your-secret-key否则 Nginx 直接返回 401。Cherry Studio 里新增 OpenAI 兼容服务时把 Base URL 填成 http://192.168.1.100:18080/v1再填入同样的 API Key就能走 Nginx 这层访问 Ollama 了。这里要重点提醒把本地模型服务暴露到局域网甚至公网一定要加访问控制。不做任何认证就把 11434 敞开的人我用搜索引擎随便找都能找到一批被人拿去做恶意调用算力是小数据泄露才是麻烦。Nginx 的 if return 401 只是最基础的校验方式生产环境建议用 auth_request 模块对接统一认证服务安全性会高一个量级。5.4 反代常见的三类问题超时、CORS、上传大小第一个是超时。Nginx 默认的 proxy_read_timeout 是 60 秒对于普通接口足够了但如果你做镜像站、文件下载服务或者后端处理一份报告要几分钟客户端就会收到 504 Gateway Timeout。这种情况要调大这几个参数location /download/ { proxy_pass http://127.0.0.1:9000; proxy_connect_timeout 15s; proxy_read_timeout 300s; proxy_send_timeout 300s; }proxy_connect_timeout 控制的是与后端建立连接的超时proxy_read_timeout 是从后端读取响应的超时proxy_send_timeout 是向后端发送请求体的超时。遇到大文件、慢接口优先检查这三兄弟而不是盲目改内核参数。第二个是跨域问题。前端页面在 a.example.com接口在 api.example.com浏览器会发起跨域请求。如果你在 Nginx 层做反代通常需要在响应头加上 CORS 标记location /api/ { proxy_pass http://127.0.0.1:8080/; add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods GET, POST, OPTIONS always; add_header Access-Control-Allow-Headers Authorization, Content-Type always; if ($request_method OPTIONS) { return 204; } }网上搜Nginx invalid cors request绝大部分原因就是缺少 Access-Control-Allow-Headers浏览器发出的预检请求被后端或者 Nginx 拒了。注意 add_header 的 always 参数它保证即使是错误响应也会带上 CORS 头否则某些场景下浏览器一样会拦截。第三个是上传大小。客户端上传一个大文件时Nginx 默认限制 body 为 1MB超出直接返回 413 Request Entity Too Large。解决办法client_max_body_size 50m;这个指令可以放在 http、server 或 location 层。放在 location 层时只对特定路径生效比如文件上传接口。5.5 补充一句四层反代也归 Nginx 管上面讲的都是 HTTP 反代。如果你的需求是转发 MySQL、Redis、或任意 TCP 流量就要用 Nginx 的 stream 模块。这部分我先埋个伏笔后面讲并发调优时再展开因为四层转发和连接数之间的关系更密切。6. 给站点加 SSL证书申请、替换与不生效排错6.1 为什么必须上 HTTPS没有 HTTPS 的时代所有内容明文传输用户密码、Cookie、请求参数在网络上裸奔。现在浏览器对 HTTP 站点的警告越来越严很多 Web API 也强制要求 HTTPS。部署 HTTPS 的核心就是三件事一张证书、一个监听 443 的 server 块、一个自动续期的机制。证书的作用是证明这个域名是可信的并完成密钥交换。申请证书有三种常见路径使用 Lets Encrypt 免费证书配合 certbot 自动续期使用云平台提供的免费证书手动部署使用自签名证书用于内网测试。线上环境首选 Lets Encrypt成本为零且支持自动续期。6.2 用 certbot 自动申请证书假设服务器上已经配置好了 example.com 这个站点并且 80 端口可访问。安装 certbot 的 Nginx 插件sudo apt install certbot python3-certbot-nginx -y sudo certbot --nginx -d example.com -d www.example.comcertbot 会检测到 Nginx 配置自动为这两个域名申请证书并修改你的 server 块加入证书路径、443 端口监听和 HTTP 跳 HTTPS 规则。整个过程只需要输入邮箱同意条款之后 certbot 会通过系统定时任务自动检查并续期证书基本不用人工干预。6.3 替换证书后不生效的排查链路替换 SSL 证书不生效是搜索热词也是我工作中被问得最多的问题之一。遇到这种情况不要急着清浏览器缓存按顺序排查第一步确认 Nginx 实际加载的证书路径。执行nginx -T 21 | grep ssl_certificate这个命令会输出所有生效配置里的证书路径。我见过有人把新证书传到 /etc/nginx/ssl/ 下但配置文件里还写着旧的 /etc/nginx/cert/ 路径或者同一份配置被多个 server 块引用你以为改的是 A 文件实际 Nginx 读的是 B 文件。第二步检查证书文件权限。Nginx 的 worker 进程通常以 nginx 用户运行如果证书文件权限是 600 且属主是 rootNginx 就可能在启动时报错或者加载不到。一般证书文件权限设置为 644 就能满足需求私钥设置为 600、属主为 root 或者 nginx 都可以。第三步看看是不是被上层代理缓存了。如果你在 Nginx 前面还有云负载均衡或 CDN证书更新后这些节点的缓存或者回源配置可能没有同步浏览器拿到的是缓存节点里的旧证书。此时需要去云控制台刷新或等待同步。第四步确认浏览器确实重新拿到了证书。用无痕窗口访问 https://example.com点地址栏的小锁图标查看证书颁发对象。如果还是没有更新继续用 openssl 直接验证openssl s_client -connect example.com:443 -servername example.com /dev/null 2/dev/null | openssl x509 -noout -dates -subject这条命令看到的是服务器实际返回的证书内容和有效期限如果 cert 的 Validity 没有变化说明问题还在 Nginx 或网络链路上如果证书已经更新那大概率就是浏览器缓存。6.4 err_cert_common_name_invalid 到底是怎么回事搜索热词里还有一条Nginx 转发 https 反向代理 net::err_cert_common_name_invalid。这个报错的本质是浏览器访问的域名和服务器证书里写的域名不匹配。常见的触发场景有三个。第一个是用户用 IP 访问站点但证书只签了域名浏览器校验时发现访问的地址不是证书里的 Common Name 或者 SANSubject Alternative Name直接报错。第二个是多域名站点配置了错误的证书比如 example.com 的 server 块里挂了 www.example.com 的证书。第三个是证书链不完整服务器只返回了站点证书没有返回中间证书浏览器无法构建完整的信任链。排查方法很简单openssl x509 -in /etc/nginx/ssl/example.com.pem -noout -text | grep -A1 Subject Alternative Name看 SAN 字段里是否包含你正在访问的域名。如果不包含要么换证书要么访问与证书匹配的域名。对于内网 IP 的测试环境最省事的做法是生成自签名证书并把 SAN 加上 IP 地址同时把该证书导入操作系统信任区。6.5 一个可复用的 HTTPS server 配置模板最后放一个我觉得比较稳妥的 HTTPS server 配置模板server { listen 443 ssl; http2 on; server_name example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; root /var/www/html; index index.html; } server { listen 80; server_name example.com; return 301 https://example.com$request_uri; }第二个 server 块负责把 HTTP 流量永久重定向到 HTTPS避免用户用 HTTP 访问时出现不安全警告。把这段配置替换到你的站点配置里nginx -t 通过后 reloadHTTPS 就生效了。7. 并发上不去连接数与性能调优的硬核细节7.1 连接数老是用超的真实原因很多人在搜索Nginx 最大并发连接数老是用超第一反应是 Nginx 配置不够大。实际上连接数超了要分两层看。第一层是 Nginx 自身的连接模型。每个 worker 进程能同时处理的连接数由 worker_connections 限制所有 worker 的连接数上限就是 worker_processes 乘以 worker_connections。举个例子worker_processes 为 2worker_connections 为 1024那么 Nginx 理论最大并发连接数约 2048。如果一台服务器同时有大量长连接比如 WebSocket这个数字会迅速被吃掉。第二层是操作系统的文件描述符限制。每个连接在 Linux 里就是一个 fd进程能打开的文件描述符数量受 ulimit 限制。很多人在 Nginx 配置里把 worker_connections 调到了 10000但系统的 ulimit -n 只有 1024实际根本跑不上来。用下面的命令确认ulimit -n cat /proc/$(pidof nginx)/limits | grep open files如果显示 1024说明 worker 进程最多同时打开 1024 个文件配置再大都白搭。7.2 核心调优参数从小水管到蓄水池我给出一个适合多数中小站点的调优配置并解释每个参数的作用worker_processes auto; # 按 CPU 核数自动设置 worker 数 worker_rlimit_nofile 65535; # worker 进程能打开的文件描述符上限 events { worker_connections 10240; multi_accept on; use epoll; } http { sendfile on; tcp_nopush on; keepalive_timeout 65; keepalive_requests 1000; gzip on; gzip_types text/plain text/css application/json application/javascript; }worker_processes 设为 autoNginx 会自动匹配 CPU 核心数。worker_rlimit_nofile 要配合系统 ulimit 一起调建议先改系统级限制echo nginx soft nofile 65535 /etc/security/limits.conf echo nginx hard nofile 65535 /etc/security/limits.confevents 块里的 multi_accept 让 worker 一次接受多个新连接避免频繁唤醒use epoll 是 Linux 上最高效的事件模型现在 Nginx 默认就会自动选写出来是让你知道这里在干什么。keepalive_timeout 控制客户端连接的空闲超时。对静态资源站点keepalive 能明显减少重复建连的开销而对于纯 API 服务可以把 keepalive_timeout 调小一点防止大量空闲连接占满 worker 连接数。keepalive_requests 表示一个 keepalive 连接最多能复用的请求数调大到 1000 能降低频繁建连的开销。7.3 压测别凭感觉说并发不行调完参数最好用 ab 压一下看真实能扛住多少并发ab -n 20000 -c 500 http://localhost/-n 表示总请求数-c 表示并发数。压测完重点看 Failed requests 是否为零以及 Requests per second。如果 Failed requests 大量增加先回到第 7.1 节检查 fd 限制再看 Nginx 错误日志tail -f /var/log/nginx/error.log看到 worker_connections are not enough 就说明真的到了连接瓶颈看到 open() ... too many open files 就说明是 fd 限制看到 upstream timed out 则是后端服务跟不上跟 Nginx 本身无关别让 Nginx 背锅。7.4 四层TCP反代stream 模块与连接数HTTP 反代处理的是应用层如果需要对 MySQL、Redis 这类纯 TCP 流量做转发就要用 stream 模块。Nginx 官方包默认编译了 ngx_stream_core_module直接在配置文件顶层写 stream 块stream { upstream mysql_backend { server 10.0.0.12:3306 max_fails3 fail_timeout30s; server 10.0.0.13:3306 max_fails3 fail_timeout30s; } server { listen 3306; proxy_pass mysql_backend; proxy_timeout 60s; proxy_connect_timeout 5s; } }这里的连接数模型和 HTTP 一样受 worker_connections 限制。四层转发少了 HTTP 的解析开销理论上能扛更多连接但每个 worker 的连接上限仍然存在。如果你的 TCP 连接数是最大瓶颈优先检查 worker_connections 和 fd 上限不要光看带宽。一个容易忽略的细节stream 块和 http 块是平级的不能写在 http 块里。如果 nginx -t 报错说 stream 指令不存在说明当前 Nginx 编译时没有包含 stream 模块需要安装 nginx-mod-stream 或者重新编译。8. 上线之后的维护日志、Zabbix 监控与安全加固8.1 日志是排错的第一现场Nginx 的日志分两种access.log 记录所有访问请求error.log 记录运行错误。默认路径一般是 /var/log/nginx/access.log 和 /var/log/nginx/error.log。access.log 每一行的常用字段包括客户端 IP、请求时间、请求方法、请求路径、状态码、响应字节数、referer、User-Agent。排错时我习惯这样看tail -f /var/log/nginx/access.log | awk {print $9} | sort | uniq -c | sort -rn这条命令统计当前访问里各状态码的数量。如果 404 刷屏说明资源路径有问题如果 500 刷屏问题大概率在后端如果 499说明客户端主动断开通常是用户等不耐烦了或者后端处理太慢。error.log 更要重视。Nginx 运行时遇到连接超时、权限不够、SSL 证书读取失败都会写到这里。很多明明配置没问题但就是不生效的情况看一眼 error.log 就能找到答案。我强烈建议你养成习惯改完配置 reload 后立刻 tail 一下 error.log确认没有新的报错再离开。8.2 用 Zabbix 7 监控 Nginx 的关键指标服务上线后不能靠人去盯要让监控系统替你看门。Zabbix 是开源监控里用得很多的一套系统Zabbix 7 监控 Nginx 的思路是先让 Nginx 暴露一个状态页再用 Zabbix Agent 采集。第一步在 server 块里加一个只允许本机访问的状态接口location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; }reload 后本机访问一下curl http://127.0.0.1/nginx_status输出类似Active connections: 3 server accepts handled requests 100 100 260 Reading: 0 Writing: 1 Waiting: 2Active connections 是当前活跃连接数Reading 是正在读取请求头的连接数Writing 是正在写响应的连接数Waiting 是空闲的 keepalive 连接数。这个数据和并发状态直接挂钩。第二步在 Zabbix Agent 配置里加一个自定义 item定时拉取这个页面并解析数值。以 Zabbix Agent 2 为例在配置文件的 UserParameter 区加UserParameternginx.active,curl -s http://127.0.0.1/nginx_status | awk NR1 {print $3}然后在 Zabbix 前端创建监控项键值填 nginx.active采集间隔 30 秒触发器可以设定活跃连接数连续 5 分钟超过阈值时告警。这样你就能在服务撑爆之前收到通知而不是等用户骂上门。8.3 一份可以直接抄的安全加固清单网上经常有人问 CTF 里 Nginx 安全加固题怎么做其实考来考去就是下面几个点。我整理了一份基础加固配置# 隐藏版本号防止泄露 Nginx 版本 server_tokens off; # 关闭目录浏览避免站点目录结构泄露 autoindex off; # 限制 HTTP 方法只允许常用方法 if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|OPTIONS)$) { return 405; } # 禁止访问隐藏文件和备份文件 location ~ /\.(?!well-known) { deny all; } location ~* \.(bak|conf|sql|sh|tar|zip)$ { deny all; }这几个点覆盖了 CTF 里八成的送分题server_tokens off 隐藏版本号、autoindex off 防止目录浏览、拒绝访问点开头隐藏文件、禁止下载配置和备份文件。实际生产环境还可以再加控制管理后台访问来源location /admin/ { allow 192.168.1.0/24; deny all; }配置 CSP 响应头add_header Content-Security-Policy default-src self;设置 X-Frame-Options: DENY 防止点击劫持定期用 logrotate 管理日志大小Ubuntu 默认已配置不要在前端项目目录里放 .env 或 config 文件这类泄露事故我见过太多次安全加固不是让你一次性做到极致而是每次上线前至少把这几个基础项做好把最常见的攻击路径堵住。等业务稳定后再考虑 WAF、证书自动化、访问审计这些进阶项。8.4 我维护 Nginx 几年攒下的几个习惯最后分享几个我一直在坚持的习惯。每次改配置之前先备份一份sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak。改坏了随时能回滚这个动作花不了几秒钟但能救命。所有站点配置按域名拆成独立文件放在 conf.d 或 sites-available 下文件名用项目名命名三个月后再回来修改也能一眼认出哪个文件是哪个项目。配置里面除了注释还要写上日期和用途。很多一线文档缺失的配置过半年自己和同事都看不懂当初为什么这么写。比如那个 proxy_pass 带不带斜杠的细节我就在注释里写明这里刻意不带斜杠后端路由包含 /api 前缀避免后来者好心改坏。还有最后一个每完成一个配置变更立刻在浏览器、curl 和 error.log 三个维度各确认一遍。浏览器看用户体验curl 看响应状态error.log 看底层报错。这三者对上了才算真正完成。这套三板斧帮我躲过了无数个看起来没事、实际在报错的隐藏坑希望你也能用起来。