Nginx生产级配置实战:从反向代理到性能优化
发布时间:2026/9/10 15:22:40 作者:尧图编辑部 阅读量:1,286

1. 别急着敲命令先想清楚生产环境到底需要什么先说个扎心的事实很多人在本机用brew install nginx或者apt install nginx装完写个location /指向静态文件看到页面能打开就觉得自己会 Nginx了。可真到了生产环境第一个晚上就会被各种诡异问题叫醒——连接数被打满、日志把磁盘撑爆、反向代理偶尔 502、配置改完一 reload 居然报错。我早年接过一个外包项目客户那边是台 2C4G 的小服务器跑的 PHP 业务。前任开发把 Nginx 配置写得跟散文似的一个 server 块里塞了十来个 location正则和前缀混着用proxy_pass还带不带斜杠全看心情。结果上线第二天数据库连接池被拖垮页面转圈转到超时。我上去一看Nginx 的error.log里全是 upstream 超时而配置里压根没设proxy_connect_timeout。所以这篇不是教你怎么启动 Nginx而是把从零写一份能扛事的生产级配置这件事拆开揉碎。你将会看到核心配置文件到底该怎么组织、反向代理和负载均衡的细节坑在哪、日志和性能参数怎么调才不回火上浇油、以及上线前必须做的那一轮自检长什么样。内容基于我在多个实际项目里的配置经验不保证适合每一台机器但保证每个参数我都会说清楚为什么要这样设。适合人群刚接触 Nginx 想直接上生产的新手、被各种半吊子教程坑过的运维、以及想把手头配置整理成规范模板的后端开发。看完你至少能写出一个结构清晰、参数有依据、出问题知道去哪查的 Nginx 配置。2. 主配置文件的组织方式把能跑和能维护分开2.1 默认配置结构的问题在哪里Nginx 安装完以后的默认nginx.conf大差不差全局块、events 块、http 块三层结构。新手最容易犯的错是把所有 server、所有 location 全部塞进这个文件里几千行下来每次改配置都得先 CtrlF 找半天。我见过最离谱的一份配置nginx.conf里躺了几十个include注释掉的旧路径还有三份互相矛盾的upstream定义。问就是怕删了出问题。这种配置本身就是定时炸弹。生产级的组织方式应该是这样的/etc/nginx/ ├── nginx.conf # 主配置全局参数、事件模型、http 骨架 ├── conf.d/ # 通用配置gzip、缓存、安全头等 ├── sites-available/ # 每个站点一个文件 ├── sites-enabled/ # 软链接指向要启用的站点 └── stream.d/ # 四层代理TCP/UDP配置主配置只负责搭骨架具体站点全部独立成文件。这样新增一个站点就是新建一个文件再做个软链回滚就是删软链完全不碰主配置。2.2 一个可以直接抄的主配置骨架下面这份主配置是我的常用起点参数含义我会逐个解释user www-data; worker_processes auto; worker_rlimit_nofile 65535; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 4096; use epoll; multi_accept on; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for $request_time $upstream_response_time; access_log /var/log/nginx/access.log main buffer32k flush5s; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; server_tokens off; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; }逐个说worker_processes auto让 Nginx 按 CPU 核心数自动派生 worker 进程。不用手动填数字升级机器以后自动适配。worker_rlimit_nofile 65535提高单个 worker 能打开的文件描述符上限。默认值往往只有 1024高并发下会报 too many open files。events块里的use epollLinux 下的高性能事件模型新版本 Nginx 会自动选但显式写出来更保险。multi_accept on一次 accept 尽量多接收连接减少进程唤醒次数。log_format main我额外加了$request_time和$upstream_response_time排查慢请求时这两个字段是命根子。access_log ... buffer32k flush5s日志先写内存缓冲32k 满了或者 5 秒到了再落盘。别小看这个高并发下能明显降低磁盘 IO。sendfile、tcp_nopush、tcp_nodelay静态文件传输优化三件套。sendfile让内核直接搬运文件不经过用户态tcp_nopush配合sendfile批量发送tcp_nodelay禁用 Nagle 算法降低小包延迟。server_tokens off隐藏版本号别把 Nginx 版本直接暴露给外部扫描器。2.3 站点文件的结构规范每个站点文件内部我习惯按顺序排四段监听与域名、SSL 配置、location 路由、日志与安全头。像这样server { listen 80; listen [::]:80; server_name example.com www.example.com; # 强制跳转 HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com www.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 1d; access_log /var/log/nginx/example.com.access.log main; error_log /var/log/nginx/example.com.error.log warn; root /var/www/example.com; index index.html index.htm; location / { try_files $uri $uri/ 404; } location ^~ /static/ { expires 30d; add_header Cache-Control public, no-transform; } location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|webp)$ { expires 7d; add_header Cache-Control public; } # 安全响应头 add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; add_header Referrer-Policy strict-origin-when-cross-origin always; # 隐藏 Nginx 版本号 server_tokens off; }这套结构的好处是每个站点一个文件SSL、路由、日志各归各位。include的顺序也有讲究——conf.d里的通用配置先加载sites-enabled里的站点配置后加载后加载的可以覆盖前面的同名字段。3. 反向代理与负载均衡生产环境最常见的正确姿势3.1 反向代理到底代理了什么反向代理这个词听着高大上本质就一句话客户端请求先到 NginxNginx 按照规则转发给后端应用服务器再把响应拿回来交给客户端。客户端全程不知道后端服务器的真实地址这就是反向的含义——正向代理是替客户端访问外部反向代理是替后端挡在门口。生产级反向代理要考虑三件事转发什么、超时多久、失败怎么办。转发规则里最容易翻车的是proxy_pass带不带斜杠。这两者的区别我必须写清楚# 情况一proxy_pass 不带 URI没有路径部分 location /api/ { proxy_pass http://backend; } # 请求 /api/users - 转发到 http://backend/api/users # 请求 /api/v1/list - 转发到 http://backend/api/v1/list # 情况二proxy_pass 带 URI有路径部分 location /api/ { proxy_pass http://backend/; } # 请求 /api/users - 转发到 http://backend/users # 请求 /api/v1/list - 转发到 http://backend/v1/list带斜杠的proxy_pass会把 location 匹配到的前缀替换掉不带斜杠则是原样追加。这个区别一旦搞错后端收到的路径就会多一层或者少一层接口 404 你还不容易想到是这里的问题。3.2 一份成熟的反向代理配置模板下面是我在多个项目里实际用过的模板直接注释了每个参数的作用location /api/ { proxy_pass http://backend_servers; 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_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_connect_timeout 5s; proxy_send_timeout 60s; proxy_read_timeout 60s; proxy_buffering on; proxy_buffer_size 8k; proxy_buffers 8 8k; proxy_next_upstream error timeout http_502 http_503 http_504; }逐条解释关键参数proxy_http_version 1.1默认是 1.0但 HTTP/1.0 不支持 keepalive 连接复用后端压力会大不少。必须显式设为 1.1。proxy_set_header Host $host把原始请求的 Host 头透传给后端。很多框架靠 Host 做路由不传会出奇怪问题。X-Forwarded-For追加客户端真实 IP。注意用的是$proxy_add_x_forwarded_for它会在已有 XFF 链上追加而不是覆盖。Upgrade / Connection upgradeWebSocket 支持的关键。没有这两行WebSocket 握手大概率失败。proxy_connect_timeout 5sNginx 与后端建立 TCP 连接的超时时间。设太短后端瞬间高负载就大量 504设太长后端假死时请求全堆在 Nginx 上。proxy_read_timeout 60s两次读操作之间的超时不是总超时。如果后端有长轮询或者慢接口需要调大。proxy_next_upstream当后端返回 502/503/504 或连接错误时自动尝试下一个后端。这是负载均衡高可用的关键。3.3 负载均衡的几种策略怎么选upstream模块支持三种主流策略# 轮询默认请求轮流分发 upstream backend_servers { server 127.0.0.1:8080; server 127.0.0.1:8081; } # 加权轮询按权重分配请求比例 upstream backend_servers { server 127.0.0.1:8080 weight3; server 127.0.0.1:8081 weight1; } # ip_hash同一客户端 IP 固定打到同一后端适合有状态服务 upstream backend_servers { ip_hash; server 127.0.0.1:8080; server 127.0.0.1:8081; }选型逻辑很简单后端服务无状态Session 存在 Redis 里用默认轮询或加权轮询最大化资源利用率。后端服务有状态Session 存在本地内存用ip_hash至少保证同一个用户尽量打到同一台机器。追求最高可用性轮询 max_fails3 fail_timeout30sproxy_next_upstream一台挂了自动摘除30 秒后自动恢复探测。max_fails和fail_timeout是生产环境必须配的参数upstream backend_servers { server 127.0.0.1:8080 max_fails3 fail_timeout30s; server 127.0.0.1:8081 max_fails3 fail_timeout30s; keepalive 32; }keepalive 32是 Nginx 与后端保持的空闲连接数。配了以后Nginx 不会每次请求都重新建立 TCP 连接而是复用空闲连接池里的连接。实测在高并发场景下QPS 能提升 20% 到 40%延迟也明显下降。4. 静态资源服务与缓存策略让 Nginx 做它最擅长的事4.1 动静分离到底在分什么生产环境里纯静态资源图片、CSS、JS、字体不应该经过后端应用服务器。原因很直白这类资源的处理逻辑是固定的——读文件、设置响应头、返回内容。让 Java/PHP/Node 去干这事等于用卡车运一张纸纯属浪费。动静分离的本质是把请求按照资源类型分流静态请求由 Nginx 直接处理走文件系统动态请求才转发给后端。以 Vue/React 打包后的前端项目为例部署 Nginx 时通常这样组织server { listen 443 ssl http2; server_name example.com; root /var/www/example.com/dist; index index.html; # 所有前端路由都交给 index.htmlVue Router history 模式必须 location / { try_files $uri $uri/ /index.html; } # 带 hash 的资源文件一年缓存 location /assets/ { expires 1y; add_header Cache-Control public, immutable; } }重点关注try_files $uri $uri/ /index.html;这一行。它的逻辑是先看文件系统里有没有对应文件没有就看对应目录还没有就把请求重写到/index.html。为什么必须这样因为 Vue Router 的 history 模式用的是 HTML5 History API前端路由/user/123在服务器上根本没有这个物理文件不重写到index.html就会 404。而 hash 模式URL 带#不存在这个问题但 URL 不好看也不利于 SEO。4.2 缓存策略怎么定才合理静态资源的缓存策略核心就一个原则文件名含版本号的缓存期拉长文件名不含版本号的缓存期缩短。像 Webpack 构建出的app.8f3k2d.js这类带 hash 的文件内容变了文件名就变永远不会命中旧缓存放心给一年location /assets/ { expires 1y; add_header Cache-Control public, immutable; }像index.html这种入口文件不能缓存太久。否则发布新版本后用户浏览器里的还是旧 HTML引用的还是旧 JS白屏问题就是这么来的location /index.html { expires -1; add_header Cache-Control no-cache, no-store, must-revalidate; }这里expires -1表示立即过期no-store是禁止任何缓存must-revalidate是必须回源验证。生产环境里这个组合是我验证过最稳的。4.3 gzip 压缩性价比最高的优化压缩是所有性能优化里投入产出比最高的一项。只需几行配置传输体积能减少 60% 以上gzip on; gzip_comp_level 5; gzip_min_length 1k; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xmlrss image/svgxml; gzip_vary on; gzip_disable msie6;几个参数的经验值gzip_comp_level 5压缩级别。1 到 9 之间5 是平衡点。调到 9 压缩率提升有限但 CPU 开销翻倍。对低配服务器4 更稳妥。gzip_min_length 1k小于 1KB 的文件不压。压缩小文件反而会变大因为还有压缩头开销。gzip_types只压文本类。图片、PDF、视频这些已经是压缩格式的再压只会浪费 CPU而且image/jpeg加进去也没效果。检查压缩是否生效用 curl 看响应头curl -H Accept-Encoding: gzip -I https://example.com/app.js如果响应头里有Content-Encoding: gzip说明生效。没有的话先查gzip_types里有没有包含该 MIME 类型。4.4 静态文件的高性能传输参数再补一组静态资源场景下的关键参数sendfile on; tcp_nopush on; open_file_cache max1000 inactive20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on;open_file_cache这组参数经常被忽略但作用很大。它让 Nginx 在内存里缓存已打开文件的元数据文件大小、修改时间等热点文件不用每次请求都去查磁盘。max1000是缓存上限超过后按 LRU 淘汰inactive20s是 20 秒内没被访问就淘汰min_uses2是访问至少 2 次才进缓存。注意在open_file_cache生效的情况下修改文件内容但文件名不变时Nginx 可能因为缓存了旧元数据而返回旧内容。发布时如果遇到明明更新了文件浏览器还是旧的除了浏览器缓存也要排查是不是 Nginx 的open_file_cache在捣鬼。一般等open_file_cache_valid的时间过去就会自动刷新实在着急就nginx -s reload。5. 日志切割与监控别等问题发生了才想起去看日志5.1 为什么 Nginx 自带日志切割不够用Nginx 默认的行为是把所有日志写进同一个文件永不切割。跑一个月access.log轻松上 GB。带来的问题很具体单文件太大grep查一次要半天磁盘被日志占满Nginx 直接拒绝写入引发连锁故障日志轮转需要重命名文件但 Nginx 还在往旧文件的 inode 里写处理不好会丢日志。logrotate是 Linux 上标准的日志轮转工具。配置放在/etc/logrotate.d/nginx/var/log/nginx/*.log { daily missingok rotate 14 compress delaycompress notifempty create 0640 www-data adm sharedscripts postrotate if [ -f /var/run/nginx.pid ]; then kill -USR1 cat /var/run/nginx.pid fi endscript }解释一下daily每天切一次。rotate 14保留 14 份也就是两周。按你的磁盘空间和日志量调整。compress切割后的旧日志用 gzip 压缩。压缩率很高能省 90% 空间。delaycompress昨天的日志先不压今天再压。这是为了避免 Nginx 的postrotate信号和压缩动作冲突导致丢日志。kill -USR1USR1 信号是让 Nginx 重新打开日志文件。新日志写入新文件旧文件安心归档。注意不能用restart重启会中断连接USR1 则完全平滑。配置完后可以用logrotate -d /etc/logrotate.d/nginx做一次 dry-run确认路径和权限没问题。5.2 access log 里到底该记录哪些字段很多人用默认的combined格式一用就是好几年。但排查线上问题的时候你就会发现combined缺少两个关键信息请求耗时和后端响应耗时。所以我在前面的主配置里加了自定义格式log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for $request_time $upstream_response_time;补充说明几个变量$request_time从 Nginx 收到请求到返回响应的总耗时单位秒精确到毫秒。$upstream_response_timeNginx 转发给后端后后端处理并从接收第一个字节到发送完最后一个字节的耗时。这两个值一对比你就能快速定位慢在哪一层——$request_time接近$upstream_response_time问题在后端前者远大于后者问题在 Nginx 层或者网络层。分析耗时分布一条 awk 命令就够了awk {if ($NF 3) print} /var/log/nginx/access.log | tail -100这条命令把耗时超过 3 秒的请求捞出来看前提是耗时字段在最后一列。再配合$upstream_response_time就能判断是某个上游节点拖慢还是整体变慢。5.3 监控 Nginx 状态页Nginx 自带一个状态页模块默认编译时通常已启用但配置默认是关掉的。生产环境可以单独开一个端口给内网监控用server { listen 127.0.0.1:8080; server_name _; location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; } }访问http://127.0.0.1:8080/nginx_status会输出Active connections: 291 server accepts handled requests 16630948 16630948 31070465 Reading: 6 Writing: 179 Waiting: 106字段含义Active connections当前活动连接数。accepts已接受的连接总数。handled已处理的连接总数。正常情况下应和accepts相等如果差距持续拉大说明有连接被丢弃多半是worker_connections不够。Reading正在读取请求头的连接数。Writing正在写响应的连接数。Waiting空闲 keepalive 连接数。把这几个数字喂给 Prometheus 或者自家监控脚本就能在连接数暴涨前收到告警。6. HTTPS 配置与浏览器兼容TLS 版本、证书链与常见 5026.1 TLS 配置的合理基线2024 年再谈 HTTPSHTTP/2 已经是标配TLSv1.0/1.1该彻底抛弃了。一份稳妥的 TLS 配置长这样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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off;重点说三个容易被忽视的ssl_session_cache shared:SSL:10mTLS 握手最耗时的是密钥交换。开启 session cache 后同一条 TCP 连接上的多个 HTTPS 请求HTTP/2 尤其明显可以复用会话密钥不用重新握手。10m大约能存 10 万个 session足够。ssl_session_timeout 1d会话缓存有效期。太长有安全风险太短缓存没意义。一天是常见平衡点。ssl_session_tickets offRFC 5077 的 Ticket 机制默认是关的。开着的话虽然也能复用会话但实现细节容易出安全问题不如关掉用服务端缓存。证书链还有一个高频坑很多人只配置了站点证书没配中间证书。Nginx 报错unable to get local issuer certificate浏览器提示不安全。用 Lets Encrypt 的 Certbot 时填写fullchain.pem就能避免这个问题它包含了站点证书和中间证书的完整链。自己合成的证书记得把中间证书追加到站点证书后面再一起配置。6.2 HTTP/2 和 HTTP/3 的取舍listen 443 ssl http2;是当前主流配置HTTP/2 的多路复用对前端性能提升明显。但 HTTP/2 有个头部阻塞问题HTTP/3 用 UDP 解决了不过 HTTP/3 依赖 QUIC 协议Nginx 需要额外编译--with-http_v3_module且 OpenSSL 版本要求较高。我的建议如果 Nginx 是官方源安装的稳定版先把 HTTP/2 跑好HTTP/3 不是必须。真要上 HTTP/3注意防火墙必须放行 UDP 443很多云厂商的安全组默认只开 TCP这是最常见的坑。6.3 后端返回 502/504 的排查链路502 和 504 是反向代理场景下最让人头疼的错误我把排查链路写在这里照着走就行第一步确认后端进程状态curl -I http://127.0.0.1:8080/health ps aux | grep java # 或 node/python/php-fpm第二步看 Nginx error.logtail -100 /var/log/nginx/error.log常见错误信息与对应原因错误信息原因解决方向connect() failed (111: Connection refused)后端端口没监听或服务挂了启动后端服务connect() failed (110: Connection timed out)后端 IP/端口不通或被防火墙拦截检查网络、安全组upstream timed out后端处理超时proxy_read_timeout调大超时或优化后端性能no live upstreams while connectingupstream 里所有节点都被标记为不可用检查max_fails/fail_timeout确认后端恢复后自动摘除是否到期104: Connection reset by peer后端进程崩溃或主动断开连接看后端日志检查内存/连接数第三步确认 Nginx 配置语法nginx -t第四步定位是否是 keepalive 引起的问题如果错误日志里有upstream prematurely closed connection while reading response header from upstream大概率是 Nginx 与后端之间的 keepalive 配置不匹配。有的后端框架尤其是某些 Java 版本并不支持 HTTP/1.1 的长连接此时在location里临时加一句proxy_set_header Connection ;强制关闭与后端的 keepalive先让服务恢复再排查后端为什么不支持。7. 实战演练从零部署一个 Vue3 项目到生产7.1 环境准备假设你有一台干净的 CentOS 8 或 Ubuntu 22.04 服务器域名example.com已解析到服务器 IP。下面实际操作一遍全流程。先装 NginxUbuntu/Debiansudo apt update sudo apt install -y nginxCentOS/RHEL 系sudo yum install -y nginx启动并设置开机自启sudo systemctl start nginx sudo systemctl enable nginx检查版本和编译参数nginx -v nginx -Vnginx -V输出的--with-http_ssl_module、--with-http_v2_module决定了你能不能直接用 SSL 和 HTTP/2。官方源编译的版本一般都带但如果你用的是某些精简版系统可能缺模块。7.2 构建前端项目在本地开发机执行npm install npm run build构建产物通常在dist目录。把dist目录整个传到服务器scp -r dist useryour-server:/tmp/example-dist到服务器上移动到站点目录并设置权限sudo mkdir -p /var/www/example.com sudo mv /tmp/example-dist/* /var/www/example.com/ sudo chown -R www-data:www-data /var/www/example.com sudo chmod -R 755 /var/www/example.comwww-data是 Nginx 的默认运行用户。权限不设对Nginx 会因为读不到文件返回 403 Forbidden这是新手最常见的错误之一。7.3 配置站点文件并启用创建站点配置sudo vim /etc/nginx/sites-available/example.com.conf填入前面给过的配置注意把server_name改成你自己的域名。需要同时处理 HTTP 和 HTTPS 的话先申请证书再写 443 的配置。启用站点sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/如果系统使用 Debian/Ubuntu 的sites-enabled体系这一步很关键。CentOS 默认没有这个目录结构需要自己在nginx.conf里加include /etc/nginx/sites-enabled/*;或者干脆把站点文件放conf.d下。检查语法并重载sudo nginx -t sudo nginx -s reload7.4 用 Certbot 配置 HTTPS先安装 Certbotsudo apt install -y certbot python3-certbot-nginx执行sudo certbot --nginx -d example.com -d www.example.comCertbot 会自动修改你的 Nginx 配置加入 SSL 证书路径和跳转规则。它配置完后会自动设置自动续期的 cron 任务。检查续期是否正常sudo certbot renew --dry-run这条命令很关键不少人的证书在 90 天后突然失效就是因为续期任务异常而没跑 dry-run 验证过。7.5 验证部署效果# 检查 Nginx 状态 systemctl status nginx # 用 curl 检查 HTTPS 响应 curl -I https://example.com # 检查响应头里的 Server 字段 curl -sI https://example.com | grep -i server如果Server返回的是nginx/1.18.0说明server_tokens off没生效检查是否写在了正确位置应该在 http 块或 server 块内。再检查静态资源是否命中缓存curl -sI https://example.com/assets/index.8f3k2d.js | grep -i cache预期看到Cache-Control: public, max-age31536000之类的响应头。8. 上线前的自检清单照着做完再睡个安稳觉这份清单是我每次上线前必过的科目直接照抄就行8.1 配置层面[ ]nginx -t无报错[ ]error_log路径存在且有写权限[ ]access_log已配置轮转logrotate -ddry-run 通过[ ]server_tokens off已生效[ ] HTTPS 证书有效期在 30 天以上certbot certificates查看[ ] 80 端口已跳转 443[ ]add_header安全头已配置8.2 性能层面[ ]worker_processes auto已设置[ ]worker_connections按当前连接数评估过[ ]gzip已开启且 types 正确[ ] 静态资源缓存策略已明确划分[ ] 反向代理超时参数与后端接口耗时匹配8.3 安全层面[ ] 只开放了需要的端口80/443[ ] 状态页仅内网可访问[ ] 日志目录权限收紧不要直接给 777[ ] 隐藏了 Nginx 版本号8.4 验证层面[ ] 首页 HTTP 200[ ] 静态资源返回 200 且命中缓存[ ] 后端接口通过域名访问正常[ ] 404/500 错误页没有泄露框架信息如 Spring Boot 默认错误页最后一条提醒上线后前 24 小时tail -f /var/log/nginx/error.log挂一个窗口。很多问题不是配置写错了而是流量场景和本地测试不一样才暴露的。比如某个接口上传大文件超过了client_max_body_size默认的 1MB返回 413 错误这类问题不跑真实流量基本测不出来。9. 一些我用真金白银换来的经验写到最后分享几条没法从官方文档里直接学到的经验都是踩过的坑换来的。关于reload和restart改完配置永远用nginx -s reload不要用systemctl restart nginx。reload是平滑重载Nginx 会先检查新配置语法然后在 worker 进程处理完当前请求后优雅替换。restart会直接杀进程重建正在进行的请求全部中断。生产环境一次restart可能断掉几千个用户的长连接。关于配置变更的版本管理我建议把整个/etc/nginx目录纳入 Git 管理。每次改动前先git commit改动后nginx -t通过再git commit。回滚就是git checkout加 reload。这个习惯救过我很多次——有一次凌晨改配置把proxy_pass写错了两分钟就回滚了客户完全没感觉到。关于client_max_body_size默认 1MB。如果你的业务涉及文件上传记得在location里加上client_max_body_size 50m;不加的话用户一传大文件就 413而且 Nginx 的error.log里只会出现client intended to send too large body排查起来挺隐蔽。关于日志时间不一致Nginx 日志默认按服务器本地时间记录。如果服务器时区不是东八区排查问题的时候对不上业务时间线会很痛苦。在http块加一行log_format ...;同时把系统时区设为Asia/Shanghai或者至少明确知道你的服务器是什么时区否则日志分析会一头雾水。关于压测上线前用ab或者wrk做一轮简单压测不是要压出极限 QPS而是确认没有明显瓶颈。一条简单的命令wrk -t4 -c100 -d30s https://example.com/如果错误率高于 0.1%先把问题解决再谈上线。Nginx 这份东西没有太多玄学核心就是理解每个参数背后的逻辑然后按照自己的业务场景去做取舍。把这篇里的配置模板拿过去改一改跑起来以后再看日志调整比盲目套用网上花里胡哨的性能优化 100 条靠谱得多。配置这东西适合你的才是最好的。