Nginx 配置文件深度解析
Nginx 配置文件深度解析
写在前面
上一篇《Nginx 安装与基本操作指南》只讲了 apt install 和 systemctl restart,但生产环境的 nginx.conf 动辄几百行,改一处出错整站挂掉。这篇会逐行拆解 nginx.conf 的五大上下文,把每一行的作用讲透——然后讲我们踩过的一个"include 注释"坑,差点把整个测试环境搞挂。
一、Nginx 配置的五大上下文
Nginx 配置文件有严格的层级关系,像俄罗斯套娃:
main # 全局上下文,只能出现一次
├── events # 连接处理模型
└── http # HTTP 服务相关
├── server # 一个虚拟主机
│ ├── listen
│ ├── server_name
│ └── location # URL 匹配规则
│ ├── location
│ └── ...
├── server
└── upstream # 负载均衡上游
每条指令只能出现在它被允许的上下文中。比如 worker_processes 只能在 main,listen 只能在 server。
二、main 上下文
main 上下文是全局,配置 Nginx 进程级别的行为。
2.1 完整 main 配置(ubuntu 默认)
user www-data; # worker 进程运行用户
worker_processes auto; # worker 数量,auto = CPU 核心数
worker_rlimit_nofile 65535; # worker 能打开的最大文件描述符
pid /run/nginx.pid; # master 进程 PID 文件
error_log /var/log/nginx/error.log warn; # 错误日志路径和级别
# 引入其他配置(模块化的关键)
include /etc/nginx/modules-enabled/*.conf;
2.2 关键指令详解
worker_processes
worker_processes auto; # 推荐:让 Nginx 自己决定
| 值 | 含义 |
|---|---|
auto |
等于 CPU 核心数 |
4 |
固定 4 个 worker |
2 * cpu_cores |
过度订阅,某些场景有用 |
worker_processes 决定的是独立的工作进程数,不是 CPU 亲和性。每个 worker 都能用满一个 CPU。
worker_rlimit_nofile
worker_rlimit_nofile 65535;
容易被忽视但极其重要。Nginx 每个连接消耗 2 个文件描述符(client socket + proxy socket)。如果设小了,高并发时会报 Too many open files。
这个值不能超过系统的硬限制:
# 查看系统硬限制
ulimit -Hn
# 524288 ← 系统的硬限制
# worker 进程的 fd 上限不能超过这个
worker_rlimit_nofile 524288 # 可以设到系统硬限制
error_log
error_log /var/log/nginx/error.log warn;
级别从低到高: debug < info < notice < warn < error < crit。
生产推荐 warn——error 级别太低会漏掉重要警告,info 又太啰嗦。
要打开 debug 级别需要先编译 Nginx 时带 --with-debug,然后:
error_log /var/log/nginx/error.log debug;
三、events 上下文
events {
worker_connections 1024; # 每个 worker 的最大并发连接数
multi_accept on; # 一次性接受所有等待连接
use epoll; # 事件模型(Linux 下只能是 epoll)
}
3.1 最大并发计算
最大并发 = worker_processes * worker_connections
但要注意:每个连接消耗 2 个 fd(client + upstream),所以理论最大连接数其实是:
实际最大并发 = (max_fds - 其他文件占用) / 2
举例:4 worker * 1024 连接 = 4096 连接,但算上反向代理,真实可用只有 ~2000。
3.2 multi_accept
multi_accept on;
让 worker 一次性 accept 所有等待中的连接(用 epoll 的 EPOLLEXCLUSIVE 语义)。默认 off,每个事件只 accept 一个,高并发场景建议 on。
四、http 上下文
http 上下文是 HTTP 服务相关配置的根,几乎所有东西都从这里开始。
4.1 完整 http 配置
http {
# ====== 基础 ======
include /etc/nginx/mime.types;
default_type application/octet-stream;
charset utf-8;
# ====== 日志格式 ======
log_format main '$remote_addr - $remote_user [$time_local] '
'\"$request\" $status $body_bytes_sent '
'\"$http_referer\" \"$http_user_agent\" '
'\"$http_x_forwarded_for\" '
'rt=$request_time uct=$upstream_connect_time '
'urt=$upstream_response_time';
access_log /var/log/nginx/access.log main;
# ====== 性能优化 ======
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
# ====== 客户端相关 ======
client_max_body_size 20m;
client_body_buffer_size 128k;
client_header_buffer_size 4k;
large_client_header_buffers 4 8k;
client_body_timeout 60s;
client_header_timeout 60s;
send_timeout 60s;
# ====== gzip 压缩 ======
gzip on;
gzip_vary on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json
application/javascript application/xml+rss
application/atom+xml image/svg+xml;
# ====== 包含虚拟主机 ======
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
4.2 关键指令详解
sendfile
sendfile on;
启用 Linux 的 sendfile(2) 系统调用——内核态直接传输文件,跳过用户态。
对比:
- 关 sendfile:磁盘 → 内核 → 用户态 → 内核 → socket
- 开 sendfile:磁盘 → 内核 → socket
对静态文件(图片、CSS、JS)传输性能提升巨大。默认 on,但要确认。
tcp_nopush 和 tcp_nodelay
这一对是矛盾的,但配合得很好:
tcp_nopush on; # 等数据包累积到 MSS 再发送,减少网络包数量
tcp_nodelay on; # 关闭 Nagle 算法,小包立即发送
两者都开——tcp_nopush 在发送响应头时生效,等响应体累积够了再发;tcp_nodelay 在 keep-alive 模式下生效,保证小包不延迟。
keepalive_timeout
keepalive_timeout 65; # 客户端 keep-alive 超时
keepalive_requests 1000; # 单个 keep-alive 连接最多处理 1000 个请求
太长会浪费 worker 连接,太短频繁重建。一般 65s 是合理值。
gzip
gzip on;
gzip_min_length 1k; # 小于 1KB 不压缩(压缩比反而不划算)
gzip_comp_level 6; # 压缩级别 1-9,6 是 CPU 和体积的平衡
gzip_types text/plain text/css ...;
生产必开——纯文本资源(HTML、CSS、JS)压缩 60-80%。
client_max_body_size
client_max_body_size 20m;
客户端请求体最大大小。超过这个值 Nginx 直接返回 413。
常见场景:文件上传。默认 1m,大文件上传必须调大。
server_tokens
server_tokens off;
不在错误页和响应头里暴露 Nginx 版本号。安全最佳实践——避免暴露版本信息让黑客更精准攻击。
4.3 log_format 变量
log_format main '$remote_addr - $remote_user [$time_local] '
'\"$request\" $status $body_bytes_sent '
'\"$http_referer\" \"$http_user_agent\" '
'\"$http_x_forwarded_for\" '
'rt=$request_time uct=$upstream_connect_time '
'urt=$upstream_response_time';
常用变量:
| 变量 | 含义 |
|---|---|
$remote_addr |
客户端 IP |
$remote_user |
Basic Auth 用户名 |
$time_local |
访问时间 |
$request |
请求行(方法 + URL + 协议) |
$status |
HTTP 状态码 |
$body_bytes_sent |
响应体字节数 |
$http_referer |
Referer 头 |
$http_user_agent |
User-Agent 头 |
$http_x_forwarded_for |
X-Forwarded-For 头 |
$request_time |
请求总耗时(秒,毫秒精度) |
$upstream_connect_time |
连接 upstream 的耗时 |
$upstream_response_time |
upstream 响应耗时 |
$upstream_addr |
upstream 服务器地址 |
强烈建议把 $request_time 加到日志——出问题查日志时,慢请求一目了然。
五、server 和 location 上下文
5.1 server 上下文
server {
listen 80;
listen [::]:80; # IPv6
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
access_log /var/log/nginx/example.com.access.log main;
error_log /var/log/nginx/example.com.error.log;
location / {
try_files $uri $uri/ =404;
}
}
listen
listen 80; # IPv4 80 端口
listen [::]:80; # IPv6 80 端口
listen 443 ssl http2; # HTTPS,启用 HTTP/2
listen 80 default_server; # 默认 server(处理未匹配 server_name 的请求)
listen 80 deferred; # Linux 特有:延迟 accept,提高性能
default_server 是关键——没有匹配 server_name 的请求落到这个 server。如果不指定,第一个 server 是隐式 default。
server_name
server_name example.com www.example.com *.example.com;
支持通配符 * 和正则 ~。匹配优先级:
- 精确匹配
- 以
*开头的最长通配符 - 以
*结尾的最长通配符 - 第一个匹配的正则(
~开头)
root 和 alias
location / {
root /var/www/html; # 请求 /index.html → /var/www/html/index.html
}
location /static/ {
alias /var/www/assets/; # 请求 /static/logo.png → /var/www/assets/logo.png
}
root 把 URI 拼接到 root 后;alias 用 URI 替换 location 前缀。两者的关键区别就是末尾的 /。
5.2 location 上下文
location [modifier] URI {
...
}
修饰符与匹配优先级
| 修饰符 | 含义 | 优先级 |
|---|---|---|
= |
精确匹配 | 1(最高) |
^~ |
前缀匹配,不再检查正则 | 2 |
~ |
大小写敏感正则 | 3 |
~* |
大小写不敏感正则 | 3 |
| (无) | 前缀匹配 | 4(最低) |
完整匹配算法下一篇专门讲,这里给个例子:
location = /health { # 优先级 1:精确匹配 /health
return 200 "ok";
}
location ^~ /static/ { # 优先级 2:前缀匹配 /static/,不再检查正则
root /var/www;
}
location ~ \.(gif|jpg|png)$ { # 优先级 3:正则匹配图片
expires 30d;
}
location / { # 优先级 4:兜底
proxy_pass http://backend;
}
六、include 机制
6.1 为什么需要 include
生产环境的 nginx.conf 如果都写在一个文件里,几百行根本没法维护。用 include 把不同职责的配置分开:
/etc/nginx/
├── nginx.conf # 主配置
├── mime.types # MIME 类型映射
├── conf.d/
│ ├── gzip.conf
│ ├── rate-limit.conf
│ └── security.conf
└── sites-enabled/
├── api.conf
├── web.conf
└── admin.conf
主配置:
http {
include /etc/nginx/mime.types;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
6.2 实战坑:include 路径写错导致整个站点挂掉
这是我同事踩过的坑,差点把整个测试环境搞挂:
# 错误的 include - 注意路径里的注释
include /etc/nginx/conf.d/*.conf # 这是后端应用的配置;
看着对吧?但同事当时用了 Ctrl-C / Ctrl-V,实际写的是:
# 注释
include /etc/nginx/conf.d/*.conf # 这是后端应用的配置;
这种写法是合法的,但因为同事忘了后面有个分号 ;,Nginx 解析时会报错:
nginx: [emerg] unexpected ";" in /etc/nginx/nginx.conf:45
更惨的是,整个 nginx.conf 都解析失败,Nginx 直接拒启动——整个测试环境挂掉 30 分钟。
教训:include 后面必须有分号,注释不能放在指令末尾。
6.3 正确的 include 写法
# OK - 注释独立成行
# 这是后端应用的配置
include /etc/nginx/conf.d/*.conf;
# OK - 注释在前
include /etc/nginx/conf.d/*.conf; # 后端应用配置
# NG - 注释在后但位置奇怪
include /etc/nginx/conf.d/*.conf # 后端应用配置
6.4 include 的测试技巧
# 测试配置语法(不实际加载)
nginx -t
# 测试配置语法并输出最终展开的配置
nginx -T
# 列出最终会加载哪些配置
nginx -T 2>&1 | grep "^# configuration file"
七、变量体系
Nginx 内置了大量变量,可以在配置里用。常见分类:
7.1 请求相关
$request_method # GET / POST
$request_uri # 完整 URL,带 query string
$uri # 解码后的 URI,不带 query string
$args # query string
$arg_<name> # 单个 query 参数,如 $arg_id
$host # Host 头
$http_<name> # 请求头,如 $http_user_agent
$cookie_<name> # Cookie 值,如 $cookie_session
7.2 响应相关
$status # 响应状态码
$body_bytes_sent # 响应体字节数
$sent_http_<name> # 响应头
$upstream_status # upstream 响应状态码
7.3 性能相关
$request_time # 请求总耗时
$upstream_connect_time # 连接 upstream 耗时
$upstream_response_time # upstream 响应耗时
7.4 自定义变量
# 用 set 指令
set $my_var "hello";
set $backend "http://api-v2";
# 用 map(更强大)
map $http_user_agent $is_bot {
default 0;
~*googlebot 1;
~*bingbot 1;
}
实战中我们用 map 做 UA 识别:
map $http_user_agent $is_bot {
default 0;
~*(google|bing|baidu|yandex|duckduckgo)(bot|spider) 1;
~*curl 1;
}
server {
location / {
if ($is_bot) {
access_log /var/log/nginx/bots.log;
}
}
}
八、配置加载与热重载
8.1 测试配置
任何修改都要先 nginx -t 测试:
nginx -t
# 输出
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
8.2 热重载
测试通过后再 reload:
nginx -s reload
reload 的过程:
- master 进程读取新配置
- 新配置正确性检查
- master 启动新 worker 进程
- 老 worker 处理完当前请求后退出
- 整个过程不停服
8.3 实战:reload 失败的排查
# reload 失败
nginx -s reload
# nginx: [error] open() "/run/nginx.pid" failed (2: No such file or directory)
原因:pid 文件路径不对。
# 看主配置里 pid 怎么写的
grep "^pid" /etc/nginx/nginx.conf
# 看实际 pid 文件
ls /run/nginx.pid
ls /var/run/nginx.pid
# 不一致就用 -c 指定配置或 -p 指定 prefix
nginx -c /etc/nginx/nginx.conf -p /etc/nginx
九、本篇小结
| 主题 | 关键点 |
|---|---|
| 五大上下文 | main / events / http / server / location |
| 性能优化 | sendfile / tcp_nopush / tcp_nodelay / gzip |
| 安全 | server_tokens off / 限制 body size |
| 模块化 | include 必须用分号结尾,注释要规范 |
| 变量 | $uri / $args / $http_* / $request_time |
| 热重载 | nginx -t 测试,nginx -s reload 平滑 |
下一篇讲 location 匹配规则与优先级,把 = / ^~ / ~ 这些修饰符讲透,会重点展开 rsbuild 那篇文章里 CSS 404 问题的根因。