目录

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

三个依赖的作用:

  • PCRElocation 的正则匹配和 rewrite 指令依赖它。
  • zlibgzip 压缩依赖它。
  • 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 为什么快?

三个层面:

  1. 进程模型:多进程 + 单线程事件循环,无锁、无线程切换开销,worker 数量与 CPU 核数对齐并绑核(worker_cpu_affinity)。
  2. I/O 模型:epoll 边缘触发的异步非阻塞,一个 worker 用一个线程管理数万连接,不为每个连接分配栈内存。
  3. 实现细节:内存池减少 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 stopnginx -s quit 有什么区别?

stopTERM,立即关闭、当前正在处理的请求会被中断;quitQUIT,优雅关闭、处理完当前请求再退出。生产环境下线服务必须用 quit

Q:master 和 worker 分别用什么用户跑?为什么?

master 用 root(需要绑定特权端口、读私钥文件、以指定用户身份 fork worker),worker 用 user 指令指定的低权限用户(如 nginx/www-data)。这样即使 worker 被 RCE 攻破,攻击者拿到的也只是低权限账号。


下一篇:Nginx-02 配置文件结构与核心指令