文章目录
- [Nginx 基础学习笔记](#Nginx 基础学习笔记)
-
- 写在前面:这份笔记怎么用
- [一、Nginx 简介](#一、Nginx 简介)
-
- [1.1 定义](#1.1 定义)
- [1.2 常见 Web 服务器对比](#1.2 常见 Web 服务器对比)
- [1.3 Nginx 优势](#1.3 Nginx 优势)
-
- [1.3.1 master-worker 进程模型与请求处理流程](#1.3.1 master-worker 进程模型与请求处理流程)
- [1.4 核心组成与常用功能](#1.4 核心组成与常用功能)
- 二、源码安装与升级
-
- [2.1 环境准备(Rocky/CentOS 系)](#2.1 环境准备(Rocky/CentOS 系))
- [2.2 源码编译安装](#2.2 源码编译安装)
- [2.3 目录结构](#2.3 目录结构)
- [2.4 启动与排错](#2.4 启动与排错)
- [2.5 进程模型与信号控制](#2.5 进程模型与信号控制)
- [2.6 版本升级与新增模块](#2.6 版本升级与新增模块)
- [2.7 核心配置文件结构](#2.7 核心配置文件结构)
- 三、静态资源部署
-
- [3.1 静态资源概念](#3.1 静态资源概念)
- [3.2 server 配置基础](#3.2 server 配置基础)
- [3.3 server_name 匹配](#3.3 server_name 匹配)
- [3.4 location 匹配优先级(高频考点)](#3.4 location 匹配优先级(高频考点))
- [3.5 root / alias(高频易混)](#3.5 root / alias(高频易混))
- [3.6 index 与 error_page](#3.6 index 与 error_page)
- [3.7 MIME 类型(default_type 相关)](#3.7 MIME 类型(default_type 相关))
- [3.8 传输优化(sendfile / tcp_nopush / tcp_nodelay)](#3.8 传输优化(sendfile / tcp_nopush / tcp_nodelay))
- [3.9 gzip 压缩](#3.9 gzip 压缩)
- [3.10 浏览器缓存(强缓存 / 协商缓存)](#3.10 浏览器缓存(强缓存 / 协商缓存))
- [3.11 跨域 CORS(同源策略)](#3.11 跨域 CORS(同源策略))
- [3.12 防盗链 valid_referers](#3.12 防盗链 valid_referers)
- [3.13 rewrite 地址重写](#3.13 rewrite 地址重写)
- [3.14 URL 组成(请求报文里)](#3.14 URL 组成(请求报文里))
- 四、代理
-
- [4.1 正向代理 vs 反向代理](#4.1 正向代理 vs 反向代理)
- [4.2 正向代理配置](#4.2 正向代理配置)
- [4.3 反向代理配置](#4.3 反向代理配置)
- [4.4 安全隔离](#4.4 安全隔离)
- [五、HTTPS 与 SSL](#五、HTTPS 与 SSL)
-
- [5.1 HTTP vs HTTPS](#5.1 HTTP vs HTTPS)
- [5.2 自签证书(一键生成,2048 位)](#5.2 自签证书(一键生成,2048 位))
- [5.3 HTTPS server 完整配置](#5.3 HTTPS server 完整配置)
- [5.4 HTTP 强制跳转 HTTPS](#5.4 HTTP 强制跳转 HTTPS)
- [5.5 HTTPS 常见报错排查表](#5.5 HTTPS 常见报错排查表)
- 六、负载均衡
-
- [6.1 为什么需要负载均衡](#6.1 为什么需要负载均衡)
- [6.2 七层 vs 四层负载均衡](#6.2 七层 vs 四层负载均衡)
- [6.3 upstream 配置(7 层负载均衡)](#6.3 upstream 配置(7 层负载均衡))
- [6.4 负载均衡算法一览](#6.4 负载均衡算法一览)
- [6.5 四层负载均衡(stream 模块,用的少)](#6.5 四层负载均衡(stream 模块,用的少))
- [6.6 高可用(keepalived)------ 了解概念即可](#6.6 高可用(keepalived)—— 了解概念即可)
- [6.7 动静分离](#6.7 动静分离)
- [6.8 JMeter 压测验证负载均衡(实战)](#6.8 JMeter 压测验证负载均衡(实战))
- 七、开发高频避坑汇总
-
- [7.1 排查清单(20 条)](#7.1 排查清单(20 条))
- [7.2 三条通用排查心法](#7.2 三条通用排查心法)
Nginx 基础学习笔记
TL;DR
- Nginx 扛高并发的底牌是 epoll IO 多路复用 + master-worker 多进程模型:master 只管配置和信号,worker 抢连接干活。
- 源码编译必须全量带参数,增量加模块时把旧参数原样抄上再追加,否则 ssl/gzip 等旧模块全丢。
location优先级:=>^~> 正则(~/~*)> 普通前缀(取最长)。root是路径拼接 ,alias是前缀替换,两者别混用。- 改完配置先
nginx -t检查语法,再nginx -s reload重载,不 reload 不生效。
写在前面:这份笔记怎么用
这份笔记不是官方文档的搬运,而是把「课堂上学到的原理」和「真机上一次又一次踩坑」揉在一起整理出来的。写它的初衷很简单:Nginx 的知识点零散、配置项多、坑又隐蔽 ,如果只按官方手册的顺序读,很容易「看得懂每个指令,却配不出一套能跑的配置」。所以这里换了个组织方式------按 Nginx 实际承担的作用来分章,你遇到什么问题,就翻对应那一章。
内容组织逻辑
| 章节 | 解决的问题 | 你会得到什么 |
|---|---|---|
| 一、Nginx 简介 | Nginx 到底是什么、凭什么比 Apache 快 | 建立整体认知,面试能讲清楚 |
| 二、源码安装与升级 | 怎么装、怎么启动、怎么平滑升级 | 一套可复现的安装 + 排错流程 |
| 三、静态资源部署 | 怎么把网页、图片、JS/CSS 正确发出去 | server/location/缓存/压缩/防盗链全套配置 |
| 四、代理 | 正向代理和反向代理到底差在哪 | 两种代理的配置模板与适用场景 |
| 五、HTTPS 与 SSL | 怎么让站点跑在 https 上 | 自签证书 + SSL 配置 + 常见报错 |
| 六、负载均衡 | 一台扛不住时怎么横向扩展 | upstream 配置、算法选型、JMeter 验证 |
| 七、开发高频避坑汇总 | 前面踩过的坑集中复盘 | 20 条可直接对照排查的清单 |
阅读建议
- 零基础读者:按章节顺序读,第一章建立概念,第二章跟着敲一遍安装命令,后面每章都动手配一次,比只看不练强十倍。
- 有基础的读者:直接跳到你要用的那一章,比如只做静态站点就看第三章,只做反向代理就看第四章;遇到报错再回第七章对照。
- 准备面试的读者:重点看每章的「高频考点」「高频易混」标注,以及表格里的对比项,这些是最容易被追问的地方。
关于格式约定
- 全文尽量用表格承载对比类知识(如 root vs alias、正向 vs 反向代理),比大段文字更容易速查;
- 命令和配置统一放在代码块里,可直接复制执行 ,路径以源码安装的默认路径
/usr/local/nginx为准; - 带
>的引用块是实战教训和踩坑记录,这些内容官方文档里通常没有,但恰恰是最容易卡住人的地方; - 文中出现的 IP(如
192.168.228.128)、域名(如www.jidai.com)都是实验环境示例,实际使用时替换成你自己的即可。
一句话总结:把这份笔记当成「Nginx 使用手册 + 排错字典」,先通读建立框架,再按需查阅细节,最后一定要在虚拟机上把配置跑一遍------Nginx 这门手艺,看会不算会,配通才算会。
一、Nginx 简介
1.1 定义
| 概念 | 说明 |
|---|---|
| Nginx | C 语言编写、开源、高性能的 HTTP 服务器和反向代理服务器;同时可作为 POP3/SMTP/IMAP 邮件代理服务器 |
| POP3 | 邮局协议版本 3(收邮件) |
| SMTP | 简单邮件传输协议(发邮件) |
| IMAP | 交互式邮件存取协议(收邮件,功能更强) |
1.2 常见 Web 服务器对比
| 服务器 | 特点 |
|---|---|
| IIS | 微软出品,性能一般 |
| Tomcat | Java 专用、重量级,静态资源、高并发弱,本质是 Java Web 容器 |
| Apache | 老牌,并发模型老旧 |
| Lighttpd | 轻量高性能,国外使用多,国内资料少 |
| Nginx | 高并发、配置简洁、模块化、热部署、BSD 协议商用免费 |
1.3 Nginx 优势
- 高并发、速度快,基于 IO 多路复用 epoll
- 配置简洁,模块化,扩展性好
- 多进程模型:
Master 进程 + Worker 进程,Master 管理 Worker - 热部署:不停服务,直接升级 Nginx
- 衍生版本:OpenResty(Nginx+Lua)、Tengine(淘宝)
1.3.1 master-worker 进程模型与请求处理流程
#mermaid-svg-MbDPdbdZVrebt9Cy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MbDPdbdZVrebt9Cy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MbDPdbdZVrebt9Cy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MbDPdbdZVrebt9Cy .error-icon{fill:#552222;}#mermaid-svg-MbDPdbdZVrebt9Cy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MbDPdbdZVrebt9Cy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MbDPdbdZVrebt9Cy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MbDPdbdZVrebt9Cy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MbDPdbdZVrebt9Cy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MbDPdbdZVrebt9Cy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MbDPdbdZVrebt9Cy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MbDPdbdZVrebt9Cy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MbDPdbdZVrebt9Cy .marker.cross{stroke:#333333;}#mermaid-svg-MbDPdbdZVrebt9Cy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MbDPdbdZVrebt9Cy p{margin:0;}#mermaid-svg-MbDPdbdZVrebt9Cy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MbDPdbdZVrebt9Cy .cluster-label text{fill:#333;}#mermaid-svg-MbDPdbdZVrebt9Cy .cluster-label span{color:#333;}#mermaid-svg-MbDPdbdZVrebt9Cy .cluster-label span p{background-color:transparent;}#mermaid-svg-MbDPdbdZVrebt9Cy .label text,#mermaid-svg-MbDPdbdZVrebt9Cy span{fill:#333;color:#333;}#mermaid-svg-MbDPdbdZVrebt9Cy .node rect,#mermaid-svg-MbDPdbdZVrebt9Cy .node circle,#mermaid-svg-MbDPdbdZVrebt9Cy .node ellipse,#mermaid-svg-MbDPdbdZVrebt9Cy .node polygon,#mermaid-svg-MbDPdbdZVrebt9Cy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MbDPdbdZVrebt9Cy .rough-node .label text,#mermaid-svg-MbDPdbdZVrebt9Cy .node .label text,#mermaid-svg-MbDPdbdZVrebt9Cy .image-shape .label,#mermaid-svg-MbDPdbdZVrebt9Cy .icon-shape .label{text-anchor:middle;}#mermaid-svg-MbDPdbdZVrebt9Cy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MbDPdbdZVrebt9Cy .rough-node .label,#mermaid-svg-MbDPdbdZVrebt9Cy .node .label,#mermaid-svg-MbDPdbdZVrebt9Cy .image-shape .label,#mermaid-svg-MbDPdbdZVrebt9Cy .icon-shape .label{text-align:center;}#mermaid-svg-MbDPdbdZVrebt9Cy .node.clickable{cursor:pointer;}#mermaid-svg-MbDPdbdZVrebt9Cy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MbDPdbdZVrebt9Cy .arrowheadPath{fill:#333333;}#mermaid-svg-MbDPdbdZVrebt9Cy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MbDPdbdZVrebt9Cy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MbDPdbdZVrebt9Cy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MbDPdbdZVrebt9Cy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MbDPdbdZVrebt9Cy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MbDPdbdZVrebt9Cy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MbDPdbdZVrebt9Cy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MbDPdbdZVrebt9Cy .cluster text{fill:#333;}#mermaid-svg-MbDPdbdZVrebt9Cy .cluster span{color:#333;}#mermaid-svg-MbDPdbdZVrebt9Cy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MbDPdbdZVrebt9Cy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MbDPdbdZVrebt9Cy rect.text{fill:none;stroke-width:0;}#mermaid-svg-MbDPdbdZVrebt9Cy .icon-shape,#mermaid-svg-MbDPdbdZVrebt9Cy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MbDPdbdZVrebt9Cy .icon-shape p,#mermaid-svg-MbDPdbdZVrebt9Cy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MbDPdbdZVrebt9Cy .icon-shape .label rect,#mermaid-svg-MbDPdbdZVrebt9Cy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MbDPdbdZVrebt9Cy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MbDPdbdZVrebt9Cy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MbDPdbdZVrebt9Cy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
reload:重开 worker
启动 nginx(读取 nginx.conf)
master 进程
fork 出多个 worker 进程
数量 = worker_processes
监听信号:reload / quit / USR2
worker 通过 epoll
监听 listen socket(80/443)
有新连接到达?
accept 连接
(accept_mutex 控制争抢)
解析 HTTP 请求
匹配 server / location
处理请求
静态资源 / 反向代理 / 负载均衡
写回响应
记录 access.log
keep-alive 连接?
关闭连接
职责分工:master 进程不处理请求,只负责读取并校验配置、fork 与管理 worker、接收信号(reload / quit / USR2 平滑升级);worker 进程才是真正干活的,各自通过 epoll 多路复用监听同一个 listen socket,谁抢到连接谁处理,互不阻塞。
并发上限 :worker_processes 决定 worker 数量(一般设为 CPU 核数),worker_connections 决定单个 worker 能同时持有的连接数,两者相乘即为理论最大并发连接数(如 4 × 1024 = 4096)。注意每个反向代理请求会同时占用客户端连接和后端连接,实际可用并发约为该值的一半。
1.4 核心组成与常用功能
| 核心组成 | 作用 |
|---|---|
| nginx | 二进制可执行程序 |
| nginx.conf | 主配置文件 |
| error.log | 错误日志 |
| access.log | 访问日志 |
| 常用功能 | 对应章节 |
|---|---|
| 静态资源部署 | 第三章 |
| 反向代理 / 正向代理 | 第四章 |
| HTTPS | 第五章 |
| 负载均衡(7 层 + 4 层) | 第六章 |
二、源码安装与升级
2.1 环境准备(Rocky/CentOS 系)
① 关闭 SELinux
bash
sestatus # 查看状态
getenforce # 查看当前模式(Enforcing / Permissive / Disabled)
vim /etc/selinux/config # 修改配置,SELINUX=disabled(永久关闭)
| 模式 | 效果 | 建议 |
|---|---|---|
enforcing |
强制拦截,nginx 可能被静默拒绝 | 生产保留 |
permissive |
只记录警告不拦截 | 学习期推荐 |
disabled |
彻底关闭 | 学习期可接受 |
临时切换:setenforce 0(permissive)/ setenforce 1(enforcing),重启失效。
② 依赖包一键安装
bash
# gcc编译器、pcre(正则rewrite)、zlib(压缩)、openssl(https加密)
yum install -y gcc pcre pcre-devel zlib zlib-devel openssl openssl-devel
rpm -qa pcre pcre-devel openssl openssl-devel # 校验
③ 防火墙放行端口(firewalld)
bash
firewall-cmd --list-all # 查看当前放行
firewall-cmd --permanent --add-port=80/tcp # 永久放行 80
firewall-cmd --reload # 必须重载才生效
| 命令 | 作用 |
|---|---|
firewall-cmd --state |
防火墙是否运行 |
firewall-cmd --list-all |
查看所有放行信息 |
firewall-cmd --permanent --add-port=80/tcp |
永久放行端口 |
firewall-cmd --permanent --remove-port=80/tcp |
删除放行 |
firewall-cmd --reload |
重载配置,永久改动后必做 |
铁律:加了
--permanent必须--reload;不加 permanent 是临时放行,重启失效。实战教训:后端机曾因防火墙只放行了 22,导致负载均衡池端口全被拒,JMeter 100% 超时。
2.2 源码编译安装
安装流程:
- 下载官网压缩包(nginx.org/download),解压到
/root/nginx/core ./configure:环境检测 + 生成编译脚本(可追加自定义编译参数)make && make install:编译 + 安装- 启动:进入
sbin目录./nginx - 防火墙放行 80 端口
- 查看信息:
./nginx -V
默认安装路径:
/usr/local/nginx
./configure 的作用:
| 职责 | 说明 |
|---|---|
| 检测系统环境 | 检查 gcc、PCRE、zlib、OpenSSL 是否齐全,缺了报错 |
| 收集编译选项 | 读取 --prefix、--with-xxx 等参数 |
| 生成 Makefile | 把检测结果+选项写进 Makefile,make 照图纸编译 |
完整编译参数:
bash
./configure \
--prefix=/usr/local/nginx \
--sbin-path=/usr/local/nginx/sbin/nginx \
--modules-path=/usr/local/nginx/modules \
--conf-path=/usr/local/nginx/conf/nginx.conf \
--error-log-path=/usr/local/nginx/logs/error.log \
--http-log-path=/usr/local/nginx/logs/access.log \
--pid-path=/usr/local/nginx/logs/nginx.pid \
--lock-path=/usr/local/nginx/logs/nginx.lock \
--with-http_gzip_static_module \
--with-http_ssl_module \
--with-stream
三个大坑:
- 多行写法每行末尾
\后不留空格 ,最后一行不加\,否则报--sbin-path=... 没有那个文件或目录- 增量编译必须把之前所有参数全量带上,再追加新参数 (如
--with-stream),否则新二进制会丢掉 ssl/gzip 等旧模块--with-http_ssl_module默认不开启,想用 HTTPS 必须自己加
yum 安装 vs 源码编译安装:
| 对比 | yum 安装 | 源码编译安装 |
|---|---|---|
| 速度 | 简单快速 | 较慢 |
| 编译参数 | 固定 | 可自定义 |
| 第三方模块 | 难扩展 | 灵活增加 |
| 适用 | 快速部署 | 生产定制 |
卸载旧源码版:./nginx -s stop → rm -rf /usr/local/nginx → make clean → 重新 configure。
2.3 目录结构
bash
yum install -y tree
tree /usr/local/nginx
/usr/local/nginx/
├── conf/ # 配置文件(nginx.conf 主配置、mime.types 媒体类型表;.default 是原文件副本)
├── html/ # 默认网页目录(index.html、50x.html)
├── logs/ # 日志目录(access.log、error.log、nginx.pid)
└── sbin/ # 可执行文件(nginx)
| 目录/文件 | 说明 |
|---|---|
conf/ |
nginx.conf 主配置;mime.types 媒体类型映射表 |
html/ |
index.html 成功默认页;50x.html 错误页 |
logs/ |
access.log 访问日志、error.log 错误日志、nginx.pid 主进程 PID |
sbin/ |
二进制可执行文件 nginx |
常用查看命令: whereis nginx(找安装位置)、./nginx -v(版本)、./nginx -V(版本+编译参数)
2.4 启动与排错
标准启动姿势:
bash
cd /usr/local/nginx/sbin
./nginx -t # ① 检查配置语法(syntax is ok)
./nginx # ② 启动
标准流程:先 ps -ef | grep nginx 看有没有在跑 → 没在跑才 ./nginx;改完配置 ./nginx -s reload,不要重复启动。
启动报错:bind() failed (98: Address already in use)
| 占用者 | 处理 |
|---|---|
| 就是 nginx(master+worker) | 已在运行,直接访问验证,别重复启动 |
| 其他服务(httpd 等) | systemctl stop httpd 再启动 |
| 不想停别人的服务 | 改 nginx.conf 的 listen 8080; |
排查命令:ss -tlnp | grep :80、ps -ef | grep nginx
访问不通排查顺序:
| 排查项 | 命令 |
|---|---|
| 防火墙 | firewall-cmd --list-all |
| SELinux | getenforce |
| nginx 进程 | `ps -ef |
| IP 漂移 | 虚拟机 DHCP 会换 IP,重启后 ip addr 确认 |
2.5 进程模型与信号控制
| 进程 | 作用 |
|---|---|
| master | 主进程:管理 worker、读取配置、处理信号 |
| worker | 工作进程:真正处理请求,数量由 worker_processes 控制 |
bash
kill -信号 `cat /usr/local/nginx/logs/nginx.pid`
| 信号 | 等价命令 | 作用 |
|---|---|---|
TERM/INT |
./nginx -s stop |
快速停止 |
QUIT |
./nginx -s quit |
优雅停止(处理完当前请求再退) |
HUP |
./nginx -s reload |
重载配置,会重开 worker |
USR1 |
./nginx -s reopen |
重新打开日志文件(日志切割) |
USR2 |
--- | 平滑升级(启动新 master,新旧并存) |
WINCH |
--- | 优雅关闭旧 worker(配合 USR2) |
命令行参数:
| 参数 | 作用 |
|---|---|
-v |
只查看版本号 |
-V |
版本 + 各种编译信息 |
-t |
检查配置语法 |
-T |
同 -t,且输出完整配置信息 |
-s signal |
stop / quit / reopen(≈USR1)/ reload(≈HUP) |
-c |
指定配置文件路径 |
-g |
指定参数配置(少用) |
2.6 版本升级与新增模块
平滑升级(核心命令序列):
升级原理:USR2 启动新 master,新旧并存;确认新版正常后 QUIT 旧 master。
bash
# ① 备份旧二进制(不要直接 cp 覆盖!会报 ETXTBSY: 文本文件忙)
mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old
# ② 拷贝新编译的二进制
cp /root/nginx/core/nginx-1.28.3/objs/nginx /usr/local/nginx/sbin/nginx
# ③ 语法检查 + 启动新 master
nginx -t
kill -USR2 `cat /usr/local/nginx/logs/nginx.pid`
sleep 1
# ④ 确认新版正常后,关掉旧 master
kill -QUIT `cat /usr/local/nginx/logs/nginx.pid.oldbin`
# ⑤ 验证
nginx -V | grep ssl
| 两个 PID 文件 | 内容 |
|---|---|
nginx.pid |
新 master 的 PID(USR2 后写入) |
nginx.pid.oldbin |
旧 master 的 PID |
关键经验:
- ETXTBSY(文本文件忙) = 旧 nginx 还在运行,文件被占用,直接 cp 覆盖会失败。解法就是 mv 改名 → cp 新文件 → USR2 → QUIT
- QUIT 前务必确认新版正常(
nginx -V有新模块、curl有响应);新版有问题别 QUIT,kill -HUP \cat nginx.pid.oldbin`` 可回滚make upgrade只做信号切换,不做编译和安装,跳过 make 直接 upgrade 等于没升
新增模块示例(--with-http_ssl_module / --with-stream):
bash
cd /root/nginx/core/nginx-源码目录
make clean # 清旧编译产物
./configure <上面全量参数> --with-stream # 全量参数 + 新模块!
make
mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old
cp objs/nginx /usr/local/nginx/sbin/nginx
kill -USR2 `cat /usr/local/nginx/logs/nginx.pid`
sleep 1
kill -QUIT `cat /usr/local/nginx/logs/nginx.pid.oldbin`
nginx -V | grep stream # 验证模块已编译
2.7 核心配置文件结构
配置位置:/usr/local/nginx/conf/nginx.conf
nginx.conf
├── 全局块(events 和 http 以外的内容)
├── events 块(网络连接相关)
└── http 块
└── server 块(可多个)
└── location 块(一个 server 可多个)
全局块:
| 指令 | 作用 |
|---|---|
user user [group] |
运行 worker 的用户;不指定 group 默认等于用户名,默认 nobody;与 Linux 用户有关,利于权限控制 |
| `master_process on | off` |
worker_processes number |
工作进程数量,用于并发处理请求 |
| `daemon on | off` |
pid 路径 |
master 进程 PID 文件路径 |
error_log 路径 |
错误日志路径 |
include file |
引入其他配置文件 |
events 块:
| 指令 | 作用 |
|---|---|
| `accept_mutex on | off` |
| `multi_accept on | off` |
worker_connections number |
单个 worker 的最大连接数 |
use epoll |
事件驱动模型,建议 epoll |
http 块:
| 指令 | 作用 |
|---|---|
include mime.types |
媒体类型映射表 |
default_type application/octet-stream |
兜底返回格式(二进制流)。常用:text/plain、text/html、application/json |
access_log path [format] |
访问日志位置和名称 |
log_format name 格式 |
日志输出格式,name 与 access_log 的 format 相同 |
| `sendfile on | off` |
keepalive_timeout time |
长连接超时时间 |
keepalive_requests number |
一个 keep-alive 连接可使用的请求次数 |
一个 http 可有多个 server,一个 server 可有多个 location。access_log 配置遵循就近原则 (location 覆盖 server)。
每次改配置:
./nginx -t检查 →./nginx -s reload重载。
三、静态资源部署
3.1 静态资源概念
| 类型 | 内容 |
|---|---|
| 静态资源 | html、css、js、图片、视频等不变的文件 |
| 动态资源 | 需要后端程序生成(JSP、接口返回等),一般部署在 Tomcat |
3.2 server 配置基础
nginx
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
listen 语法:
| 写法 | 含义 |
|---|---|
listen 127.0.0.1:8000; |
指定 IP+端口 |
listen 127.0.0.1; |
指定 IP 的所有端口 |
listen 8000; |
指定端口(所有 IP) |
listen *:8000; |
同 listen 8000,* 代表所有 IPv4 |
listen 80 default_server; |
指定默认服务器(端口匹配但 server_name 都没命中时兜底) |
当 IP 没匹配到任何 server_name 时,匹配到第一个 server 模块 ;可通过
default_server显式指定。
3.3 server_name 匹配
| 方式 | 写法 | 说明 |
|---|---|---|
| 精确匹配 | server_name www.jidai.com; |
最常用,可空格隔开多个 |
| 通配符 | server_name *.jidai.com; |
* 只能在域名首/尾部,不能放中间(www.*.com 不行) |
| 正则匹配 | server_name ~^www\.\w+\.com; |
以 ~ 开头,用正则表达式 |
匹配顺序:
精确匹配 → 通配符开头匹配 → 通配符结尾匹配 → 正则匹配 → default_server → 找不到则 404/第一个 server
实战:域名解析靠
/etc/hosts(Windows: C:\Windows\System32\drivers\etc)。非管理员改 hosts 会被 UAC 拒绝;1.1.1.1是公网 Cloudflare 地址,浏览器直访到不了本机,必须 hosts 映射。
3.4 location 匹配优先级(高频考点)
= 精确匹配 > ^~ 前缀匹配(命中即停) > 正则(~ 区分大小写 / ~* 不区分) > 普通前缀(取最长)
| 写法 | 示例 | 规则 |
|---|---|---|
| 不带符号 | location /abc |
前缀匹配:/abc、/abcd、/abcdf 都能访问 |
= |
location = /abc |
精确匹配,必须完全相等(/abc/ 也不行),优先级最高 |
~ |
location ~ \.png$ |
正则匹配,区分大小写 |
~* |
`location ~* .(png | jpg)$` |
^~ |
location ^~ /server |
普通前缀命中后不再向后匹配,可防正则块抢走 |
实战教训:
- 防盗链正则块
~*\.(html|js|css|png)$会抢掉前缀块的请求 → 用^~ /server或调整正则- 正则 location 一旦匹配就覆盖普通前缀(取最长规则失效)
3.5 root / alias(高频易混)
| 指令 | 拼接规则 | 示例 |
|---|---|---|
root |
root 路径 + location 路径(最上层目录) | root html; + location /images → html/images/... |
alias |
替换 location 路径(目录别名) | alias /data/img; + location /images → /data/img/... |
若 location 以
/结尾,alias 也必须以/结尾;root 无此要求。
3.6 index 与 error_page
nginx
location / {
root html;
index index.html index.htm; # 可多个,从左到右匹配
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
| 关键点 | 说明 |
|---|---|
index 管目录 |
访问目录时自动找默认文件 |
error_page 管出错 |
状态码 → 内部重定向到具体 URI,不走 index 逻辑 |
为什么用 = |
精确匹配优先级最高,保证内部跳转不被 location / 干扰 |
| 改状态码 | error_page 500 502 503 504 =200 /50x.html; 把响应码改成 200 |
| 跳外链 | error_page 404 http://www.bilibili.com; 重定向到外部地址 |
| 命名 location | error_page 404 @jump_to_error; + location @jump_to_error |
3.7 MIME 类型(default_type 相关)
default_type是兜底值 :请求的文件扩展名在 mime.types 里找不到对应类型时才用它。include mime.types;之后绝大多数常见类型已自动映射。
| MIME 类型 | 对应文件 | 说明 |
|---|---|---|
text/html |
.html .htm | 网页 |
text/plain |
.txt | 纯文本 |
text/css |
.css | 样式 |
text/javascript |
.js | JS |
application/json |
.json | JSON 接口返回最常用 |
image/png / image/jpeg / image/gif |
.png .jpg .jpeg .gif | 图片 |
video/mp4 |
.mp4 | 视频 |
application/pdf / application/zip |
.pdf .zip | 文件下载 |
application/octet-stream |
任意 | 默认兜底,浏览器会直接下载而不是打开 |
font/woff2 |
.woff2 | 字体(前端部署常踩坑) |
3.8 传输优化(sendfile / tcp_nopush / tcp_nodelay)
| 指令 | 作用 | 前提 |
|---|---|---|
sendfile on |
高效文件传输,减少用户态↔内核态两次拷贝和进程切换 | --- |
tcp_nopush on |
数据先攒到缓冲区,满了再一次性发,提升网络包传输效率 | 必须在 sendfile on 下生效 |
tcp_nodelay on |
有数据就发,提高实时性 | 必须在 keep-alive 开启下生效 |
tcp_nopush 是"攒够再发"(吞吐优先),tcp_nodelay 是"有就发"(实时优先),两者可同时开。
3.9 gzip 压缩
nginx
gzip on; # 开启压缩
gzip_types text/html text/css application/javascript; # 压缩哪些类型
gzip_comp_level 1; # 1-9:1 压缩最低最快,9 最高最慢,默认 1
gzip_vary on; # 响应头加 Vary: Accept-Encoding
gzip_min_length 1024; # 小于该字节数不压缩(默认 20 字节)
gzip_buffers 4 16k; # 压缩缓存数量和大小
gzip_disable "MSIE [1-6]\.(.*?)$"; # 匹配 User-Agent 的客户端不压缩
| 易混点 | 说明 |
|---|---|
gzip 与 gzip_static |
不互斥;gzip_static 直接发预压缩的 .gz 文件 |
gzip_min_length |
默认 20 字节,太小压了没意义 |
| sendfile 与 gzip 的矛盾 | sendfile 数据不进应用程序,但压缩需要进应用 → 两者同开时 nginx 自动处理 |
3.10 浏览器缓存(强缓存 / 协商缓存)
用户发请求
↓
① 本地有缓存吗?
├─ 没有 → 直接请求服务器,拿新数据存缓存
└─ 有 ↓
② 缓存过期了吗?(强缓存)
├─ 没过期 → 直接用本地缓存,不发请求 ← 强缓存
└─ 过期了 ↓
③ 带 ETag / Last-Modified 问服务器(协商缓存)
├─ 服务器说 304(没变) → 用本地缓存
└─ 服务器说 200(变了) → 拿新数据,更新缓存
| 缓存类型 | 相关头 | 机制 |
|---|---|---|
| 强缓存 | Expires(过期时间)、Cache-Control: max-age(秒) |
没过期直接用本地,不发请求 |
| 协商缓存 | Last-Modified(最后修改时间)、ETag(实体标签) |
每次带条件问服务器,304 就用本地 |
expires 指令:
| 写法 | 效果 |
|---|---|
expires 时间(正整数/0) |
Cache-Control: max-age=time |
expires -1(负数) |
Cache-Control: no-cache(全走协商缓存) |
expires epoch |
Expires=1970,Cache-Control: no-cache |
expires max |
Cache-Control: max-age=10年 |
expires off |
默认,不设置缓存头 |
实战教训:配置了防盗链/新规则但浏览器还显示旧图 = 浏览器缓存,强刷 Ctrl+F5 或用 expires -1 验证。
3.11 跨域 CORS(同源策略)
同源策略:协议 + IP(域名) + 端口 三者全部相同才叫同源,A 页面异步请求 B 服务器数据 = 跨域。
nginx
location /getUser {
default_type application/json;
add_header Access-Control-Allow-Origin *; # 允许来源:* 或指定 http://192.168.228.128
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE;
return 200 '{"id":1,"name":"jidai","age":18}';
}
| 坑 | 说明 |
|---|---|
Access-Control-Allow-Origin http:192.168.228.128 |
少个斜杠 http: 应为 http://,浏览器不识别 |
| 指定来源写法 | add_header Access-Control-Allow-Origin http://192.168.228.128; |
3.12 防盗链 valid_referers
原理 :HTTP 头 Referer 告诉服务器"这个请求是从哪个页面链接过来的"。nginx 把 Referer 和 valid_referers 匹配,匹配不上就把 $invalid_referer 置 1。
nginx
location ~*\.(html|js|css|png)$ {
valid_referers none blocked www.bilibili.com; # 白名单
if ($invalid_referer) {
return 403;
}
expires max;
}
| 参数 | 含义 |
|---|---|
none |
没有 Referer 头也允许(直接输 URL 访问) |
blocked |
Referer 被防火墙/代理过滤后也允许 |
www.bilibili.com |
白名单域名(可多个),支持 *. 通配符和正则 |
高频坑:
none和blocked必须一起写 ,单独写none会报错:the "none" or "blocked" referers are specified ... without any valid referer- 正则 location 抢前缀块会导致防盗链不生效(location 优先级问题)
- Referer 什么时候会带上 :页面里的 img/script/link 等资源请求会带(来源是当前页面 URL);地址栏直接输入、或超链接跳转不带 Referer(属 none)
- 浏览器缓存也会让"改了配置还看到图"
3.13 rewrite 地址重写
依赖 pcre(正则库),由 ngx_http_rewrite_module 解析。
nginx
rewrite ^/rewrite/url\w*$ https://www.bilibili.com; # 跳外网
rewrite ^/rewrite/(testbreak)\w*$ /$1 last; # 捕获组 + last
rewrite ^/rewrite/(testreturn)\w*$ /$1 last;
核心指令:
| 指令 | 作用 |
|---|---|
set $key value; |
设置变量(不能和 nginx 预设全局变量重名) |
if (条件) {} |
条件判断:~ 区分大小写、~* 不区分;-f 文件存在、-d 目录存在、-e 文件或目录存在、-x 可执行;前面加 ! 取反 |
break |
退出 rewrite,不匹配其他 location,在当前 root 找资源 |
return code [text/url] |
直接返回状态码/重定向;301 永久、302 临时 |
rewrite regex replacement [flag] |
重写规则 |
flag 四种:
| flag | 行为 |
|---|---|
last |
匹配后去其他 location 块继续匹配(最常用) |
break |
停止 rewrite,不换 location,在本 location 的 root 下找文件 |
redirect |
返回 302 临时重定向(浏览器地址栏变化) |
permanent |
返回 301 永久重定向 |
应用场景: 域名跳转(www→不带 www)、域名镜像(旧目录跳新目录)、URL 简化(长地址正则转短)、防盗链完善。
实战教训:rewrite 多次失败大多是缺括号/缺斜杠/正则写错 ;
~^/rewrite/(testbreak)\w*$里捕获组$1配合last是最常用组合。
3.14 URL 组成(请求报文里)
浏览器地址栏 URL ≠ 请求报文里的 URL:
| 组成部分 | 地址栏(你看到的) | 请求行里实际发出 |
|---|---|---|
协议 http:// |
✅ | ❌(版本单独写 HTTP/1.1) |
主机 192.168.228.128:81 |
✅ | ❌(放 Host 请求头) |
路径 /server/44.html |
✅ | ✅ |
参数 ?name=jidai |
✅ | ✅ |
锚点 #top |
✅ | ❌(根本不发,浏览器自留地) |
实际请求行:GET /server/44.html?name=jidai HTTP/1.1
Host: 192.168.228.128:81
| nginx 变量 | 含义 | 对应 |
|---|---|---|
$request_uri |
请求行里原样(路径+参数,未解码) | /server/44.html?name=jidai |
$uri |
nginx 处理后的路径(去参数、可被 rewrite 改) | /server/44.html |
$args |
查询参数(= $query_string) | name=jidai |
$scheme |
协议 | http |
$host |
主机(不带端口) | 192.168.228.128 |
四、代理
4.1 正向代理 vs 反向代理
正向代理:客户端 → 代理 → 目标服务器,代表【客户端】,隐藏客户端 IP
反向代理:客户端 → 代理 → 后端多台服务器,代表【服务器】,隐藏后端真实服务器
| 对比项 | 正向代理 | 反向代理 |
|---|---|---|
| 配置方 | 客户端(浏览器/本机设置) | 服务端配置,客户端无感知 |
| 代表对象 | 客户端 | 后端服务器集群 |
| 隐藏对象 | 客户端 IP | 后端服务器 IP |
| 客户端感知 | 知道代理存在 | 以为代理就是目标服务器 |
| 典型场景 | 内网出口、梯子 | Nginx 负载均衡、统一入口、HTTPS |
记忆口诀:正向藏客户端,反向藏服务器。
4.2 正向代理配置
nginx
server {
listen 8090;
resolver 8.8.8.8; # DNS 解析器
location / {
proxy_pass http://$host$request_uri; # 拿客户端请求的 host 和 url
proxy_set_header Host $host;
}
}
链路: 客户端在浏览器/系统配置代理 IP+端口 → 请求到代理端 nginx → nginx 拿 Host 和 URL 用 proxy_pass http://$host$request_uri 转发 → 服务端响应原路返回。
4.3 反向代理配置
nginx
server {
listen 80;
location / {
proxy_pass http://192.168.56.101; # 转发到后端
proxy_set_header username jidai; # 添加/修改请求头
}
}
| 指令 | 作用 |
|---|---|
proxy_pass url |
转发地址(可带路径、可带变量) |
proxy_set_header 字段 值 |
添加/删除请求头(如 Host、X-Real-IP) |
proxy_redirect redirect replacement |
修改后端返回的 Location/Refresh 头 |
实战教训:
- 后端 IP 写错(192.168 写成 192.128/192.169)→ 502/超时
if (!-f $request_filename)对目录/永远"不存在" → 无限 302 死循环,改用if (!-e ...)
4.4 安全隔离
客户端永远不直接连后端,只连代理(nginx)一个入口;防火墙只开放代理的 80/443,后端全藏内网,客户端根本不知道它们存在。
五、HTTPS 与 SSL
5.1 HTTP vs HTTPS
| 对比 | HTTP | HTTPS |
|---|---|---|
| 传输内容 | 密码、账号、请求体明文,抓包可见 | 加密,抓包看到乱码 |
| 流量劫持 | 运营商/中间人可篡改页面(插广告) | 篡改即失效 |
| 身份验证 | 无法确认对方是谁 | 证书验证服务器身份 |
HTTPS = HTTP + TLS;TLS 是 SSL 3.0 的继任者,网课先讲 SSL,学完不用重学,同一套东西。
5.2 自签证书(一键生成,2048 位)
bash
mkdir -p /root/cert
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout /root/cert/server.key -out /root/cert/server.crt \
-days 365 -subj "/CN=www.jidai.com"
必须 2048 位!Rocky 9 的 OpenSSL 3 默认安全等级拒绝 1024 位 证书:
SSL_CTX_use_certificate() failed (SSL: error:0A00018F:SSL routines::ee key too small)
5.3 HTTPS server 完整配置
nginx
server {
listen 443 ssl;
server_name www.jidai.com;
# ① 证书与私钥(必须成对,且私钥不能带密码)
ssl_certificate /root/cert/server.crt;
ssl_certificate_key /root/cert/server.key;
# ② 协议版本:只留 TLSv1.2 / TLSv1.3,禁用 SSLv3、TLSv1.0、TLSv1.1
ssl_protocols TLSv1.2 TLSv1.3;
# ③ 加密套件:优先服务端套件,剔除匿名和弱算法
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:HIGH:!aNULL:!MD5:!RC4:!3DES;
ssl_prefer_server_ciphers on;
# ④ 会话复用:减少握手开销,提升 HTTPS 性能
ssl_session_cache shared:SSL:10m; # 10m 约可缓存 4 万个会话
ssl_session_timeout 10m;
ssl_session_tickets on;
location / {
root html;
index index.html index.htm;
}
}
| 指令 | 作用 | 常用值 |
|---|---|---|
listen 443 ssl |
监听 443 并开启 SSL(ssl 不能漏,否则按明文 HTTP 处理) |
443 ssl / 443 ssl http2 |
ssl_certificate |
服务器证书(公钥),含证书链 | /root/cert/server.crt |
ssl_certificate_key |
私钥文件,权限建议 600 | /root/cert/server.key |
ssl_protocols |
允许的 TLS 版本 | TLSv1.2 TLSv1.3 |
ssl_ciphers |
加密套件列表,! 表示排除 |
HIGH:!aNULL:!MD5 |
ssl_prefer_server_ciphers |
以服务端套件顺序为准 | on |
ssl_session_cache |
会话缓存,shared:名称:大小 |
shared:SSL:10m |
ssl_session_timeout |
会话缓存有效期 | 10m |
前提:编译时必须带
--with-http_ssl_module,否则nginx -t直接报unknown directive "ssl"。用nginx -V | grep ssl确认。
5.4 HTTP 强制跳转 HTTPS
浏览器默认走 80 端口,需要把 http:// 的请求 301 到 https://。两种写法:
写法一:return 301(推荐,性能更好)
nginx
server {
listen 80;
server_name www.jidai.com;
return 301 https://$host$request_uri; # 保留原路径和参数
}
写法二:rewrite(老写法,功能等价)
nginx
server {
listen 80;
server_name www.jidai.com;
rewrite ^(.*)$ https://$host$request_uri permanent;
}
| 对比 | return 301 |
rewrite ... permanent |
|---|---|---|
| 执行阶段 | rewrite 阶段直接返回,不再走 location 匹配 | 需走完 rewrite 流程 |
| 性能 | 略优 | 略差 |
| 可读性 | 直观 | 正则需理解 |
| 推荐度 | ✅ 首选 | 兼容旧配置 |
关键点:
- 用
$host$request_uri而不是写死域名,能同时兼容多域名和带参数请求;- 301 是永久重定向,浏览器会缓存,调试期建议先用 302,确认无误再改 301;
- 80 和 443 必须是两个独立 server 块,不能写在同一个 server 里。
5.5 HTTPS 常见报错排查表
| 报错 / 现象 | 原因 | 解决命令 / 方法 |
|---|---|---|
SSL_CTX_use_PrivateKey() failed (SSL: key values mismatch) |
证书与私钥不匹配(用了别的证书对应的 key) | 比对模数:`openssl x509 -noout -modulus -in server.crt |
unable to load certificate / PEM_read_bio_X509 |
证书文件格式错误或路径写错 | openssl x509 -in server.crt -noout -text 能正常输出即格式正确;检查 ssl_certificate 路径 |
| 浏览器提示「证书链不完整」/ 部分设备报不安全 | 只配了站点证书,没拼中间 CA | 合并证书链:cat server.crt intermediate.crt > fullchain.crt,ssl_certificate 指向 fullchain.crt |
SSL_ERROR_RX_RECORD_TOO_LONG |
用 https:// 访问了一个没开 SSL 的端口 (如 80),或 listen 443 漏写 ssl |
确认 listen 443 ssl;;用 curl -I https://域名 验证;80 端口只做跳转 |
| 浏览器提示「不安全 / NET::ERR_CERT_AUTHORITY_INVALID」 | 自签证书不被信任(学习环境正常现象) | 点「继续访问」验收;或把 server.crt 导入系统受信任根证书;生产环境换正式 CA 证书 |
SSL: error:0A00018F:ee key too small |
证书密钥位数太低(1024 位),OpenSSL 3 拒绝 | 重新生成 2048 位:openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365 -subj "/CN=www.jidai.com" |
no "ssl_certificate" is defined for the "listen ... ssl" |
写了 listen 443 ssl 但没配证书 |
补上 ssl_certificate 与 ssl_certificate_key 两行 |
unknown directive "ssl" |
编译时没加 --with-http_ssl_module |
`nginx -V |
| 访问 443 超时 / 连接被拒 | 防火墙没放行 443 | firewall-cmd --permanent --add-port=443/tcp && firewall-cmd --reload;`ss -tlnp |
curl 报 certificate has expired |
证书过期 | openssl x509 -in server.crt -noout -dates 查看有效期,重新签发 |
排查三板斧:
nginx -t看配置语法 →tail -f logs/error.log看握手错误 →curl -vkI https://域名看证书链和协议版本。自签证书下
curl报ssl_verify_result=18(self-signed certificate)属正常,加-k跳过校验即可。
六、负载均衡
6.1 为什么需要负载均衡
服务器处理并发能力有限、可能宕机。系统扩展两种方式:
| 扩展方式 | 思路 | 代价 | 上限 |
|---|---|---|---|
| 纵向扩展(scale up) | 一台机器加 CPU/内存 | 花钱换硬件 | 受硬件限制 |
| 横向扩展(scale out) | 加机器多台干活 | 需要负载均衡器分发 | 理论上无限 |
负载均衡器侧重内容一样 (分发到集群),反向代理侧重内容不一样(转发到不同后端)。
6.2 七层 vs 四层负载均衡
| 概念 | 在第几层 | 对应知识 |
|---|---|---|
| 浏览器输 URL、Host 头 | 7 应用层 | HTTP、server_name 匹配 |
| 80/443 端口 | 4 传输层 | listen 80、443 ssl |
| IP 地址 | 3 网络层 | 192.168.228.128 |
| nginx 负载均衡 | 7 层 (HTTP) | 按 URL/Host/Cookie 分发,能看到 HTTP 内容 |
| 四层负载均衡(如 LVS) | 4 层 (TCP/IP) | 只按 IP+端口转发,更快但看不到内容 |
6.3 upstream 配置(7 层负载均衡)
nginx
upstream backend {
server 192.168.56.101:80;
server 192.168.56.101:8080;
server 192.168.56.101:5672;
}
server {
listen 81;
location / {
proxy_pass http://backend;
}
}
| upstream server 状态参数 | 作用 |
|---|---|
weight=2 |
权重,默认 1 |
down |
当前 server 不参与负载均衡 |
backup |
预留备份服务器,其他全挂了才上 |
max_fails + fail_timeout |
允许失败次数 + 失败后暂停时间(故障转移) |
max_conns |
最大连接数 |
高频坑:upstream 里
server只能写 IP:端口,不能带路径 !server 192.168.56.101:80/server1;会报invalid host in upstream。路径属于 location/proxy_pass 层。
6.4 负载均衡算法一览
| 算法 | 配置 | 分配规则 | 官方内置? | 适用/注意 |
|---|---|---|---|---|
| 轮询 | (默认) | 轮流分发 | ✅ | 默认、最常用 |
| 加权轮询 | weight=2 |
按权重比例分 | ✅ | 服务器性能不同时 |
| ip_hash | ip_hash; |
按客户端 IP 哈希,同一 IP 固定同一台 | ✅ | 会话保持;单机压测 IP 全相同会全打一台,负载均衡失效 |
| least_conn | least_conn; |
谁连接少分给谁 | ✅ | 生产推荐,请求耗时不均时 |
| url_hash | hash $request_uri; |
按 URL 哈希,同一 URL 固定一台 | ✅ | 缓存命中场景(配 consistent 是一致性哈希) |
| fair | 第三方模块 | 按后端响应时间,谁快多分谁 | ❌ 需编译 | 用得少,官方有 least_conn 替代 |
ip_hash 和 url_hash 区别:ip_hash 按客户端 IP(单机压测 IP 全相同会全打一台、NAT 后一堆人共享一个 IP 也没意义);url_hash 按 URL(IP 不同没关系)。
6.5 四层负载均衡(stream 模块,用的少)
| 方案 | 类型 | 特点 | 适用 |
|---|---|---|---|
| LVS | 内核态 L4 | 性能最高,经典方案 | 大规模高并发 |
| HAProxy | 专业 L4/L7 代理 | 功能全 | 中小规模讲究 |
| 云负载均衡(SLB/NLB) | 云厂商托管 | 免运维 | 云上主流 |
| nginx stream | Web 服务器兼职 | 配置简单、与 nginx 统一管理 | 小项目顺带转发端口 |
LVS 不是中间件 :它是 Linux 内核自带功能(ip_vs 模块),内核态转发所以性能高;配上
ipvsadm工具配置规则,三大模式:NAT / DR / TUN。nginx 做 4 层只是"兼职"(stream 块在 main 层,和 http 平级),生产主流是 LVS/HAProxy/云 LB。
nginx
stream {
upstream mysql_pool {
server 192.168.56.101:3306;
server 192.168.56.101:3307;
}
server {
listen 33000;
proxy_pass mysql_pool;
}
}
6.6 高可用(keepalived)------ 了解概念即可
| 技术 | 解决什么 | 核心机制 | 类似物 |
|---|---|---|---|
| keepalived | IP 高可用:主挂了 VIP 漂到备 | VRRP 心跳 + VIP 虚拟IP + 主备漂移 | ≈ Redis 哨兵(故障切换) |
| Redis 主从 | 数据冗余(复制) | RDB/AOF 同步 | 只是"备胎",不会自动切换 |
| Redis 哨兵 | 自动故障转移 | 心跳监控 + 选举新主 | ≈ keepalived |
记忆:"keepalived ≈ Redis 哨兵"(都做故障切换),Redis 主从只是复制数据。 非运维方向了解概念即可。
6.7 动静分离
nginx
# 静态资源 → nginx 自己
location ~* \.(html|css|js|png|jpg)$ {
root html;
expires max;
}
# 动态请求 → 转发 Tomcat
location / {
proxy_pass http://192.168.56.101:8080;
}
6.8 JMeter 压测验证负载均衡(实战)
测试目标:压 81 端口(upstream 池),观察请求是否被轮询分发到 80/8080/5672。
JMeter 使用要点:
| 项 | 要点 |
|---|---|
| 测试计划结构 | 线程组(nginx) → HTTP请求采样器 → 聚合报告/查看结果树 |
| 线程组 | 200 线程、Ramp-up 1s、循环 1 次 |
| HTTP 请求 | 协议 http(默认值别写成 tcp,否则 100% 异常)、服务器 IP、端口 81、路径 / |
全链路排错顺序(压测失败排查):
| 层 | 检查 | 现象 |
|---|---|---|
| JMeter 配置 | 协议是否 http、有没有 HTTP 请求采样器 | 全异常、平均 21084ms≈连接超时 |
| 后端服务 | `ps -ef | grep nginx` 看后端是否运行 |
| 网络 | 从 Windows 测 后端IP:80(python socket) |
防火墙只放行 22 导致 80/8080/5672 全被拒 |
| 防火墙 | firewall-cmd --list-all |
放行后端所有池端口 |
| 负载均衡验证 | 看三台后端 access.log 或响应内容 | 轮询时应三台都出现请求 |
实战教训:手动启动的 nginx 重启虚拟机后不会自动启动 ,开机后要手动
/usr/local/nginx/sbin/nginx(或配 systemd 服务)。演示/考试前先确认两台机器的 nginx 都在跑。
七、开发高频避坑汇总
前面六章里,坑是散落在各处的:有的藏在安装步骤的引用块里,有的写在某个配置示例的注释后面。真到线上排查时,往往记不住「这个报错在哪一章见过」。所以这一节把全文出现过的踩坑点集中复盘一遍,整理成一张可直接对照排查的清单表------按「现象 → 原因 → 定位命令 → 解决方式」四列组织,遇到问题先按现象搜表,再照着命令验证。
7.1 排查清单(20 条)
| # | 现象 | 原因 | 定位命令 | 解决方式 |
|---|---|---|---|---|
| 1 | 启动报 bind() failed (98: Address already in use) |
80 端口已被占用(nginx 自己已在跑,或 httpd 等别的服务占着) | `ss -tlnp | grep :80、ps -ef |
| 2 | 平滑升级时 cp 报 ETXTBSY: 文本文件忙 |
旧 nginx 进程还在运行,二进制文件被占用,不能直接覆盖 | `ps -ef | grep nginx` |
| 3 | 执行 make upgrade 后版本没变、新模块也没有 |
make upgrade 只做信号切换,不做编译和安装 |
nginx -V 看版本与编译参数 |
先 make && make install(或手动 cp objs/nginx),再走 USR2/QUIT 流程 |
| 4 | 新增模块后 HTTPS / gzip 突然不能用了 | 增量编译时 ./configure 只写了新参数,旧参数没带上,旧模块全丢 |
`nginx -V | grep -E "ssl |
| 5 | ./configure 报 --sbin-path=... 没有那个文件或目录 |
多行写法里 \ 后面多了空格,或最后一行也加了 \ |
逐行看 cat -A 或肉眼检查行尾 |
每行末尾 \ 后不留空格 ,最后一行不加 \ |
| 6 | 防火墙放行了端口,外部还是访问不通 | 加了 --permanent 但没 --reload,规则没生效 |
firewall-cmd --list-all |
firewall-cmd --reload;后端机要放行负载均衡池的所有端口(80/8080/5672) |
| 7 | 负载均衡池里所有后端 100% 超时 | 后端机防火墙只放行了 22,池端口全被拒 | 从客户端 telnet 后端IP 80 或 firewall-cmd --list-all |
后端机逐个放行池端口并 reload |
| 8 | 防盗链配置报 the "none" or "blocked" referers are specified ... without any valid referer |
valid_referers 里只写了 none,没写 blocked |
nginx -t 直接报错定位 |
写成 valid_referers none blocked www.bilibili.com;,none 和 blocked 必须一起写 |
| 9 | 防盗链配了但图片照样被外站引用 | 正则 location 抢走了前缀块的请求,防盗链块根本没命中 | tail -f logs/access.log 看请求落到哪个 location |
用 ^~ /server 让前缀命中即停,或调整正则范围 |
| 10 | 直接输 URL 访问图片被 403 | 地址栏直访不带 Referer ,属于 none 场景,而白名单里没放 none |
浏览器 F12 看请求头有无 Referer |
白名单补 none(和 blocked 一起写) |
| 11 | 改了配置还看到旧图片 / 旧页面 | 浏览器强缓存命中,请求根本没到 nginx | F12 Network 看 from disk cache;curl -I 看响应头 |
强刷 Ctrl+F5;调试期用 expires -1 关掉强缓存再验证 |
| 12 | root 配了却 404,文件明明存在 |
root 是路径拼接(root + location 路径),不是替换 |
error.log 里会打印实际查找的完整路径 |
想替换前缀就用 alias;用 root 时确认拼接后的路径真实存在 |
| 13 | alias 配完 404 或路径多了一层 |
location 以 / 结尾时 alias 也必须以 / 结尾,否则拼接错位 |
error.log 看实际路径 |
两边斜杠保持一致:location /images/ { alias /data/img/; } |
| 14 | 某个 location 死活不生效,请求被别的块接走 | 没搞清优先级:= > ^~ > 正则 > 普通前缀(取最长),正则一旦命中就覆盖前缀 |
nginx -T 看完整生效配置 |
精确匹配用 =,要挡住正则用 ^~,别指望「最长前缀」赢过正则 |
| 15 | 跨域请求被浏览器拦截 | Access-Control-Allow-Origin 写成 http:192.168.228.128,少了一个斜杠 |
F12 Console 看 CORS 报错原文 | 改成 http://192.168.228.128,或调试期先用 * |
| 16 | 自签证书启动报 ee key too small |
Rocky 9 的 OpenSSL 3 默认安全等级拒绝 1024 位密钥 | `openssl x509 -in server.crt -noout -text | grep "Public-Key"` |
| 17 | 浏览器报 SSL_ERROR_RX_RECORD_TOO_LONG |
用 https:// 访问了没开 SSL 的端口,或 listen 443 漏写 ssl |
curl -I https://域名、`ss -tlnp |
grep :443` |
| 18 | 反向代理 502 / 超时 | 后端 IP 写错(192.168 写成 192.128/192.169),或后端服务没起 | tail -f logs/error.log、`ps -ef |
grep nginx`(后端机) |
| 19 | 反向代理出现无限 302 死循环 | if (!-f $request_filename) 对目录 / 永远判定「不存在」 |
浏览器看地址栏反复跳转;access.log 刷屏 |
改用 if (!-e $request_filename),-e 同时判断文件或目录 |
| 20 | 重启虚拟机后站点全挂,ps 里没有 nginx |
手动启动的 nginx 不会开机自启 | `ps -ef | grep nginx、ip addr` 确认 IP 是否漂移 |
7.2 三条通用排查心法
清单之外,再补三条贯穿全文的排查思路,遇到表里没有的新问题也能照着走:
- 先看语法,再看日志,最后看网络 。
nginx -t能挡掉一大半配置错误;tail -f logs/error.log会直接告诉你文件找不到、端口被占、证书不匹配;前两步都过了还访问不通,才去查防火墙、SELinux、IP 漂移。 - 改完必 reload,reload 前必 -t 。
nginx -t通过再nginx -s reload,是全文出现次数最多的一条纪律------不 reload 配置不生效,语法错就 reload 会让旧 worker 继续跑旧配置,看起来「改了没用」。 - 怀疑缓存时,先排除浏览器 。配置类问题排查到一半发现「怎么改都一样」,八成是浏览器强缓存或 DNS 缓存。用
curl -I绕过浏览器直接看响应头,能立刻区分是服务端没生效还是客户端没刷新。
一句话收尾:这张表不用背,收藏起来按现象搜就行。真正要记住的是那三条心法------语法、日志、网络,按顺序走,绝大多数 Nginx 问题都能在十分钟内定位到根因。