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