
提到nginx网站服务很多人第一反应就是“一个网页服务器”把HTML丢进去访问一下完事。但真到线上跑一跑你会发现nginx在这个链条里远不止“跑静态页面”这么简单它承担的是网站流量的入口、分发、缓存和安全屏障。这篇文章我会顺着一次完整网站上线流程把nginx从安装、配置、调优、平滑升级到故障排查完整拆一遍重点讲清楚每个配置背后的原理而不是丢给你一堆可以抄但不知道含义的代码块。适合刚接触nginx的运维新人也适合写后端但对web层不太熟的开发同学。1. 先建立对nginx网站服务的整体认知1.1 三个核心身份静态站点、反向代理、负载均衡我日常用nginx解决最多的问题其实可以归成三类。第一类是静态资源站点比如前端打包出来的dist目录、图片服务、PDF下载站nginx直接读磁盘文件返回几个毫秒就能搞定配合系统页缓存性能非常夸张。第二类是反向代理把来自公网的请求转发给内网真实的业务服务比如Java的Tomcat、Python的Gunicorn、Node.js的端口服务客户端只看到nginx不直接触达业务端口这层间接性本身就带来安全收益业务节点也可以随便换端口而不影响外部访问。第三类是负载均衡通过upstream指令把同一组后端服务做分发可以加权重、做健康检查、自动剔除故障节点这是中大型网站最常用的能力。这三个身份不是互斥的经常叠加在同一个nginx实例里。比如一个站点可以这样拆静态资源请求直接由nginx返回接口请求代理到后端集群图片接口再做一轮负载均衡。理解这一点很重要因为后续看配置时你会发现很多指令不冲突它们只是在不同层级上处理不同的流量。1.2 事件驱动模型高并发能力的底层来源nginx能扛高并发靠的不是堆机器而是进程模型设计。它启动后会有一个master进程和若干个worker进程。master进程负责读取配置、管理worker生命周期、接收运维发来的信号真正处理请求的是worker进程。每个worker进程内部使用epoll这样的事件通知机制同时监听成千上万个socket连接某个连接可读或可写时才去处理没有事件发生就休眠几乎不占CPU。用餐厅例子类比传统Apache的MPM多线程模式相当于每来一桌客人就安排一个专职服务员客人少没问题客人一多服务员人数堆不上去餐厅就崩溃。nginx的做法是一个服务员同时看几十桌哪桌举手了才过去没人叫就待着不动。这就是“事件驱动、异步非阻塞”。当然这也要求业务代码本身不能阻塞如果nginx反代的后端响应很慢worker还是会一直在等待上游数据这也是为什么nginx适合做网关层、不适合跑大量耗时业务逻辑的原因。1.3 和其它Web服务的选型对照新手经常把nginx和Apache、Tomcat放一起比较其实它们不是完全同一层的东西。我列一个简化的对照表方便你快速定位服务主要定位擅长场景略显吃力的场景nginxWeb服务器/反向代理静态文件、高并发入口、反向代理、负载均衡动态业务逻辑、长时间连接的复杂会话管理ApacheWeb服务器模块丰富、老牌生态、动态内容处理高并发下的内存和CPU开销较大TomcatJava应用容器Servlet/JSP应用、Java后端部署静态资源吞吐、千级以上并发接入CaddyWeb服务器/反向代理自动HTTPS、配置简洁生态和性能极致优化经验少、插件依赖多我的选型建议比较实在动静分离架构里前端静态文件、代理转发、限流、证书终止这些都交给nginx动态接口丢给后面的应用容器去跑。是否用Caddy看团队习惯自动HTTPS确实香但生产环境里nginx的坑我踩得多周边资料和排查经验也最多我自己仍然首选nginx。2. 安装部署两条路线怎么选2.1 包管理器安装适合快速验证和低维护场景Linux发行版的包管理器默认就带nginxCentOS系用yum install nginxDebian/Ubuntu系用apt install nginx装完自动注册systemd服务systemctl start nginx就能跑起来。好处是依赖自动处理、升级方便、配置目录规范坏处也很明显发行版仓库里的版本通常偏旧比如一些云服务器的系统源还停留在老版本缺新模块、缺少安全补丁而这种“装着能用”的错觉最危险。如果你只是本地写个小项目、验证配置语法用包管理器没问题。但生产环境我建议至少先确认版本nginx -v看一下然后对比一下当前社区是否发布了安全更新。另外即使使用包管理器也建议优先配置国内的软件源或镜像站加速下载装出来更稳也方便后续yum update统一升级。2.2 编译安装生产环境我推荐的做法编译安装最大的价值不是“显得专业”而是可以精确控制版本和模块。比如你需要HTTP/2、需要流媒体代理、需要http_stub_status模块看连接数这些功能如果发行版里没编译进去后面很被动。我常用的configure参数大概是这样的./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-http_stub_status_module \ --with-http_gzip_static_module \ --with-stream \ --with-stream_ssl_module \ --with-pcre \ --with-ld-opt-ljemalloc解释一下关键点。--prefix指定安装目录装完所有文件都在/usr/local/nginx下面卸载时直接删目录即可不会污染系统。--with-http_ssl_module是HTTPS必须--with-http_v2_module对应HTTP/2--with-stream用来做四层TCP/UDP代理--with-http_stub_status_module提供一个/nginx_status页面可以实时看活跃连接数。依赖方面configure阶段需要gcc、make、pcre正则解析、zlibgzip压缩、opensslSSL/TLS一般这样装yum install -y gcc make pcre-devel zlib-devel openssl-devel输入make -j4并行编译执行make install然后/usr/local/nginx/sbin/nginx -V验证编译参数。编译参数必须记下来后期平滑升级新版本时要能复刻这一套参数漏一个模块后面都要返工。2.3 安装后立刻要做的三件事装完不要急着把配置改得花里胡哨先把三点确认清楚。第一nginx -V看一下实际生效的编译参数和版本号防止装错包。第二nginx -t检查默认配置语法注意nginx -t只是校验语法不会启动服务任何配置改动后都应该先跑这一条。第三启动后用ss -lntp看80端口是否真的在监听再curl -I http://127.0.0.1确认返回头。很多线上事故都倒在一个基础问题上进程启动了但外部访问不通。这时要马上检查云安全组策略、本机firewalld/iptables以及SELinux是否拦截。我遇到过不止一次所有配置都对最后发现是SELinux enforcing状态导致nginx没有权限绑定端口。快速执行setenforce 0验证如果确实是SELinux问题再去写正确的策略而不是图省事永久关闭。3. 核心配置逐段拆解看懂再改3.1 配置文件的主干结构nginx.conf看起来指令很多其实结构非常规律。最外层是main上下文main里只能有全局指令比如worker_processes、user。往下是events块配置事件模型再往下是http块几乎所有网站相关指令都写在http里。http块里可以写多个server每个server对应一个虚拟主机、一个域名或一组端口的监听server里面再写多个location按URI路由到不同处理逻辑。这个概念和编程语言里的作用域很像内层指令如果和外层同名内层优先有些指令只允许出现在特定上下文比如worker_processes写在http里就会报错。我建议把配置拆成多个小文件管理nginx.conf主文件只保留全局和include站点配置放到/usr/local/nginx/conf/conf.d/下面每个域名一个文件用include conf.d/*.conf接进来。这样几百个站点也能快速定位问题而不是在一个几千行的文件里反复找。3.2 location匹配优先级一处配置错全站乱location是整个nginx配置里最容易被误解的部分。它不是按配置文件书写顺序一个一个试而是有一套严格的优先级规则。精确匹配的优先级最高写法是location /path其次是^~开头的前缀匹配一旦命中就不再看正则再往下是正则表达式匹配按书写顺序执行匹配到第一个就停最后才是普通前缀匹配取最长匹配的那个。举个真实例子如果同时存在location /logo.png { access_log off; } location ^~ /static/ { root /data/static; } location ~* \.(png|jpg|gif)$ { expires 30d; } location /static/img/ { root /data/www; }请求/logo.png会精确命中第一段请求/static/img/banner.png会命中^~ /static/直接返回/data/static/static/img/banner.png而不会进入后面的正则。要特别注意root和alias的区别root会把完整URI拼在root路径后面alias会把location前缀替换掉。这一对指令几乎每周都能在社区看到有人在问“为什么404”十有八九是root路径下根本没有对应文件。3.3 静态网站上线一套最基础但完整的配置假设前端项目构建后的目录在/data/www域名是www.example.com最简单的server配置长这样server { listen 80; server_name www.example.com example.com; root /data/www; index index.html; charset utf-8; location / { try_files $uri $uri/ /index.html; } location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2)$ { expires 30d; access_log off; add_header Cache-Control public; } }listen和server_name决定这个server块监听什么、匹配哪些域名。root指定网站根目录index指定目录访问时默认找哪个文件。try_files $uri $uri/ /index.html这行是给前端history路由模式用的访问/order/2024时服务器上并没有这个物理路径就回退到/index.html由前端路由接管这样刷新页面不会404。静态资源那段location用正则匹配常见图片、字体后缀设置30天缓存同时关掉这些资源的访问日志能减轻大量无意义日志写入压力。3.4 反向代理与负载均衡把请求正确送到后端反向代理的核心指令是proxy_pass但想在生产环境用顺手必须配合upstream和其它头信息设置。下面这个配置比较典型upstream backend_java { server 10.0.0.11:8080 weight3 max_fails2 fail_timeout10s; server 10.0.0.12:8080 weight1 max_fails2 fail_timeout10s; keepalive 32; } server { listen 80; server_name api.example.com; location /api/ { proxy_pass http://backend_java; 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; } }upstream定义一组后端节点weight表示权重10.0.0.11处理能力更强就多分一点流量max_fails和fail_timeout表示10秒内失败2次就把节点摘掉这是最朴素但有效的健康检查。proxy_set_header这几个头信息很关键如果不把$host传过去后端拿到的是内网IP而不是真实域名如果不传X-Forwarded-For后端的访问日志里所有请求来源都变成nginx地址排查刷单和攻击时两眼一黑。keepalive 32是让nginx与后端之间复用长连接否则每次请求都要重新建立TCP连接在接口频繁时开销非常大。负载均衡的默认策略是round-robin按权重轮询。如果业务有会话状态比如单机登录session存在本地需要同一个用户固定打到同一台后端可以用ip_hash或least_conn。ip_hash按客户端IP做哈希简单但不够均匀更精细的是用sticky模块按Cookie粘连。无状态后端优先轮询这能最大程度利用机器资源。3.5 缓存与动静分离减少后端压力最有效的一招很多后端服务慢不是代码不行而是大量静态请求也打到了Tomcat上。动静分离的意义就是把图片、CSS、JS这些请求直接拦在nginx层只有真正的接口请求才进后端。除了前面的静态locationnginx还可以做代理缓存把后端接口的响应缓存起来。配置分两步先定义缓存区再在location里使用proxy_cache_path /data/nginx_cache levels1:2 keys_zoneapi_cache:10m max_size10g inactive60m use_temp_pathoff; server { location /api/list { proxy_pass http://backend_java; proxy_cache api_cache; proxy_cache_key $scheme$request_method$host$request_uri; proxy_cache_valid 200 5m; proxy_cache_valid 404 1m; } }proxy_cache_path指定缓存目录、目录分层方式、共享内存区大小和上限。这里levels1:2表示缓存文件分两级子目录避免一个目录塞几十万个文件文件系统性能会严重下降。proxy_cache_key决定用什么做缓存的标识通常就是请求的完整URI。proxy_cache_valid按响应码分别设置过期时间200状态缓存5分钟前端在5分钟内请求同一个列表接口时根本不进后端。这种配置对读多写少的接口效果立竿见影但对实时性要求高的接口要慎用必要时用cache-control响应头动态控制。4. 高并发场景下的性能与安全调优4.1 进程数、连接数和文件句柄很多教程一上来就让你把worker_processes改成8、worker_connections改成65535实际上要看机器配置和业务形态。worker_processes最稳妥的设置是autonginx会自动按CPU核心数启动worker或者设置成与CPU逻辑核数一致。worker数超过CPU核数并不会带来吞吐提升反而会因为进程频繁切换增加开销。worker_processes auto; worker_rlimit_nofile 65535; events { use epoll; worker_connections 4096; }worker_rlimit_nofile决定每个worker进程最多能打开多少文件描述符。Linux默认的1024甚至2048对nginx来说太低一个连接对应一个fd并发1000就触及上限。worker_connections指每个worker支持的最大连接数理论上nginx最大并发连接数约等于worker_processes乘worker_connections但还要考虑内存。比如4个worker乘以4096就是16384个并发连接这个数字对绝大多数中小网站已经足够。一个重要认知设置太高不一定更好每个连接都会占内存Linux的socket缓冲区、请求头缓冲区都是真金白银的RAM极限值设计要按业务估算。另外修改了worker_rlimit_nofile后如果没生效检查系统级限制/etc/security/limits.confnginx进程的ulimit可能被systemd或启动脚本覆盖。4.2 请求体验相关参数keepalive、gzip、sendfile这些参数看似零散但组合起来对用户体感影响很大。sendfile on直接调用内核的sendfile系统调用把文件从磁盘发到socket不经用户态复制这就是所谓的零拷贝对静态文件吞吐提升非常明显。配合tcp_nopush on数据包攒够了再一次性发送减少小包数量tcp_nodelay on则是针对keepalive长连接禁用Nagle算法让交互类请求延迟更低。gzip是Web老生常谈但依然好用对HTML、CSS、JS的压缩通常能减少70%以上体积gzip on; gzip_comp_level 5; gzip_min_length 1024; gzip_types text/plain text/css application/javascript application/json application/xml image/svgxml;gzip_comp_level并不是越大越好压缩率在5到6之后提升有限CPU开销却成倍增加。gzip_min_length是小于1KB的资源不压缩因为压缩头本身就有开销小文件压缩后反而更大。keepalive_timeout 65设置客户端长连接保持时间。对后端代理场景前面upstream里的keepalive是nginx到后端的连接这里的keepalive_timeout是nginx到客户端的连接两个概念别搞混。open_file_cache可以做文件句柄缓存减少重复文件请求时的open系统调用配合下一条指令使用open_file_cache max10000 inactive60s; open_file_cache_valid 30s; open_file_cache_min_uses 2;4.3 安全加固隐藏身份、限流、控制超时网站在公网环境不要默认nginx配置开箱即用。第一步隐藏版本号server_tokens off这样报错页面不会暴露nginx版本降低被针对性扫描的风险。第二步限制请求体大小client_max_body_size比如上传场景限制到20m防止有人拿大请求体灌满你的磁盘。第三步限制请求方法和超时。限流是更重要的安全能力。nginx内置的limit_req模块可以按IP做请求频率控制比如限制每个IP每秒1个请求允许突发3个limit_req_zone $binary_remote_addr zonereq_zone:10m rate1r/s; server { location /api/ { limit_req zonereq_zone burst3 nodelay; proxy_pass http://backend_java; } }$binary_remote_addr按客户端IP做key固定大小内存里可以存更多条目rate1r/s是平均速率burst3是允许瞬时超过3个请求进入队列nodelay表示这些排队请求不延时直接放行但之后要补回来。这个配置对爬虫和简单刷接口有很好的压制作用但注意如果网站被打了分布式攻击单IP限流拦不住需要在更上层配合云WAF或高防。其它安全习惯还包括用独立的低权限用户运行nginx日志目录定期隔离不用root启动生产进程还有定期升级版本因为nginx本身也出过缓冲区错误类漏洞及时跟进官方安全通告是底线。4.4 HTTPS配置与HTTP/2落地免费证书现在很好拿Lets Encrypt用certbot可以自动续期商业证书则适合需要兼容老设备和更高验证等级的场景。拿到PEM证书和私钥后配置一个443的serverserver { listen 443 ssl; http2 on; server_name 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; ssl_prefer_server_ciphers on; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; add_header Strict-Transport-Security max-age31536000; includeSubDomains always; }ssl_protocols只留TLSv1.2和TLSv1.3老旧的TLSv1.0/1.1已确认不安全不要因为兼容性而保留。ssl_session_cache是复用SSL握手参数否则每次新连接都要完整走一遍TLS握手非常耗时。证书文件权限建议设为600只允许root读取私钥泄露等于整个加密体系崩溃。再配一个80端口的server做301跳转把HTTP流量全部导到HTTPSserver { listen 80; server_name www.example.com; return 301 https://$host$request_uri; }最后用curl -I https://www.example.com检查返回头看到HTTP/2和301都能如预期工作这一层才算落地完成。5. 生产维护平滑升级与故障排查实录5.1 不中断服务的平滑升级原理与操作nginx的二进制升级不需要停机因为master进程可以无缝拉起一个新版本。整个机制建立在这些信号上HUP信号让nginx重新加载配置文件不会重启进程USR2信号让旧master启动新master进程新旧版本同时运行WINCH信号让旧master的worker进程优雅退出处理完当前请求后关闭QUIT信号让进程优雅退出。操作流程大概是这样的。假设旧版本安装在/usr/local/nginx先下载新版本源码用和编译旧版本时相同的configure参数重新编译但不要急着make install覆盖生产目录先执行make。然后备份现有二进制cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old把新编译出来的objs/nginx拷贝到安装目录覆盖sbin/nginx。接着用master进程的PID发信号kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid) kill -WINCH $(cat /usr/local/nginx/logs/nginx.pid)USR2之后会生成一个nginx.pid.oldbin旧master还在新master已经开始服务新连接。执行nginx -V确认新版本生效观察一段时间等旧worker自然退出后处理干净。如果新版本有问题想回滚就给旧master发送HUP信号重新加载旧二进制再对新的master发QUIT让它退出。我特别想提醒一件事绝对不要对运行中的nginx直接kill -9。这个命令杀不掉worker只会留下一堆僵死状态更糟的是把master干掉后所有worker变成孤儿进程连接接管机制全部失效后果是线上请求直接断裂。平滑升级不是花哨技巧而是生产环境的基本操作纪律。5.2 常见故障排查实录把实际操作中遇到的典型问题整理成速查表按现象定位原因现象最常见原因排查命令解决方向502 Bad Gateway后端进程没启动或超时ss -lntp | grep 8080curl后端地址启动后端调大proxy_connect_timeout504 Gateway Time-out后端接口响应太慢超过代理超时时间观察后端日志适当调大proxy_read_timeout从代码层排查慢接口403 Forbiddennginx运行用户没有目录读权限或不允许索引ls -l 网站目录tail access.log/error.log调整目录权限或归属或配置index指令404 Not Foundroot和alias用混或location匹配到了错误目录nginx -t检查语法curl -v看实际命中确认root基底路径检查location优先级upstream no live upstreams后端所有节点都被标记失败检查后端负载、健康检查状态确认后端恢复调整max_fails/fail_timeout请求头过大业务传了超大Cookie或自定义头error.log里能看到“client intended to send too large header”调大client_header_buffer_size和large_client_header_buffers日志把磁盘写满访问日志无清理策略df -hdu -sh logs/配logrotate或定时清理高频静态资源关闭access_log连接数到顶端口无法访问云安全组、firewalld或SELinux拦截systemctl status firewalldgetenforce放行端口写正确的SELinux策略排查故障有一个固定顺序我每次都会用先nginx -t排除配置问题再看error.log错误日志里往往直接写了原因和行号然后是nginx -V确认版本模块再顺着请求链路看后端状态。不要一上来就改配置碰运气那样只会让问题更复杂。5.3 几个被反复问到的知识点速答第一nginx和Apache的本质区别在于并发模型。Apache可以一个进程处理一个连接nginx是一个进程处理N个连接前者逻辑简单但内存开销大后者异步事件驱动适合高并发静态和代理场景。第二reload和restart完全不同。reload发送HUP信号重读配置worker进程还活着只是配置变了restart是重新启动进程会瞬间断开所有连接。所以生产环境改配置一律用nginx -s reload。第三为什么nginx的worker数建议等于CPU核数因为每个worker单线程事件循环再多也不会同时利用更多核心反而增加调度成本。第四配置里session等状态如何保持答案是无状态设计优先必要时用ip_hash、sticky或者外部存储如Redis而不是把nginx变成有状态节点。最后分享一个自己总结的维护习惯。每次编译升级前我都会把当时的configure参数完整存到/usr/local/nginx/conf/build.conf里下次照着这个文件重新配置不会漏模块。上线前必做nginx -t然后用reload而不是restart真的只允许断连的场景才会重启。还有新配置拆到conf.d目录里分文件管理后找问题的时间大幅缩短这比记任何命令都管用。nginx看着不难真正让线上稳定可靠的往往是这些枯燥但必须坚持的细节。