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) 系统调用——内核态直接传输文件,跳过用户态。

对比:

对静态文件(图片、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;

支持通配符 * 和正则 ~。匹配优先级:

  1. 精确匹配
  2. 以 * 开头的最长通配符
  3. 以 * 结尾的最长通配符
  4. 第一个匹配的正则(~ 开头)

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 的过程:

  1. master 进程读取新配置
  2. 新配置正确性检查
  3. master 启动新 worker 进程
  4. 老 worker 处理完当前请求后退出
  5. 整个过程不停服

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 问题的根因。

参考资料