Nginx 的强大,不在于它装得多快,而在于它配得有多灵活。前面我们已经完成了多种环境下的安装,接下来真正的重点来了:配置。几乎所有核心功能,都是通过精心编写的 .conf 文件实现的。学会配置,才算真正学会了用 Nginx。
Nginx 能反向代理、限流、负载均衡,全靠它的 .conf 文件来实现。我在多年的工作中总结了 Nginx 最常用的功能,接下来,本文将带入生产环境常用的实际场景,去解决如何配置 conf ,干货文章建议收藏。
一、负载均衡 单节点的反向代理配置,将 HTTP 请求转发的 Tomcat 服务器,但是只能代理到单节点,无法在集群中使用 。
1 server { 2 location /testapi/ { 3 proxy_pass http://178.168.1.10:9120/; 4 index /; # index.html index.htm; 5 proxy_set_header Host $host; 6 # 表示与这个nginx 直接通信的IP, 如果这个是第一层代理,这将是最真实的客户端IP 7 proxy_set_header X-Real-IP $remote_addr; 8 # 表示与这个nginx 直接通信的Port, 如果这个是第一层代理,这将是最真实的客户Port 9 proxy_set_header X-Real-Port $remote_port; 10 # NGINX 在转发请求时会将客户端的 IP 地址添加到 X-Forwarded-For 头部 11 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 12 client_max_body_size 8M; 13 client_body_buffer_size 128k; 14 proxy_connect_timeout 10s; 15 proxy_send_timeout 60s; 16 proxy_read_timeout 60s; 17 } 18 }
而 负载均衡配置 用于将 HTTP 请求分布到多个节点的 Tomcat 服务器;负载均衡功能支持自动下线,当某一台节点服务端返回 500,则会自动下线该节点不再访问 。负载均衡的配置可以单独写成 conf 文件。
1 upstream openserver-api { 2 # ip_hash; 根据访问ip的hash结果分配 3 # least_conn;请求分配到连接数最少的服务 4 # fair; 请求分配到后端响应的时间最短 5 server 127.0.0.1:8900 weight=100 max_fails=10 fail_timeout=15s; 6 server 127.0.0.1:8901 weight=100 max_fails=10 fail_timeout=15s; 7 } 8 9 server { 10 location /test-api/{ 11 proxy_pass http://openserver-api/; 12 index /; # index.html index.htm; 13 proxy_set_header Host $host; 14 proxy_set_header X-Real-IP $remote_addr; 15 proxy_set_header X-Real-Port $remote_port; 16 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 17 client_max_body_size 8M; 18 client_body_buffer_size 128k; 19 } 20 }
上面是按权重轮询服务,在15秒(fail_timeout)内该服务器出现了10次(max_fails)失败,负载均衡器就会停止向它发送请求。生产环境就是使用这样平均分配的策略;
除了轮询策略,还有其他负载均衡策略。在注释上标注了,去掉注释就可以直接使用,但是这些策略都不是平均分配的,需要自己去控制流量的大小,如果没有特殊的情况,还是建议轮询服务。
二、静态资源访问 2.1 配置网页 现在前端开发项目已经不是简单的 html 页面了,多是使用 Vue 等框架开发的,这里给出 Vue 前端项目的页面配置。
1 server { 2 location /page/ { 3 alias /home/user/web/page/dist/; 4 try_files $uri $uri/ /page/index.html; 5 } 6 }
当浏览器访问 https://example.com/page 时,nginx 会代理到 /home/user/web/page/dist/
目录,此时,$uri 就表示 /page
。
try_files 会先检查 alias 目录下 page文件是否存在,存在则返回该文件;
然后会检查 page/
目录是否存在,存在则返回 page/
目录下的 index.xml(page 不带斜杠表示文件,page/ 带斜杠表示目录);
最后如果都找不到,则返回 /page/index.html
,此时 /page/
表示相对目录,和上面的 location /page/
表示一个意思,即 alias 表示的路径,所以 /page/index.html
完整的路径表示 /home/user/web/page/dist/index.html
alias 和 root 的区别?
alias 是一个目录别名的定义,可以直接替换,比如 /page/ 就表示 /home/user/web/page/dist/
root 是最上层目录的定义,访问 /page/ 则表示 访问 root 后面的目录 + /page/,即 /home/user/web/page/dist/page/
2.2 配置验证文件 在很多第三方页面嵌入到主应用,或者跳转第三方域名的时候,往往需要先提前校验域名是否合法。
一般做法是将一个文本校验文件放到域名根目录,例如要访问 https://example.com
, 必须要求 https://example.com/mK49l7IJfF.txt
能访问,这里的 mK49l7IJfF.txt
就需要放到域名根目录,怎么做呢?
1 # 根路径,在 localtion 内部嵌套 2 location / { 3 alias /home/user/web/page/dist/; 4 try_files $uri $uri/ /index.html; 5 6 location = /mK49l7IJfF.txt { 7 alias /home/user/web/index/dist/mK49l7IJfF.txt; 8 } 9 } 10 11 # 普通路径, 在 localtion 内部嵌套 12 location /page/ { 13 alias /home/user/web/page/dist/; 14 try_files $uri $uri/ /page/index.html; 15 16 location = /page/mK49l7IJfF.txt { 17 alias /home/user/web/index/dist/mK49l7IJfF.txt; 18 } 19 }
如果文件一直访问不通 返回404,考虑 mK49l7IJfF.txt 文件是否 Nginx 存在权限不够,无法访问。
2.3 页面禁止缓存 为了提供响应速度,浏览器访问页面会有缓存,页面无法及时更新。如何彻底浏览器禁止缓存?需要在响应的请求头中加入禁止缓存头。
1 location / { 2 add_header Cache-Control "no-cache, no-store, must-revalidate"; 3 add_header Pragma "no-cache"; 4 add_header Expires 0; 5 }
2.4 配置404页面 可以通过指定error_page 404 触发显示 404页面,location 一定要配置在 server{} 目录下,否则不生效。
另外页面中如果存在图片等资源,请注意路径参数等问题。
1 http{ 2 ... 3 # 设置全局的 404 错误页面 4 error_page 404 /404.html; 5 ... 6 } 7 8 server{ 9 ... 10 location = /404.html { 11 root /home/user/web/404/; 12 # 限制外部访问 13 internal; 14 } 15 ... 16 }
1 <!DOCTYPE html> 2 < html lang = "en" > 3 < head > 4 < meta charset = "UTF-8" > 5 < meta name = "viewport" content = "width=device-width, initial-scale=1.0" > 6 < title > 404 - Page Not Found </ title > 7 < style > 8 body { font-family : Arial, sans-serif; } 9 h1 { color : red; } 10 </ style > 11 </ head > 12 < body > 13 < h1 > 404 - Page Not Found 当前页面不存在 </ h1 > 14 </ body > 15 </ html >
三、隐藏版本号 在浏览器响应头里有 Server 项,这里返回的是当前服务 web 服务器的名称,如 Nginx、Kong 等。
但是这里不应该把服务器的版本号返回出来,万一版本有漏洞,会被针对攻击。所以如何隐藏版本号呢?
1 # 1、关闭 http{} 的 server_tokens 2 server_tokens off ; 3 4 5 #2、修改 ../nginx/conf/fastcgi_params 6 fastcgi_params SERVER_SOFTWARE 7 nginx/ $nginx_version 8 # 改为 9 fastcgi_params SERVER_SOFTWARE nginx
fastcgi_params 这个文件里的存放的都是 Nginx 环境变量。
四、日志管理 4.1 日志定时分片 请直接参考这篇文章:别让日志撑爆磁盘!Nginx日志优化实战:自动分片、定时归档、轻松管理亿级日志
4.2 日志 JSON 格式化 将 Nginx 的日志格式化为 JSON,可以修改 log_format
指令,将日志字段转化为 JSON 格式。
JSON 格式的日志,更适合日志聚合和后续处理,比如 ELK, Flume 等工具。
JSON 格式化配置:
1 log_format json '{"remote_addr": " $remote_addr ", "connection": " $connection ", "connection_requests": " $connection_requests ", "remote_user": " $remote_user ", "time_local": " $time_local ", "request_length": " $request_length ", "request": " $request ", "status": " $status ", "request_time": " $request_time ", "upstream_response_time": " $upstream_response_time ", "body_bytes_sent": " $body_bytes_sent ", "content_length": " $content_length ", "http_x_forwarded_for": " $http_x_forwarded_for ", "upstream_addr": " $upstream_addr ", "http_referer": " $http_referer ", "http_user_agent": " $http_user_agent "}' ;
确保将此配置添加到 http
块中,并设置 access_log
指令以使用这个 JSON 格式:
1 http { 2 log_format json '{"remote_addr": " $remote_addr ", "connection": " $connection ", "connection_requests": " $connection_requests ", "remote_user": " $remote_user ", "time_local": " $time_local ", "request_length": " $request_length ", "request": " $request ", "status": " $status ", "request_time": " $request_time ", "upstream_response_time": " $upstream_response_time ", "body_bytes_sent": " $body_bytes_sent ", "content_length": " $content_length ", "http_x_forwarded_for": " $http_x_forwarded_for ", "upstream_addr": " $upstream_addr ", "http_referer": " $http_referer ", "http_user_agent": " $http_user_agent "}' ; 3 4 # 日志格式可以引用main ,也可以引用 json 5 access_log /var/log/nginx/access.log json; 6 }
日志将记录为 JSON 格式,示例如下:
1 { 2 "remote_addr" : "180.101.49.58" , 3 "connection" : "3" , 4 "connection_requests" : "1" , 5 "remote_user" : "-" , 6 "time_local" : "18/Jan/2025:08:02:38 +0000" , 7 "request_length" : "1237" , 8 "request" : "POST /api/queryList HTTP/1.1" , 9 "status" : "200" , 10 "request_time" : "0.112" , 11 "upstream_response_time" : "0.111" , 12 "body_bytes_sent" : "1574" , 13 "content_length" : "2" , 14 "http_x_forwarded_for" : "-" , 15 "upstream_addr" : "180.101.49.58:18105" , 16 "http_referer" : "http://example.com:18105/query/?token=erwerwer&activityId=werwer&level=100" , 17 "http_user_agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0" 18 }
五、灰度发布 如何基于 Nginx 设计灰度发布呢?请看这篇文章。主要运用的配置是负载均衡和权重策略。轻松搞定服务灰度发布!基于Nginx的流量分发与版本控制实战指南
六、拓展模块 6.1、限流功能 参考下面这篇文章: 高并发场景下接口防刷!3 分钟配置 Nginx 基于 IP 限流实战指南
6.2、禁止区域流量 简单的使用 deny 和 allow 配合使用。放在 http 、server、location 中都可以。
1 server { 2 listen 80 ; 3 server_name example.com; 4 5 # 允许特定 IP 地址 6 allow 192.168.1.1 ; 7 allow 203.0.113.2 ; 8 9 # 拒绝所有其他 IP 地址 10 deny all; 11 12 location / { 13 # 其他配置... 14 } 15 } 16 17 server { 18 listen 80 ; 19 server_name example.com; 20 21 # 禁止多个 IP 地址访问 22 deny 192.168.1.100 ; 23 deny 203.0.113.50 ; 24 25 location / { 26 # 其他配置... 27 } 28 }
也可以使用文件批量实现 IP 限制 ,然后把语法文件写入到 limitip.conf 文件中
1 server { 2 listen 80 ; 3 server_name example.com; 4 include /nginx/conf/limitip.conf; 5 ... 6 }
1 deny 192.168.1.100 ; 2 deny 203.0.113.50 ;
那只允许中国的 IP 访问怎么做呢?
1 http { 2 # 定义中国 IP 范围,默认所有 IP 都拒绝 3 geo $allowed_country { 4 default 0 ; # 默认拒绝所有 IP 5 # 以下为中国的 IP 地址段示例,请根据实际情况更新 6 101.0.0.0/8 1; 7 103.0.0.0/8 1; 8 106.0.0.0/8 1; 9 # 其他中国 IP 地址段... 10 } 11 12 server { 13 listen 80 ; 14 server_name example.com; 15 16 location / { 17 # 如果不是中国 IP,返回 403 Forbidden 18 if ( $allowed_country = 0 ) { 19 return 403 ; # 非中国 IP 返回 403 Forbidden 20 } 21 22 root /usr/share/nginx/html; 23 index index.html; 24 } 25 } 26 }
6.3、配置各种响应头 配置响应头是为了告诉浏览器某些操作,比如禁止缓存、禁止iframe嵌套等, Nginx 作为 Web 服务器,支持设置响应头 。加在 server 里可以全局统一
1 location / { 2 # 禁止浏览器缓存 3 add_header Cache-Control "no-cache, no-store, must-revalidate"; 4 add_header Pragma "no-cache"; 5 add_header Expires 0; 6 7 #该页面不允许在frame中展示,另外还有 SAMEORIGIN(表示该页面可以在相同域名页面的frame中展示), 8 # ALLOW-FROM url(页面可以在指定来源的frame中展示) 9 # 这个头加之前需要和前端同步 10 add_header X-Frame-Options:DENY 11 12 #启用XSS保护; 13 add_header X-Xss-Protection: 1; 14 add_header X-Xss-Protection: mod=block; 15 16 # 严格按照 Content-Type指定的类型加载,直接猜测 17 add_header X-Content-Type-Options "nosniff"; 18 19 #指定哪些域名可以访问当前资源 20 add_header Access-Control-Allow-Origin "*"; 21 22 #防止浏览器在下载文件时自动执行内容(如 JS 文件) 23 add_header X-Download-Options "noopen"; 24 25 # 默认只允许加载与页面相同源的资源,帮助防止跨站脚本攻击(XSS) 26 add_header Content-Security-Policy "default-src 'self';" always; 27 28 # 启用 HSTS,强制所有连接使用 HTTPS,禁止304 跳转 29 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; 30 31 }
6.4、防止 DNS欺骗 恶意域名解析 DNS 欺骗可以将非法域名解析到服务器上,导致服务器被恶意访问,在原有配置的上面可以添加一个默认 server ,当其他域名或通过 IP 访问的时候,由于匹配不到 server,就会走默认的 server, 返回444。
444 是 Nginx 服务器扩展的 HTTP错误状态码,服务器不向客户端返回任何信息,并关闭连接, 断开客户端和服务器的连接,防止恶意软件攻击威胁。 通过此方式直接通过IP 将无法访问。
1 server { 2 listen 80 default; 3 server_name _; # 匹配所有没有匹配到其他虚拟主机的请求 4 return 444 ; # 直接返回 444,关闭连接 5 }
Q&A Q1:remote_addr 和 http_x_forwarded_for的 区别是什么? A: remote_addr 表示上游真实IP。假设用户和服务器间有多级代理,那么 remote_addr 就表示最接近服务器的那个代理。
remote_addr 无法伪造,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求。
如果上游是 nginx,那么 remote_addr 只能表示 nginx IP,而不是客户 IP;
http_x_forwarded_for 格式为 X-Forwarded-For: client, proxy1,它是一个请求头,表示多级代理的IP,可以完整的展示客户端到服务器间的代理链,每级代理需要将上级客户端IP(remote_addr)加到 X-Forwarded-For 后面。
所以 X-Forwarded-For 不包含最后一级代理,最后一级代理直接通过 remote_addr 获取。
计算方式:X-Forwarded-For = 最近代理获取到的 X-Forwarded-For + 最近代理获取到的 remote_addr。
所以服务如果通过 Nginx 代理访问,就要禁止直接访问服务,这是为了保证上级代理不会被伪造,否则客户端直连会伪造 IP 数据。 必须使用从 TCP 连接中得到的 Remote Address 才是真实的,其他均不可信!
请一定记住:
1、使用 Nginx 就要禁掉直连服务,保证上级代理不会被伪造请求头;
2、单 Nginx 转发,X-Real-IP 和 X-Forwarded-For 最后一节为真实客端 IP;
3、多 Nginx 转发,X-Real-IP 和 X-Forwarded-For 最后一节为倒数第二级代理IP,建议直接将第一级转发;
Q2: Nginx 访问异常,有时能访问到 有时候访问不到? 排查发现:
1、域名本身过期;
2、域名证书过期;
3、域名没有备案;
只要服务器在国内,所有的域名,包括 hosts 自定义的的域名在国内都需要备案。
Q3 : 走 Nginx 压测后端服务出现问题? Nginx 负载到两个节点上,流量直接走Nginx ,使用 Jmter 压测服务,接口一直响应超时返回502,QPS 上不去。
查询 Nginx 日志发现异常,从日志可以看出, Nginx 获取上游响应超时,但是直接压测服务不走 Nginx 的话,就没有这个问题,这说明服务端没有问题,而是 Nginx ->api 这个路径出现问题, Nginx 负载出现问题, 请求还没到 Tomcat ?
报错日志如下:
1 1、 upstream server temporarily disabled while connecting to upstream 2 2 、(upstream timedout( 110 : Connection timedout)whilereading response header from upstream) 3 3 、 no live upstreams while connecting to upstream,
解决方案如下:
1、填加超时配置:max_fails 重试次数,fail_timeout = 超时重试时间,keepalive 保持长连接时间,(Http1.1,默认支持长连接,可以减少 TCP 创建)
1 # 10秒内连续出现超过2次 2 server 127.0.0.1:9501 max_fails= 2 fail_timeout= 10s weight= 1 ; 3 server 127.0.0.1:9502 max_fails= 2 fail_timeout= 10s weight= 1 ; 4 keepalive 10 ;
2、nginx->API 链路大量 TCP 被断开,检查 TCP 连接是否存在异常。
1 # 统计TCP状态数量 2 netstat -n | awk '/^tcp/ {++state[ $NF ]} END {for(key in state) print key,"\t",state[key]}' 3 4 # 返回如下结果 5 FIN_WAIT_1 1 6 CLOSE_WAIT 8 7 TIME_WAIT 44 8 ESTABLISHED 42
1 location ^~ /xxxxxx/ { 2 ... 3 # 强制走http 1.1,默认会采用 Keep-Alive 4 proxy_http_version 1 . 1 ; 5 ... 6 } 7 参考 https://xiezefan.me/2017/09/27/nginx-502-bug-trace/
3、排除nginx 所在的服务器 内核参数设置太小导致丢包的可能性
以上就是本文的全部内容。
阅读原文:原文链接
该文章在 2025/7/22 17:23:23 编辑过