Nginx-01 入门与安装
这是 Nginx 系列的第一篇。整个系列按「入门 → 配置 → 原理 → 功能 → 调优 → 实战 → 面试」的顺序展开,本篇解决三个问题:Nginx 到底是干什么的、怎么装、装完之后的目录和进程怎么管。
1. Nginx 是什么
一句话:Nginx 是一个高性能的 HTTP 服务器和反向代理服务器,同时也能做邮件代理和通用的 TCP/UDP 代理。
它由俄罗斯人 Igor Sysoev 在 2002 年开始开发,最初就是为了解决 C10K 问题(单机同时处理一万个连接)。当时主流的 Apache 用的是「一个连接一个进程/线程」的模型,一万个连接意味着一万个线程,光是上下文切换和内存开销就把机器压垮了。Nginx 用**多进程 + 事件驱动(epoll)**的模型,单个 worker 进程就能扛住数万连接,内存占用只有 Apache 的零头。
1.1 Nginx 的典型职责
在一个现代的后端架构里,Nginx 通常站在最前面,承担这些角色:
| 角色 | 说明 |
|---|---|
| 静态资源服务器 | 直接返回 HTML/CSS/JS/图片,配合 sendfile 零拷贝,性能极高 |
| 反向代理 | 接收客户端请求,转发给后端应用(Go/Java/Python 服务) |
| 负载均衡器 | 把请求按策略分发到多个后端实例,并做健康检查 |
| HTTPS 终结 | 统一处理 TLS 握手和证书,后端只跑明文 HTTP,减轻业务服务负担 |
| 缓存层 | 缓存后端响应,热点内容不再回源 |
| 网关与防护 | 限流、限连、IP 黑白名单、防盗链、基础 WAF |
| API 网关(OpenResty) | 用 Lua 做鉴权、灰度、改写、动态路由 |
理解这张表很重要:大部分「Nginx 怎么配」的问题,本质上是「我要它扮演哪个角色」的问题。角色定了,配置的方向就定了。
1.2 正向代理 vs 反向代理
这是入门最容易混的概念,也是面试常问的第一题。
正向代理:代理的是客户端。客户端知道自己在用代理,服务端不知道真实客户端是谁。
客户端 ──> 正向代理 ──> 目标服务器
(客户端主动配置代理,如科学上网、公司出口代理)
反向代理:代理的是服务端。客户端以为自己直接访问的就是目标服务器,不知道后面还有一堆真实服务器。
客户端 ──> 反向代理(Nginx)──> 后端服务器集群
(客户端无感知,DNS 解析到的就是 Nginx 的 IP)
判断口诀:看代理站在谁那一边、为谁隐藏身份。正向代理隐藏客户端,反向代理隐藏服务端。Nginx 两者都能做,但 99% 的场景是反向代理。
1.3 Nginx vs Apache
| 维度 | Nginx | Apache |
|---|---|---|
| 并发模型 | 多进程 + 异步非阻塞(epoll) | prefork/worker/event 多进程多线程 |
| 高并发内存 | 1 万连接约 几 MB~几十 MB | 1 万连接可能上 GB |
| 静态资源 | 极强(sendfile 零拷贝) | 一般 |
| 动态处理 | 需要转发给 FastCGI/后端服务 | 内置 mod_php 等模块可直接处理 |
| 配置热加载 | 支持(reload 不断连接) |
支持但代价更大 |
| 模块 | 默认编译期决定(商业版支持动态模块) | 运行时动态加载 |
| rewrite 能力 | 强 | 更强(.htaccess 目录级配置) |
选型上现在基本没什么争议:前面放 Nginx 抗流量、做静态和代理,后面放业务服务。Apache 的 .htaccess 这种「每个目录都能有独立配置」的能力 Nginx 没有,但这个特性本身在性能上就是负担(每次请求都要逐级查目录)。
1.4 Nginx 开源版 / Plus / OpenResty / Tengine
- nginx(开源版):官方免费版本,本系列的主角。
- NGINX Plus:商业版,多了动态配置 API、主动健康检查、会话保持、实时监控面板等。
- OpenResty:章亦春发起,把 Nginx 和 LuaJIT 深度整合,能用 Lua 写业务逻辑,是做 API 网关的事实标准之一(Kong、APISIX 都基于它)。
- Tengine:淘宝基于 Nginx 的分支,加了动态模块加载、更强的健康检查、一致性 hash 等。
2019 年 Nginx 公司被 F5 收购。2024 年因为治理分歧,核心开发者 Maxim Dounin 出走并 fork 出了 freenginx,目前生态影响还很小,了解即可。
2. 安装
2.1 包管理器安装(推荐日常使用)
# Ubuntu / Debian
sudo apt update
sudo apt install -y nginx
# CentOS / RHEL / Rocky
sudo yum install -y epel-release
sudo yum install -y nginx
# macOS
brew install nginx
包管理器装的版本通常偏旧,而且模块是发行版打包时定死的。想用官方最新的稳定版,加官方源:
# Ubuntu 示例
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list
sudo apt update && sudo apt install -y nginx
2.2 源码编译安装(需要自定义模块时)
开源版 Nginx 的模块在编译期就决定了,运行时不能像 Apache 那样随便加载。所以只要你需要一个默认没编译进去的模块(比如 --with-http_realip_module、第三方的 ngx_http_substitutions_filter_module),就得源码编译。
# 1. 装依赖
sudo apt install -y build-essential libpcre3-dev zlib1g-dev libssl-dev
# 2. 下载源码
wget https://nginx.org/download/nginx-1.26.2.tar.gz
tar zxvf nginx-1.26.2.tar.gz
cd nginx-1.26.2
# 3. 配置编译选项
./configure \
--prefix=/usr/local/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-stream \
--with-stream_ssl_module \
--with-threads \
--with-file-aio
# 4. 编译安装
make -j$(nproc)
sudo make install
三个依赖的作用:
- PCRE:
location的正则匹配和rewrite指令依赖它。 - zlib:
gzip压缩依赖它。 - OpenSSL:HTTPS 依赖它。
几个高频用到的 --with 选项:
| 选项 | 作用 |
|---|---|
--with-http_ssl_module |
HTTPS 支持,必加 |
--with-http_v2_module |
HTTP/2 支持 |
--with-http_v3_module |
HTTP/3(QUIC),1.25+ 起 |
--with-http_realip_module |
从 X-Forwarded-For 还原真实客户端 IP,有多层代理时必加 |
--with-http_stub_status_module |
暴露连接数等基础监控指标 |
--with-http_gzip_static_module |
直接返回预压缩好的 .gz 文件,省 CPU |
--with-stream |
四层 TCP/UDP 代理 |
--with-threads |
线程池,配合 aio threads 处理大文件 |
2.3 平滑升级(不中断服务替换二进制)
这是 Nginx 一个很漂亮的能力,也是面试加分点。原理是新旧 master 进程共存,旧 master 把 listen fd 交给新 master。
# 1. 备份旧二进制,把新的放上去
cp /usr/sbin/nginx /usr/sbin/nginx.old
cp objs/nginx /usr/sbin/nginx
# 2. 给旧 master 发 USR2:启动新 master + 新 worker,新旧同时接收请求
kill -USR2 `cat /var/run/nginx.pid`
# 此时 nginx.pid 变成 nginx.pid.oldbin,新 master 写入新的 nginx.pid
# 3. 给旧 master 发 WINCH:旧 worker 优雅退出,只留旧 master 兜底
kill -WINCH `cat /var/run/nginx.pid.oldbin`
# 4. 验证新版本没问题后,让旧 master 退出
kill -QUIT `cat /var/run/nginx.pid.oldbin`
# 如果新版本有问题要回滚:
# kill -HUP `cat /var/run/nginx.pid.oldbin` # 旧 master 重新拉起 worker
# kill -QUIT `cat /var/run/nginx.pid` # 新 master 退出
3. 目录结构
以官方源安装为例:
/etc/nginx/ 配置根目录
├── nginx.conf 主配置文件
├── conf.d/ 子配置目录,被 nginx.conf 的 include 引入
│ └── default.conf
├── sites-available/ Debian 系约定:可用站点配置
├── sites-enabled/ Debian 系约定:软链到 available,实际生效的
├── mime.types 文件扩展名 → Content-Type 的映射表
├── fastcgi_params FastCGI 参数(配 PHP 时用)
└── snippets/ 可复用的配置片段
/usr/sbin/nginx 主程序
/var/log/nginx/ 日志目录
├── access.log
└── error.log
/var/cache/nginx/ 缓存和临时文件目录
/usr/share/nginx/html/ 默认站点根目录
/var/run/nginx.pid master 进程的 pid 文件
sites-available + sites-enabled 是 Debian/Ubuntu 的包管理约定,不是 Nginx 本身的机制。官方源码和 CentOS 的包只有 conf.d/。它的好处是「下线一个站点只需删软链,配置文件还留着」。
4. 命令行与信号控制
4.1 常用命令
nginx # 启动
nginx -t # 测试配置文件语法(改完配置必做)
nginx -T # 测试并把最终合并后的完整配置打印出来(排查 include 很有用)
nginx -s reload # 平滑重载配置
nginx -s reopen # 重新打开日志文件(配合 logrotate)
nginx -s quit # 优雅停止(处理完当前请求再退出)
nginx -s stop # 立即停止
nginx -v # 版本号
nginx -V # 版本号 + 编译参数(查有没有某个模块,这个最常用)
nginx -c /path/conf # 指定配置文件启动
nginx -p /path/prefix # 指定 prefix 目录
nginx -V 是排查问题的第一手工具。比如线上报「unknown directive “limit_req_zone”」,先跑 nginx -V 2>&1 | tr ' ' '\n' | grep limit 看模块在不在。
4.2 信号
nginx -s xxx 本质上就是给 master 进程发信号,直接用 kill 也一样:
| 信号 | 等价命令 | 作用 |
|---|---|---|
TERM / INT |
nginx -s stop |
快速停止,不等当前请求 |
QUIT |
nginx -s quit |
优雅停止,处理完当前请求 |
HUP |
nginx -s reload |
重读配置,启动新 worker,老 worker 优雅退出 |
USR1 |
nginx -s reopen |
重新打开日志文件 |
USR2 |
— | 平滑升级二进制 |
WINCH |
— | 优雅关闭所有 worker(master 保留) |
kill -HUP $(cat /var/run/nginx.pid)
# 等价于 nginx -s reload
reload 为什么不断连接? master 收到 HUP 后重新读配置、创建新的 worker,然后给老 worker 发 QUIT。老 worker 收到后先关闭监听 socket(不再接新连接),把手上已建立的连接处理完再退出。整个过程新老 worker 短暂共存,客户端无感知。这个机制在下一篇和第 03 篇会详细讲。
坑:
reload期间如果老 worker 上有长连接(WebSocket、SSE、大文件下载),它会一直等到连接结束才退出。可以用worker_shutdown_timeout 30s;给它设个上限,超时就强制断开。
4.3 systemd 管理
systemctl start nginx
systemctl stop nginx
systemctl reload nginx # 内部执行 nginx -s reload
systemctl status nginx
systemctl enable nginx # 开机自启
journalctl -u nginx --since "10 min ago" # 看启动失败的原因
5. 第一个配置
把默认配置清掉,从零写一个最小可用的:
# /etc/nginx/conf.d/hello.conf
server {
listen 80;
server_name example.com;
root /var/www/hello;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location /api/ {
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;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
这十几行已经包含了 Nginx 最核心的两个能力:静态文件服务和反向代理。生效流程永远是这三步:
vim /etc/nginx/conf.d/hello.conf
nginx -t # 必须先测试!语法错了直接 reload 会导致配置不生效但你以为生效了
nginx -s reload
改完配置一定要先 nginx -t。这是纪律,不是建议。reload 时如果配置有语法错误,Nginx 会拒绝加载新配置、继续用旧配置跑——服务不会挂,但你的改动一个字都没生效,而你却以为改好了。这种「静默失败」排查起来非常浪费时间。
6. 验证安装
curl -I http://127.0.0.1
# HTTP/1.1 200 OK
# Server: nginx/1.26.2
# ...
ps -ef | grep nginx
# root 1234 1 0 10:00 ? 00:00:00 nginx: master process /usr/sbin/nginx
# nginx 1235 1234 0 10:00 ? 00:00:00 nginx: worker process
# nginx 1236 1234 0 10:00 ? 00:00:00 nginx: worker process
看到一个 master + N 个 worker 就对了。master 以 root 运行(因为要绑定 80 这种特权端口、读证书私钥),worker 降权到普通用户运行(安全考虑,即使 worker 被攻破也拿不到 root)。这个模型是下一篇原理部分的重点。
7. 常见启动失败排查
| 现象 | 原因 | 处理 |
|---|---|---|
bind() to 0.0.0.0:80 failed (98: Address already in use) |
80 端口被占(常见是 Apache 或另一个 nginx) | ss -lntp | grep :80 找到占用进程 |
bind() to 0.0.0.0:80 failed (13: Permission denied) |
非 root 用户绑定 1024 以下端口 | 用 root 启动,或 setcap cap_net_bind_service=+ep /usr/sbin/nginx |
nginx: [emerg] unknown directive "xxx" |
模块没编译进去 | nginx -V 确认,重新编译或换包 |
open() "/var/log/nginx/error.log" failed (13: Permission denied) |
日志目录权限不对 | chown -R nginx:nginx /var/log/nginx |
| 502 Bad Gateway | 后端没起或地址写错;SELinux 拦截 | 看 error.log;setsebool -P httpd_can_network_connect 1 |
| 403 Forbidden | root 目录权限不足,或 worker 用户读不到文件 | 检查 root 路径上每一级目录的 x 权限 |
排查的固定动作:先看 /var/log/nginx/error.log 的最后几十行,Nginx 的报错信息质量很高,绝大多数问题它已经直接告诉你了。
tail -50 /var/log/nginx/error.log
8. 面试题
Q:Nginx 为什么快?
三个层面:
- 进程模型:多进程 + 单线程事件循环,无锁、无线程切换开销,worker 数量与 CPU 核数对齐并绑核(
worker_cpu_affinity)。 - I/O 模型:epoll 边缘触发的异步非阻塞,一个 worker 用一个线程管理数万连接,不为每个连接分配栈内存。
- 实现细节:内存池减少 malloc 次数、
sendfile零拷贝、tcp_nopush合并小包、buffer 复用、状态机式的 HTTP 解析不做多余拷贝。
Q:正向代理和反向代理的区别?
正向代理代理客户端、对服务端隐藏客户端身份,客户端需主动配置;反向代理代理服务端、对客户端隐藏后端拓扑,客户端无感知。
Q:reload 会不会丢请求?为什么?
不会。master 收到 HUP 后先解析新配置,成功才创建新 worker;老 worker 收到 QUIT 后停止 accept 新连接,但把已建立连接处理完再退出。若老 worker 上有长连接会一直挂着,用 worker_shutdown_timeout 兜底。注意:如果新配置有语法错误,master 会保留旧配置继续跑,reload 静默失效——所以要先 nginx -t。
Q:nginx -s stop 和 nginx -s quit 有什么区别?
stop 发 TERM,立即关闭、当前正在处理的请求会被中断;quit 发 QUIT,优雅关闭、处理完当前请求再退出。生产环境下线服务必须用 quit。
Q:master 和 worker 分别用什么用户跑?为什么?
master 用 root(需要绑定特权端口、读私钥文件、以指定用户身份 fork worker),worker 用 user 指令指定的低权限用户(如 nginx/www-data)。这样即使 worker 被 RCE 攻破,攻击者拿到的也只是低权限账号。
xingliuhua