最近后台好几个朋友来问Nginx反向代理的问题,从“为什么我配了proxy_pass还是502”到“怎么一套Nginx挂好几个项目”都有。今天干脆把这些东西揉碎了说一说。Nginx反向代理,简单说就是让Nginx站在后端服务前面,替客户端转发请求、接收响应,对外只暴露Nginx这一个入口。它解决的不是“能不能上网”的问题,而是“服务怎么安全、稳定、高效地暴露出去”的问题,适合正在搭服务器、搞前后端分离或者负责后端部署的同学参考。
我最早用Nginx做反向代理,是因为一台服务器上同时跑着好几个应用:一个Java写的后台接口、一个Python爬虫管理系统、一个前端静态页面。如果每个服务都直接用自己的端口对外开放,不仅端口号难记,还得给每个服务单独搞HTTPS证书,安全上也是漏洞百出。后来统一用Nginx代理,80/443端口全归Nginx管,里面再接各种端口,维护起来舒服太多。这篇文章不会只写配置粘贴,我会把每个关键参数为什么这么配、踩过哪些坑都讲清楚,照着操作就能跑起来。
1. 反向代理到底是什么:先弄懂原理再动手
1.1 正向代理和反向代理的区别
很多人一开始分不清这两个概念,我习惯用一个前台类比:正向代理是“你带着工牌,说自己给别人办事”,代理对象是客户端;反向代理是“你戴着公司logo,接待所有来找公司的人”,代理对象是服务端。
放到Nginx语境里,正向代理通常服务于客户端,客户端请求先到一个统一入口,由这个入口替客户端去请求目标服务器,再返回结果。公司内网统一出口访问外部站点,对内网机器来说就是一个典型场景。反向代理则完全反过来了,客户端只知道Nginx的域名,不知道后端实际有几台机器、跑在哪个端口,请求到了Nginx,Nginx按规则转发给后端,后端响应后再原路返回。整个过程中客户端完全无感,它以为自己访问的就是真正的服务。
两者的核心差异可以总结成一张表:
| 对比项 | 正向代理 | 反向代理 |
|---|---|---|
| 代理对象 | 客户端身份 | 后端服务 |
| 隐藏的信息 | 客户端IP身份 | 后端服务拓扑 |
| 典型用途 | 内网出口、内容过滤、访问控制 | 负载均衡、统一入口、SSL卸载 |
| Nginx常见形态 | 较少使用 | 业务中最常见 |
1.2 反向代理解决了哪些实际问题
反向代理为什么是后端部署的标配?我把它解决的问题拆成四块。
第一是统一入口。多台后端服务、多个端口、多个域名,全部收敛到Nginx的80/443。客户端只认一个地址,后端内部怎么切端口、怎么扩容,对客户端完全透明。第二是安全隔离。后端服务不直接暴露到公网,防火墙只需要放开Nginx所在机器的端口,后端端口只在内网出现。就算有人扫描到Nginx,想继续往下摸还得先过Nginx这一关。第三是负载均衡。一台后端扛不住流量,Nginx会把请求按策略分给多台后端,横向扩容直接靠加机器和改配置实现。第四是SSL证书统一管理。所有域名证书统一放在Nginx这一层,后端服务不用各自处理加密,Java、Python、PHP服务都能少操一份心。
再往细里说,Nginx反向代理还可以做静态资源缓存、请求头重写、限流、灰度发布。很多业务刚起步时不用那么多,但入口先留好,后续演进不伤筋动骨。这也是我强烈建议新项目上线前就规划好反向代理层的原因。
1.3 什么时候该用、什么时候大可不必
技术方案最忌讳无脑上,反向代理也不是所有场景都需要。我按经验分了三类。
该用的场景:一台机器上跑多个应用,需要通过不同域名或路径区分;需要对外提供HTTPS服务但后端不支持证书;需要多台后端做负载均衡;需要在入口层做统一限流、统一日志、统一header处理。这些情况反向代理能明显降低维护成本。
不该硬套的场景:只有一个静态站点,直接用Nginx做静态文件服务器就够了,不需要考虑proxy_pass,因为根本没有“后端”要转发;本地开发调试阶段,后端直接监听端口就能访问,再套一层反向代理反而多了排查链路。还有频繁销毁重建的临时测试服务,用系统自带的端口监听直接跑,比Nginx配置更快。
判断标准其实很朴素:你的后端是否对客户端可见?是否需要多个服务共享一个入口?如果答案是“不需要”,那就不必为了用而用。
2. 准备工作:装Nginx的三种主流方式
2.1 装之前先定方案
安装Nginx不是一把梭,先根据你的环境选方案。我常用的是三种:系统包管理器、源码编译、Docker。
包管理器是最快的,CentOS、Ubuntu、Debian都有Nginx包,几条命令就能装完,系统会自动处理依赖和systemd配置,适合想快速跑起来的环境。缺点是版本通常落后,而且官方源里的Nginx默认编译参数可能缺一些模块,比如stream流量转发模块、http_v2模块,后面想加还得重编。
源码编译是可控性最高的方案,模块、安装路径、编译参数全部自己定,适合生产环境和对参数有洁癖的人。缺点是要自己编译、自己写systemd、自己维护升级,门槛稍高。Docker则适合快速验证和隔离环境,一个nginx容器加一行端口映射就能用,但要注意容器网络和宿主机端口之间的转换逻辑。
如果环境特殊,比如内网离线机器、aarch64架构的国产系统、麒麟这类不常见的发行版,源码编译几乎是唯一稳妥路径。依赖包提前下载好,编译参数一次定死,后面不会再被yum源折腾。
2.2 源码编译安装完整步骤
以Linux环境为例,我习惯按这个流程走。
先装编译依赖,CentOS系:
yum install -y gcc gcc-c++ make zlib-devel openssl-devel pcre-develUbuntu系则换一套:
apt install -y build-essential libpcre3-dev zlib1g-dev libssl-dev依赖里的四样东西各有用途:gcc系列负责编译,zlib负责响应压缩,openssl负责HTTPS和证书,pcre负责正则表达式解析。Nginx的location匹配、rewrite规则都依赖pcre,版本太老容易出现兼容问题,我一般会选择与当前Nginx兼容性较好的pcre版本,比如pcre-8.45在多数Linux上都很稳。
然后下载源码编译:
wget https://nginx.org/download/nginx-1.24.0.tar.gz tar -xzf nginx-1.24.0.tar.gz cd nginx-1.24.0 ./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_realip_module \ --with-http_v2_module make -j$(nproc) make installcompile参数里的几个模块是常规操作:http_ssl_module管HTTPS,stub_status模块提供存活监控页,realip模块读取真实客户端IP,http_v2模块支持HTTP/2。括号里加不加,严格取决于你的业务是否需要HTTPS和页面监控,但反代场景基本都会用到,建议一次编译进去。
编译完成后,Nginx默认装在/usr/local/nginx下,主程序在sbin目录里,配置在conf目录里。有个细节值得注意:源码安装不会自动生成systemd unit文件,需要自己补一个,否则开机自启和systemctl管理都做不了。
2.3 验证安装与systemd管理
先验证可执行文件是否正常:
/usr/local/nginx/sbin/nginx -v /usr/local/nginx/sbin/nginx -V小v看版本,大V看编译参数。如果之前编译时带了模块,大V输出里能看到。接下来验证配置语法和启动:
/usr/local/nginx/sbin/nginx -t /usr/local/nginx/sbin/nginxnginx -t只会检查配置文件语法,不会真正启动,这一步在每次改配置之后都要养成习惯。启动后再用curl验证:
curl -I http://127.0.0.1如果返回200或302,说明主服务和欢迎页都正常。为了后续方便管理,我补一段systemd unit文件:
[Unit] Description=nginx web server After=network.target [Service] Type=forking PIDFile=/usr/local/nginx/logs/nginx.pid ExecStart=/usr/local/nginx/sbin/nginx ExecReload=/usr/local/nginx/sbin/nginx -s reload ExecStop=/usr/local/nginx/sbin/nginx -s stop Restart=on-failure PrivateTmp=true [Install] WantedBy=multi-user.targetType=forking是因为Nginx启动后会派生出worker进程,主进程变成后台模式,所以要靠PID文件确认启动状态。把unit文件放到/etc/systemd/system/nginx.service后执行systemctl daemon-reload,就能用systemctl start nginx、systemctl enable nginx了。
3. 反向代理配置拆解:一个能跑的server是怎么来的
3.1 最小反向代理配置:先让流量转起来
一个最简单的反向代理配置只需要一个location块和一行proxy_pass。我用最典型的场景举例:前端页面放在Nginx上,后端接口跑在8080端口。
server { listen 80; server_name example.com; 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; } }把这套配置放进conf文件后,访问example.com时Nginx会把请求转发到本机8080端口。关键点在于,proxy_pass是服务端内部转发,不是302跳转,客户端浏览器地址栏不会发生变化。后端返回什么,客户端看到的就是什么。
很多人会问,Nginx支持JSP吗?严格说Nginx不解析JSP,它不认识Java的那套动态语法。但把请求转发给Tomcat后,Tomcat能解析,最终用户依然能拿到完整的HTML页面。这正好说明反向代理的价值:后端用什么语言、什么框架,对客户端来说完全透明,Nginx只做搬运工。
那后端部署时你需要给Nginx提供什么信息?就三样:后端监听地址和端口、需要转发的URL前缀、连接是否长连接。有了这三样,一个能跑的反向代理基本就够了。
3.2 proxy_pass带不带斜杠的坑:一个字符差出的两种结果
这个坑我一开始也吃过亏,后来发现几乎每个新手都会踩一遍,值得单独拿出来讲。
关键在于proxy_pass后面如果带了URI路径(也就是带斜杠或者具体路径),转发规则会变成“用proxy_pass的路径部分替换location匹配到的前缀”;如果proxy_pass没有路径,只是裸IP端口,那就把原始请求原样转发。
举例说明。第一种写法:
location /api/ { proxy_pass http://127.0.0.1:8080; }请求/api/user会被转发为http://127.0.0.1:8080/api/user,后端收到的路径还是带api前缀,和原始请求一模一样。
第二种写法:
location /api/ { proxy_pass http://127.0.0.1:8080/; }请求/api/user会被转发为http://127.0.0.1:8080/user,api前缀被斜杠替换掉了。如果后端接口本身没有api这个ContextPath,第二种写法反而是正确的;如果后端有,那就必须用第一种或调整路径。
还有一种稍复杂的场景:后端接口地址是http://127.0.0.1:8080/core,希望前端请求/api/user时后端收到/core/user,可以写成:
location /api/ { proxy_pass http://127.0.0.1:8080/core; }注意,末尾没有斜杠,此时policy是把/api/替换为/core,再拼上原URI剩余部分。带不带斜杠不只是习惯差异,是实打实的路径转换规则。我给你的建议是:写完后用curl反复验证后端实际收到的URL,别靠猜。
3.3 请求头和超时参数:反代最容易被忽略的配置
只有一行proxy_pass的配置能跑,但跑得稳不稳,要靠几个配套参数撑着。我常用的标配如下:
location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Connection ""; 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; proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; }先说Host和X-Real-IP。很多后端框架会拿Host头判断域名,做多域名区分或者生成绝对链接,如果Nginx不显式传Host$host,后端收到的是Nginx自己的地址。X-Real-IP和X-Forwarded-For则解决真实IP问题:反代模式下后端看到的永远是Nginx的IP,要把客户端真实IP传给后端,这两个Header是标配。涉及到用户IP记录、封禁、风控时,缺了这个配置后端全抓瞎。
再说proxy_http_version和Connection。Nginx默认和后端之间走HTTP/1.0,而HTTP/1.0每次请求都要重新建立连接。生产环境我一般会把这组配置加上,告诉Nginx与后端通信时使用HTTP/1.1协议,并清空Connection请求头。这样可以用上keepalive连接池,后端长连接模式下性能差异非常明显。
超时参数是另一个重点。proxy_connect_timeout控制Nginx与后端TCP握手的最长等待时间,默认60秒太长,改成5秒可以更快暴露后端不健康的状况。proxy_read_timeout控制Nginx发出请求后等待后端响应的时间,如果你转发的是导出文件、大查询这类慢接口,30秒不够就往上调,否则响应稍微慢一点就504。
3.4 一套Nginx挂多个站点:server块和include管理
一台机器只有一个站点的情况很少,更常见的是同一台Nginx上挂好几个服务。Nginx通过server_name区分不同的域名,每个域名一个server块。
我把配置拆到不同文件里管理:
# conf/nginx.conf 主配置 http { include /usr/local/nginx/conf/conf.d/*.conf; }然后在conf.d目录下建一个文件对应一个站点,例如api.example.com.conf:
server { listen 80; server_name api.example.com; location / { proxy_pass http://127.0.0.1:8080; } }另一个文件app.example.com.conf:
server { listen 80; server_name app.example.com; location / { proxy_pass http://127.0.0.1:9090; } }这样划分的好处很直接:改某个站点配置时只动对应文件,不用在整个大配置里上下翻找;新加一个站点就是新建一个文件再reload,操作风险小很多。
如果没有独立域名,也可以按路径区分服务,比如/api走Java、/crawler走Python,这时候location匹配规则会比多个server块更容易踩坑。我的建议是,优先用域名区分,其次才用路径区分,能让后端逻辑干净不少。
4. 负载均衡与高可用:从单点代理到集群入口
4.1 upstream的几种策略:轮询、权重、ip_hash、least_conn
当后端有多台机器时,Nginx通过upstream块定义后端服务器组,然后在proxy_pass里指向组名而不是具体地址。
upstream backend { server 192.168.1.10:8080 weight=3; server 192.168.1.11:8080 weight=1; server 192.168.1.12:8080 backup; } server { listen 80; server_name api.example.com; location / { proxy_pass http://backend; } }upstream默认策略是轮询,请求按顺序依次分配。加weight后按权重比例分配,权重越高分到的请求越多。老机器、性能弱的机器降低权重,新机器提高权重,这是最常见的使用方式。
ip_hash策略根据客户端IP计算哈希,同一IP的请求总是转发到同一台后端。这个策略适合后端有本地会话状态、不想引入分布式会话的场景。代价是当后端扩缩容时,哈希映射会变化,一部分用户会被重新分配。least_conn策略则每次挑选当前活跃连接最少的后端,适合请求处理时长差异较大的情况。理论上它比轮询更贴合真实负载,但需要Nginx维护连接数,整体开销稍高。
还有个backup标记,标记为backup的服务器平时不接收请求,只有当其他服务器全部不可用时才启用。这相当于一台容灾机器,白名单级别保底。
4.2 健康检查:Nginx怎么知道后端挂了
负载均衡要生效,前提是Nginx能判断哪台后端“不行了”。默认配置下,Nginx靠max_fails和fail_timeout做被动健康检查。
我的推荐配置:
upstream backend { server 192.168.1.10:8080 max_fails=3 fail_timeout=10s; server 192.168.1.11:8080 max_fails=3 fail_timeout=10s; }含义是:在10秒内,如果某台后端失败次数达到3次,Nginx就认为它不可用,并在接下来的10秒内不再往这台发送请求,10秒后重新尝试。这里的“失败”指网络连接失败、超时、返回500/502等错误。
被动检查的好处是零额外依赖,缺点是不主动、有滞后:当一台后端刚挂掉但还没达到max_fails阈值时,总有一部分请求会打到这台坏机器上。如果业务对可用性要求很高,可以引入主动健康检查模块,比如upstream_check_module,或者直接使用Nginx Plus的主动检查功能。不过我从实际经验看,大多数中小规模场景,被动检查配合多后端冗余已经能扛住日常故障,不会因为一台机器挂了让整体崩溃。
4.3 高并发调优:让Nginx撑住大流量
负载均衡解决了多台后端分担的问题,但如果Nginx自身先扛不住,后端再强也没用。我总结几个关键参数:
worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 4096; use epoll; } http { upstream backend { server 192.168.1.10:8080; server 192.168.1.11:8080; keepalive 32; } server { listen 80; location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; } } }worker_processes设为auto会让每个CPU核心跑一个worker进程,应对计算密集和网络中断时能充分利用多核。worker_connections表示单个worker最大并发连接数,配合worker_rlimit_nofile把进程文件描述符上限开放到65535,避免“too many open files”报错。
选epoll是Linux下高并发连接的首选IO模型,Nginx在多数发行版上会自动启用,手写出来只是为了明确。upstream里的keepalive 32也值得注意,它不是Nginx与客户端的连接池,而是Nginx与后端之间的空闲连接缓存数量。配合前面的proxy_http_version和Connection头,可以让Nginx复用与后端的TCP连接,减少握手开销。
估算连接数时我常用一个公式:并发连接数约等于每秒请求数乘以平均响应时间。如果一个接口QPS是500、平均响应时间200毫秒,那并发大约100;再把keepalive空闲连接考虑进去,worker_connections给到4096足够大部分中小型业务。真正高并发再到8000、16000,但得注意系统内核参数同步调整。
5. HTTPS证书配置:给反向代理加上安全层
5.1 证书类型与申请方式
反向代理的一个隐藏优势是SSL统一卸载:后端不用自己折腾证书,所有加解密都交给Nginx。证书类型上,最常见的是单域名证书、多域名证书和泛域名证书。单域名证书只能保护example.com,泛域名证书可以保护example.com和*.example.com下所有子域名,价格和维护策略要结合业务定。
个人站点和测试环境我建议直接用免费证书,现在有成熟的自动化申请工具,可以免手动审核。生产环境则建议从正规CA机构购买商业证书,流程上填报域名、验证所有权、下载证书,服务商一般会提供Nginx格式、Apache格式等选项。比如在GoDaddy这类国际服务商后台下载证书时,选择Nginx格式,下载结果就是一个.crt或.pem加一个.key,正好对应Nginx的ssl_certificate和ssl_certificate_key。
还有一类自签名证书,适合纯内网测试,浏览器会报警告,但可以导入信任解决。生产对外服务不要用自签名,用户体验和信任度都过不了关。
5.2 HTTPS反向代理完整配置
我的标准HTTPS反代配置长这样:
server { listen 443 ssl http2; 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; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } server { listen 80; server_name example.com; return 301 https://$host$request_uri; }443端口的listen后面加ssl是启用HTTPS,http2则是开启HTTP/2协议。ssl_protocols限制最低支持TLSv1.2,建议直接去掉更低版本,老TLS版本的加密强度已经不满足现代安全要求。ssl_ciphers设置密码套件,HIGH:!aNULL:!MD5的意思是只使用高安全强度的套件,禁止匿名套件和MD5签名。
ssl_session_cache和ssl_session_timeout是性能参数。SSL握手非常耗时,session cache可以让同一客户端在一定时间内跳过完整握手,共享内存10m大约能存几万条session,对高访问量站点提升明显。
HTTP到HTTPS的跳转我单独用一个server块,把80端口收到的所有请求直接301跳到https。要注意的是,跳转会丢失POST请求的原始方法,所以如果担心有少量HTTP POST进来,得像上面一样用return 301 https://$host$request_uri,只在访问入口层面做,业务接口本身还是直接代理到后端。
证书文件权限必须收紧,私钥文件泄露等于HTTPS保护失效。我用chmod 600保护.key文件,并确保Nginx进程有读取权限。
5.3 常见SSL报错:从证书链到双向认证
SSL配置报错里我遇到最多的有这么几类。
第一类是no required ssl certificate was sent。这种报错常见于Nginx开启了客户端证书校验的配置里,也就是mTLS双向认证场景。Nginx要求客户端必须携带证书,但客户端没有发送,或者发送的证书不被信任,就会出现这个错。如果你并不打算做双向认证,就检查server块里是不是多写了ssl_verify_client配置,去掉即可;如果确实要做双向认证,要确认客户端用的证书和Nginx配置的ca_file一致。
第二类是证书链不完整。浏览器提示“证书不受信任”或“证书链不完整”,但curl测试后端却正常。这种情况通常是部署的.pem文件只包含服务器证书,漏了中间CA证书。解决方法是把服务器证书和中间证书按顺序合并到一个文件中再配置。服务器证书通常在最前,中间证书跟在后面,顺序不能反。
第三类是证书过期和私钥不匹配。用这两条命令自查:
openssl x509 -enddate -noout -in /etc/nginx/ssl/example.com.pem openssl x509 -noout -modulus -in /etc/nginx/ssl/example.com.pem | openssl md5 openssl rsa -noout -modulus -in /etc/nginx/ssl/example.com.key | openssl md5第一条看证书到期时间,后两条对比证书公钥和私钥的hash,不一致就说明证书和key不是一对。证书续期后常见的坑是只更新了证书文件忘了更新key,或者反过来,key变了证书没变。
6. 常见问题与排查技巧实录
6.1 反向代理报502/504,最快的排查路径
502 Bad Gateway是最经典的反向代理错误,说明Nginx能收到请求,但没能从后端拿到有效响应。我的排查顺序已经固定了,按步骤走基本五分钟内能定位。
第一步,确认后端进程还活着。直接在Nginx服务器上执行curl http://127.0.0.1:8080/health,如果返回失败,说明后端本身就有问题,先修后端,别怀疑Nginx。第二步,确认后端监听在正确端口。用ss -lntp | grep 8080查看监听地址,注意有些程序只监听了127.0.0.1,没有监听0.0.0.0,从其他机器访问就会失败。第三步,查Nginx的error.log,默认路径在/usr/local/nginx/logs/error.log,里面会明确写出“connect() failed (111: Connection refused)”还是“upstream timed out”。第四步,检查SELinux。CentOS和部分国产系统上,SELinux可能阻止Nginx进程发起对外网络连接,用setsebool -P httpd_can_network_connect 1解决。
504则通常是超时。Nginx转发请求后,后端处理时间超过了proxy_read_timeout,Nginx就会先放弃并返回504。这时优先看日志里后端的真实处理耗时,再决定调大超时还是优化后端业务。
| 现象 | 优先检查点 | 常用处理 |
|---|---|---|
| 502且后端本地curl正常 | 监听地址、防火墙 | 修改bind地址、放行端口 |
| 502且错误日志Connection refused | 后端进程或端口 | 启动后端、核对端口 |
| 502且错误日志No route to host | 双机内网不通、防火墙 | 检查网络、iptables/firewalld |
| 504 | 后端处理慢、超时短 | 调大proxy_read_timeout |
| reload报emerg | 配置文件语法或目录不存在 | nginx -t定位报错 |
6.2 404和路径重写:前后端路由对不上的解法
反向代理后的404,大部分不是Nginx没转发,而是转发路径和后端路由对不上。比如前端请求/api/login,后端实际路由绑定的却是/login,直接转过去自然404。
解决思路有两个方向。一是调整location和proxy_pass的映射关系,用上一节讲的斜杠规则把不需要的前缀剥掉。二是用rewrite改写路径。我常用的重写写法:
location /api/ { rewrite ^/api/(.*)$ /$1 break; proxy_pass http://127.0.0.1:8080; }这条规则把/api/user改写成/user再转发,后端就不需要感知api前缀。rewrite后面的break关键字很重要,它让改写后的路径不再重新匹配location,直接进入当前的proxy_pass,避免死循环。
另一个常见的404是前端用的history路由。前端框架在路由切换时使用HTML5 history模式,刷新页面时请求的是/user/detail,但服务器上根本没有这个物理路径。跟Nginx静态托管场景一样,反代到后端前可以先try_files兜底,把不存在路径重写到首页入口。不过更推荐的做法是后端直接支持SPA回退,Nginx只转发不掺和。
6.3 reload、重启和平滑升级:别一改配置就停机
Nginx配置改动后,我强烈建议先nginx -t再reload,不要直接重启。nginx -s reload会优雅地让旧worker处理完当前请求后退出,让新配置生效,几乎不影响在线流量。reload不中断请求,这是我最常用的操作。
停止服务时,sbin目录下可以用nginx -s stop强制停止,但生产环境更推荐kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid),让Nginx处理完现有连接后再退出,是优雅停机。
平滑升级算是Nginx的看家本领。新版本编译好后,不直接替换二进制,让Nginx运行新二进制,再手动通知旧master进程平滑退出。核心步骤我写在下面:
mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old cp /usr/local/nginx-1.26.0/objs/nginx /usr/local/nginx/sbin/nginx kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid) kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid.oldbin)USR2信号让Nginx用二进制启动新的master进程,新老进程并行工作一段时间,老进程处理完存量连接后退出。升级后如果发现新版本有问题,还可以把旧二进制换回来,再走一遍信号流程回滚。整个过程HTTP服务不断,是Nginx高可用性的一个体现。
Windows环境下的Nginx稍微特殊,信号机制不完整,reload需要手动启动新进程再停掉旧进程,而且配置文件里如果用反斜杠或指向不存在的目录,启动时会出现[emerg] createfile() "d:/phpstudy_pro/www/..." failed这类报错。Windows上给我最大的教训是路径改用正斜杠,验证目录真实存在后再启。
6.4 安全加固必做清单
反向代理作为入口,安全配置不能省。我列一个自己的必做清单。
第一,隐藏版本号。默认Nginx响应头里会带Server: nginx/1.24.0,版本号等于直接告诉坏人可以查哪些漏洞。加一行server_tokens off;只显示nginx,不显示版本。第二,限制请求体大小。client_max_body_size 10m;避免有人传超大文件把代理和后端拖垮。第三,加限流。用limit_req模块对入口做速率控制:
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; server { location /api/ { limit_req zone=api_limit burst=20 nodelay; proxy_pass http://backend; } }rate=10r/s表示平均每秒允许10个请求,burst=20表示允许瞬时多放20个进队列,nodelay表示队列中的请求不延迟处理,而是直接瞬时放行。限流不只是防攻击,也是保护后端的重要手段。
第四,定期关注安全公告,及时升级版本。Nginx和F5曾披露过缓冲区溢出类漏洞,如CVE-2022-41742,这类漏洞一旦被利用可能影响服务安全。应对办法没有捷径,跟踪官方安全公告,在测试环境验证后升级到修复版本,同时依赖上面的server_tokens、限流、最小权限配置,即使有漏洞也难以直接利用。
最后,配置文件和证书私钥的权限要收紧,尽量做到Nginx进程账号只读、只有root可写,避免被低权限用户篡改配置。
经验说了这么多,最后分享一点个人体会吧。我用Nginx做反向代理这几年,最大的感触是配置语法本身不难,真正难的是理解转发链路上每一跳做了什么、丢了什么。很多排查不下去的问题,都是因为没有把“客户端到Nginx到后端再回客户端”这条完整链路在大脑里走一遍。我给新人的建议是:先从一句话配置起步,curl每个端口验证,再逐步加HTTPS、负载均衡、安全限制。等你把Nginx当成一块可插拔的入口积木,后续不管是上容器、Kubernetes还是统一网关,思路都会有底。上面这些坑我基本都是踩过一遍的,希望你少走几步。