Nginx
Nginx 是事件驱动的 HTTP 服务器与反向代理服务器,用少量内存支撑数万并发连接,同时提供 IMAP/POP3 邮件代理。本文按功能特性 → 安装 → 运行控制 → 配置 → 常用模块 → 实战示例 → 优化的顺序组织,可直接作为部署与调优的参考手册。
功能特性
为什么高并发:事件驱动模型
Nginx 的高并发来自事件驱动 + 多进程模型:每个工作进程不为每个连接创建线程,而是向操作系统注册"连接就绪"事件,一个进程即可轮转处理上千个连接。这也是它比 Apache 省资源的根本原因。
| 维度 | Nginx | Apache(prefork 模式) |
|---|---|---|
| 并发模型 | 事件驱动,每进程处理上千连接 | 每连接一个进程/线程 |
| 内存占用 | 10,000 个非活动 keep-alive 连接约 2.5M | 连接数上升内存线性增长 |
| 静态文件性能 | sendfile 直接输出,快 | 经过用户态拷贝,较慢 |
| 模块生态 | 精简,编译期选择 | 丰富,动态加载 |
表格中涉及的术语:
- keep-alive(长连接):TCP 连接在一次请求完成后不关闭,后续请求复用同一条连接,省去反复三次握手的开销。"非活动连接"指空闲挂起的长连接。
- sendfile:Linux 系统调用,让文件数据从磁盘直接经内核发给网卡,不经过用户态缓冲区拷贝,静态文件分发的关键优化。
- gzip / byte ranges / chunked responses:三种 HTTP 响应能力——gzip 是压缩响应体节省带宽;byte ranges 支持按字节范围下载(断点续传、视频拖动进度条靠它);chunked responses 是分块传输编码,内容长度未知时边生成边发送。
- SSI-filter(Server Side Includes):在输出 HTML 时把公共片段(如页头页尾)动态嵌入页面,避免每个页面重复存放相同内容。
- FastCGI:Web 服务器与 PHP、Python 等动态语言进程之间的通信协议,Nginx 自己不执行动态代码,把请求转发给常驻的语言进程处理。
支持的操作系统
主流平台均可运行:Linux 2.6+(生产首选)、FreeBSD、macOS、Windows、Solaris。生产环境几乎都部署在 Linux 上,配合 epoll 事件机制(见后文 events 模块)。
架构:主进程与工作进程
Nginx 采用主进程(master)+ 多个工作进程(worker)的模型。主进程不处理任何客户端请求,只负责读取配置、管理工作进程的生命周期;真正处理请求的是工作进程,各进程之间互不影响,一个 worker 崩溃不会拖垮整个服务。
下图展示了三者的分工:
事件机制是 worker 高并发的基础:Linux 上用 epoll,FreeBSD 上用 kqueue,两者都是"由操作系统主动通知哪些连接就绪"的机制,避免进程挨个轮询所有连接(select/poll 的做法,连接越多越慢)。
安装
包管理器安装(推荐)
发行版软件源的 Nginx 已编译好常用模块,自动处理依赖并注册 systemd 服务,生产环境优先选择:
sudo apt update
sudo apt install nginx
sudo systemctl enable --now nginxsudo yum install epel-release
sudo yum install nginx
sudo systemctl enable --now nginx源码编译安装
只有需要裁剪模块或启用非默认模块(如特定第三方模块)时才需要源码编译。编译前先理解三个依赖各自的作用:
| 依赖 | 作用 | 不装会失去什么 |
|---|---|---|
| PCRE | 正则表达式库 | rewrite 模块无法使用 URL 重写 |
| zlib | 压缩库 | gzip 模块无法压缩响应 |
| OpenSSL | TLS 库 | http_ssl_module 无法提供 HTTPS |
源码编译完整步骤
编译环境按系统安装依赖:
sudo apt install build-essential libtoolsudo yum install gcc gcc-c++ automake autoconf libtool make:::
# 下载依赖源码(版本号以官网最新发布为准)
cd /usr/local/src
wget https://sourceforge.net/projects/pcre/files/pcre2/10.44/pcre2-10.44.tar.gz
wget https://zlib.net/zlib-1.3.1.tar.gz
wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz
wget https://nginx.org/download/nginx-1.26.2.tar.gz
tar -zxvf pcre2-10.44.tar.gz
tar -zxvf zlib-1.3.1.tar.gz
tar -zxvf openssl-1.1.1w.tar.gz
tar -zxvf nginx-1.26.2.tar.gz
# 编译安装:--with-pcre 等指向解压后的源码目录,由 nginx 构建系统统一编译
cd nginx-1.26.2
./configure \
--prefix=/usr/local/nginx \
--with-http_ssl_module \
--with-pcre=../pcre2-10.44 \
--with-zlib=../zlib-1.3.1 \
--with-openssl=../openssl-1.1.1w
make && sudo make install:::
启动验证
启动前确认 80 端口未被其他进程占用,更多端口排查命令见 Linux工具:
# 查看 80 端口占用(ss 是 netstat 的现代替代)
sudo ss -lntp | grep :80
# 启动并验证
sudo systemctl start nginx
curl -I http://localhost # 返回 HTTP/1.1 200 OK 即成功注意
80 端口被占用时 Nginx 启动会直接报 bind() to 0.0.0.0:80 failed (98: Address already in use)。常见占用者是 Apache 或 Docker 映射了宿主 80 端口的容器,先停掉占用方再启动。
运行和控制
命令行参数
| 参数 | 说明 |
|---|---|
-c <文件> | 指定配置文件路径(默认 /usr/local/nginx/conf/nginx.conf) |
-t | 测试配置文件语法,改完配置先跑一次再重载 |
-v | 显示版本号 |
-V | 显示版本号及编译时的全部参数 |
信号控制
Nginx 的控制本质是向 master 进程发送信号:master 收到信号后决定是重载配置、优雅退出还是重启工作进程。systemd 环境直接用 systemctl reload nginx 即可,其底层就是发送 HUP 信号;手动控制时用 kill 向 PID 文件记录的 master 进程发信号:
| 信号 | 作用 | 典型场景 |
|---|---|---|
| TERM / INT | 立即关闭,不等待请求处理完 | 紧急停机 |
| QUIT | 优雅关闭,等存量请求处理完再退出 | 计划内停机 |
| HUP | 重载配置:校验语法后应用新配置 | 修改 nginx.conf 后 |
| USR1 | 重新打开日志文件 | 日志切割后让 Nginx 写入新文件 |
| USR2 | 平滑升级:新旧版本二进制共存 | 不停机升级 Nginx 版本 |
| WINCH | 优雅关闭工作进程 | 配合 USR2 完成升级 |
# systemd 管理(包管理器安装)
sudo systemctl reload nginx # 重载配置
sudo systemctl stop nginx # 优雅停止
# 手动控制(源码编译安装)
PID=$(cat /usr/local/nginx/logs/nginx.pid)
kill -QUIT $PID # 优雅停止
kill -HUP $PID # 重载配置
kill -USR1 $PID # 重新打开日志重载配置的完整过程
HUP 重载是日常最常用的操作,它的全过程做到了零停机:旧工作进程处理完手头请求才退出,新请求由新工作进程按新配置处理。流程如下:
这也解释了为什么改配置后应先 nginx -t 校验:校验不通过时重载会被拒绝,服务保持原配置运行,不会中断。
配置语法基础
Nginx 配置由 指令 参数; 形式的指令组成,部分指令接受带单位的时间与容量值:
| 类别 | 符号 | 说明 |
|---|---|---|
| 时间 | ms / s / m / h / d / w / M / y | 毫秒 / 秒 / 分钟 / 小时 / 日 / 周 / 月(30天)/ 年(365天) |
| 容量 | k 或 K / m 或 M | 千字节 / 兆字节 |
注意
时间参数必须带单位(client_body_timeout 60s;),只写数字虽然多数情况默认按秒解析,但不同指令的默认单位不一致,显式带单位才能避免歧义。
指令按作用域分层:全局指令写在最外层,events {}、http {} 是一级块,http 内的 server {}、location {} 逐级嵌套,子块未显式配置时继承父块的值。
主模块配置
全局指令决定进程模型与日志行为:
user www users; # worker 进程运行的系统用户
worker_processes 4; # 工作进程数,通常等于 CPU 核数
worker_cpu_affinity 0001 0010 0100 1000; # 每个 worker 绑定一个 CPU 核,减少切换开销
worker_priority -10; # 进程优先级(nice 值,越小越优先)
pid /usr/local/nginx/logs/nginx.pid; # master 进程的 PID 文件
error_log /var/log/nginx/error.log warn; # 错误日志及级别worker_processes 的取值依据:CPU 密集型取核数即可,IO 密集型(大量磁盘或上游等待)可取核数的 1.5~2 倍。进程数决定了并发上限,最大客户端连接数的估算公式:
最大连接数 = worker_processes × worker_connections
作为反向代理时,一个客户端请求要占用"客户端→Nginx"和"Nginx→上游"两条连接,因此实际可服务的客户端数要按该乘积的一半估算。
events 块
events 块决定每个 worker 如何处理连接:
events {
use epoll; # 事件机制:Linux 用 epoll,FreeBSD 用 kqueue
accept_mutex on; # 同一时刻只允许一个 worker 争抢新连接,避免惊群
multi_accept on; # worker 被唤醒后一次接受所有等待的连接
worker_connections 1024; # 单个 worker 的最大并发连接数
}- accept_mutex(惊群防护):新连接到达时若所有 worker 同时被唤醒去 accept,绝大多数会抢空而归,白白消耗 CPU;开启后同一时刻只有一个 worker 参与争抢。
- epoll/kqueue 无需手动指定的情况:不写
use指令时 Nginx 自动选择当前平台最高效的机制,显式写出主要是为了锁定行为便于排查。
HTTP 核心模块
客户端请求参数
这组指令限制客户端行为,防的是慢速攻击与超大请求体撑爆内存:
client_body_buffer_size 16k; # 请求体缓冲区,超出部分写入临时文件
client_body_temp_path /spool/nginx/client_temp 1 2; # 临时文件目录,1 2 表示两级散列子目录
client_body_timeout 60s; # 两次读取请求体之间的最大间隔
client_header_timeout 60s; # 读取请求头的超时时间
client_header_buffer_size 1k; # 请求头缓冲区,绝大多数请求头 1k 足够
client_max_body_size 8m; # 请求体上限,超出直接返回 413client_max_body_size 是文件上传场景的第一排查点:上传大文件报 413 Request Entity Too Large,就是请求体超过了它的值。
静态文件:root 与 alias
root 与 alias 都用于定位静态文件,区别在于拼接方式:root 把完整的请求 URI 追加到目录后;alias 只把 location 匹配到的前缀替换掉,其余部分追加。
# root:最终路径 = root + 完整 URI
location / {
root /var/www/html; # 请求 /i/a.png → /var/www/html/i/a.png
index index.html index.htm; # 访问目录时依次查找的默认文件
}
# alias:最终路径 = alias + URI 去掉 location 前缀的部分
location /i/ {
alias /spool/w3/images/; # 请求 /i/a.png → /spool/w3/images/a.png
}注意
alias 只能用在带明确前缀的 location(如 /i/),用在 location / 下会让所有请求都映射到同一目录,几乎必然是配置错误。目录映射统一用 root,只有"URL 路径与磁盘目录结构不一致"时才用 alias。
日志
访问日志记录每个请求,错误日志记录运行异常。日志格式用 log_format 自定义变量组合:
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" $request_time';
access_log /var/log/nginx/access.log main; # 访问日志,main 为上面定义的格式名
error_log /var/log/nginx/error.log warn; # 错误日志,warn 级别及以上常用变量:$remote_addr 客户端 IP、$request_time 请求总耗时(排查慢请求的第一指标)、$upstream_response_time 上游响应耗时(两者的差值即 Nginx 自身开销)。
日志切割配合 USR1 信号:先把日志文件重命名,再发 kill -USR1 让 Nginx 重新打开文件句柄,否则 Nginx 会继续写被重命名后的旧文件。
其他核心指令
| 指令 | 说明 |
|---|---|
| keepalive_timeout 65s | 长连接空闲超过该时间后关闭,过短增加握手开销,过长浪费连接资源 |
| sendfile on | 静态文件走内核直传(见功能特性),生产必开 |
| tcp_nopush on | 配合 sendfile,攒满一个数据包再发送,减少小包数量 |
| tcp_nodelay on | 禁用 Nagle 算法,数据立即发送。Nagle 算法会把小包攒在一起合并发送以降低网络开销,但对延迟敏感的长连接响应应关闭它 |
| gzip on | 启用响应压缩,详见下文 gzip 模块 |
常用模块
访问控制(ngx_http_access_module)
解决"只允许特定来源访问"的问题,按 IP 网段放行或拒绝。规则从上到下依次匹配,第一条命中的规则生效,之后不再往下判断——因此精确的小网段要写在宽泛规则之前。局限是只能基于 IP,无法区分同一出口 IP 下的不同用户。
location /admin/ {
allow 192.168.1.0/24; # 放行内网网段
deny all; # 其余全部拒绝
}基本认证(ngx_http_auth_basic_module)
给资源加一道"用户名+密码"的浏览器弹窗认证,凭据存放在 htpasswd 文件(由 htpasswd 命令生成)。它只解决"挡住随手访问"的问题:凭据仅 Base64 编码而非加密,必须配合 HTTPS 使用,否则密码等同明文传输。
location /admin/ {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}压缩(ngx_http_gzip_module)
压缩文本类响应以节省带宽,对 HTML/CSS/JS 通常能压缩 60% 以上。图片、视频本身已是压缩格式,再 gzip 几乎没有收益反而耗 CPU,因此 gzip_types 只配文本类型:
gzip on;
gzip_comp_level 6; # 压缩级别 1-9,6 是压缩率与 CPU 开销的平衡点
gzip_types text/plain text/css application/json application/javascript;反向代理(ngx_http_proxy_module)
把请求转发给上游服务,客户端只与 Nginx 通信,上游的地址、结构对客户端不可见——这是它能做负载均衡、灰度、统一入口的基础。转发时默认不传递原始请求头信息,需要用 proxy_set_header 显式传给上游:
location / {
proxy_pass http://backend;
proxy_set_header Host $host; # 客户端请求的原始域名
proxy_set_header X-Real-IP $remote_addr; # 客户端真实 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 经过的代理链 IP
}一次反向代理请求的完整链路:
上游服务要获取客户端真实 IP,必须信任并读取这些头,否则它看到的来源 IP 只是 Nginx 的地址。
URL 重写(ngx_http_rewrite_module)
按正则改写请求路径,用于旧路径迁移、URL 美化。rewrite 按在配置中出现的顺序执行,标志位决定匹配后的走向:
| 标志 | 行为 |
|---|---|
| last | 用重写后的路径重新匹配 location(当前 server 内继续找) |
| break | 停止本 location 内的后续 rewrite,用当前路径继续处理 |
| redirect | 返回 302 临时重定向,浏览器发起新请求 |
| permanent | 返回 301 永久重定向,浏览器会缓存新地址 |
location / {
rewrite ^/images/(.*)$ /media/images/$1 break; # 内部改写,客户端无感知
rewrite ^/blog/(.*)$ /news/$1 permanent; # 301 跳转,SEO 权重转移到新地址
}redirect/permanent 是"让浏览器换地址再请求一次",last/break 是"服务器内部悄悄换路径",客户端地址栏不变。
HTTPS(ngx_http_ssl_module)
为站点启用 TLS 加密。TLSv1.2 是当前兼容性底线,TLSv1.3 握手更快更安全,1.0/1.1 已知存在漏洞且主流浏览器已弃用,不应再启用:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/cert.pem; # 证书链文件
ssl_certificate_key /path/to/key.pem; # 私钥文件
ssl_protocols TLSv1.2 TLSv1.3;
}通常同时保留一个 80 端口的 server,把 HTTP 请求 301 到 HTTPS,避免明文入口。
限流(ngx_http_limit_req_module)
限制单个客户端的请求速率,防恶意刷接口和突发流量打垮上游。机制是漏桶:以固定速率匀速放行请求,超出的先排队,队列也满了就直接拒绝(返回 503):
http {
# 按客户端 IP 计数,10m 共享内存约可记录 16 万个 IP,rate 为放行速率
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location / {
limit_req zone=one burst=5 nodelay; # 允许突发 5 个请求不排队直接处理
}
}
}burst 是"排队窗口":平时限 1r/s,瞬时来了 6 个请求,5 个进 burst 快速处理,第 7 个才被拒绝。
配置示例
虚拟主机
一个 Nginx 实例托管多个域名:每个 server 块是一个虚拟主机,Nginx 按请求头中的域名与 server_name 匹配(域名解析机制见 DNS),匹配不到的走 default_server:
server {
listen 80;
server_name www.example.com;
root /var/www/example.com;
}
server {
listen 80 default_server; # 未匹配到任何 server_name 时兜底
server_name www.example.org;
root /var/www/example.org;
}负载均衡
upstream 定义一组上游服务器,proxy_pass 指向它的名字即可。默认按轮询分发,还可按权重、客户端 IP、最少连接调整:
upstream backend {
server backend1.example.com weight=3; # weight 越大分到的请求越多
server backend2.example.com;
# ip_hash; # 同一客户端 IP 固定打到同一台(会话保持)
# least_conn; # 优先发给当前连接数最少的机器
}
server {
location / {
proxy_pass http://backend;
}
}某台 upstream 宕机时 Nginx 会自动把它摘除(失败重试到其他节点),恢复后自动加回,无需人工干预。
反向代理完整示例
server {
listen 80;
server_name www.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;
}
}防盗链
防止其他站点直接引用本站的图片、视频消耗带宽。机制是检查请求头 Referer(浏览器访问来源页面):来源不在白名单内则返回 403:
location ~* \.(jpg|gif|png)$ {
valid_referers none blocked example.com; # none=无 Referer,blocked=Referer 被篡改
if ($invalid_referer) {
return 403;
}
}判断流程:
none 和 blocked 通常要放行:用户直接输入 URL、某些客户端不发送 Referer 都属于这两种情况,一刀切拦截会误伤正常访问。
完整配置文件
点击展开完整 nginx.conf
worker_processes 4;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/nginx/mime.types; # MIME 类型映射,决定 Content-Type
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65s;
gzip on;
gzip_types text/plain text/css application/json application/javascript;
server {
listen 80;
server_name example.com;
root /var/www/html;
location / {
index index.html index.htm;
}
location /images/ {
alias /data/images/;
}
error_page 404 /404.html;
}
}优化建议
事件模型选择
不同平台的高效事件机制不同,一般无需手动指定(Nginx 自动选择),跨平台部署排查时才需要显式配置:
| 平台 | 推荐机制 | 原理 |
|---|---|---|
| Linux 2.6+ | epoll | 内核维护就绪连接列表,只返回活跃连接 |
| FreeBSD | kqueue | 同 epoll 思路的 BSD 实现 |
| Solaris | /dev/poll | 轮询设备文件获取就绪连接 |
| 其他 | select/poll | 逐个扫描所有连接,连接多时性能差 |
Hash 表调优
Nginx 用 hash 表存储 server_name、MIME 类型等查找频繁的数据。报错 "could not build the server_names_hash, you should increase server_names_hash_bucket_size" 时,按提示调大桶大小:
server_names_hash_max_size 512; # hash 表总容量
server_names_hash_bucket_size 64; # 单个桶大小,须为 CPU 缓存行的倍数(32/64/128)域名数量多或单个域名很长时触发,bucket_size 从 64 起按倍增调整。
连接数设置
见主模块配置中的公式。worker_connections 还受操作系统文件描述符上限约束(每个连接占一个描述符),调大前先 ulimit -n 65535 提高系统限制,否则 Nginx 日志会出现 worker_connections are not enough。
常见问题
502 Bad Gateway
反向代理时上游服务不可达:进程未启动、端口错误、处理超时。排查顺序:先直接 curl http://上游地址 验证上游存活,再看 error_log 中的具体原因(connection refused 是没启动,upstream timed out 是响应太慢,此时调大 proxy_read_timeout)。
413 Request Entity Too Large
上传内容超过 client_max_body_size。调大该值即可,注意它可以在 http/server/location 三层配置,就近生效。
URL 重写不生效
rewrite 的执行顺序和标志位最容易配错。在 error_log 中把日志级别临时调为 notice,重写动作会逐条记录,能直接看到规则是否命中、被哪条规则截断。
调试单个客户端
全量 debug 日志量巨大,debug_connection 只对指定 IP 输出 debug 日志(需要 Nginx 编译时带 --with-debug):
events {
debug_connection 192.168.1.1;
}模块速查
点击展开完整模块列表
| 模块 | 说明 |
|---|---|
| ngx_http_core_module | HTTP 核心:server/location 匹配、root/alias |
| ngx_http_access_module | IP 访问控制 |
| ngx_http_auth_basic_module | 基本认证 |
| ngx_http_gzip_module | 响应压缩 |
| ngx_http_proxy_module | 反向代理 |
| ngx_http_rewrite_module | URL 重写 |
| ngx_http_ssl_module | HTTPS |
| ngx_http_limit_req_module | 请求限流 |
| ngx_http_stub_status_module | 运行状态页 |
| ngx_mail_core_module | 邮件代理核心 |
| ngx_mail_auth_module | 邮件认证 |
| ngx_mail_proxy_module | 邮件代理 |
| ngx_mail_ssl_module | 邮件加密 |