开玩笑的,其实nginx和apache一样好用
apache移步:Apache入门
什么是Nginx
Nginx(发音为 “engine-X”)是一款开源的、高性能的HTTP和反向代理服务器
它的设计哲学是提供极致的性能、稳定性、丰富的功能、简单的配置和低资源消耗,看上去就很厉害
Nginx采用事件驱动的异步非阻塞架构
与传统的Apache等服务器为每个请求创建一个新进程或线程不同,Nginx使用一个主进程和少数几个工作进程
主进程负责读取配置、管理工作进程,而真正处理网络请求的是工作进程
每个工作进程都是单线程的,异步地处理成千上万个并发连接
这种模型避免了创建和销毁进程/线程的开销以及上下文切换的成本,因此能以极低的内存占用应对高并发场景
接下来我们会介绍直接安装的Nginx,也会顺带介绍通过Docker容器启动的Nginx
二者在活动状态控制和日志方面有些区别,配置基本一致
主要用法
Web服务器
直接向客户端提供静态资源(如HTML、CSS、图片)的服务
由于其高效的文件读取和网络传输能力,Nginx在处理静态内容方面表现极其出色,适合搭建博客
反向代理服务器
它是是客户端和后端真实服务器之间的中间人
它可以将客户端请求转发到后端的应用服务器(如Node.js、Java、Python应用),并将后端响应返回给客户端,从而实现请求分发、负载均衡,并能隐藏后端服务的真实IP和端口,提升安全性
负载均衡器
当后端有多台服务器时,Nginx可以根据预设的策略将请求分发到这些服务器上,从而分担单一服务器的压力
API网关
在微服务架构中,Nginx可以作为所有API请求的统一入口,执行如身份验证、速率限制、日志记录、服务发现等通用功能
目录结构
不同来源
通过不同方式安装的nginx的默认目录结构都会有所差别
官方Nginx包(从 nginx.org)
从Nginx下载官方提供的.rpm/.deb包安装
/ # 根目录
├── etc/
│ └── nginx/ # 配置文件目录
│ ├── nginx.conf # 主配置文件(Nginx 的核心配置)
│ ├── mime.types # MIME类型映射表
│ └── conf.d/ # 子配置目录(用于虚拟主机)
│ └── default.conf # 默认网站配置文件
│
├── usr/
│ ├── sbin/
│ │ └── nginx # 主程序二进制文件
│ └── share/
│ └── nginx/
│ └── html/ # 默认 Web 根目录
│ ├── index.html # 默认欢迎页面
│ └── 50x.html # 服务器错误页面(如 500、502)
│
├── var/
│ └── log/
│ └── nginx/ # 日志文件目录
│ ├── access.log # 访问日志
│ └── error.log # 错误日志
│
└── lib/
└── systemd/ # 服务管理相关
└── system/ # Nginx systemd 配置
└── nginx.service
源码编译安装
从Nginx官方下载源码.tar.gz,自己编译安装
/ # 根目录
└── usr/
└── local/
└── nginx/
├── sbin/
│ └── nginx # 主程序二进制文件
├── conf/
│ ├── nginx.conf # 主配置文件
│ ├── mime.types # MIME类型映射表
│ └── conf.d/ # 子配置目录
│ └── default.conf # 默认网站配置文件
├── html/ # 默认 Web 根目录
│ ├── index.html # 默认欢迎页面
│ └── 50x.html # 服务器错误页面
└── logs/ # 日志文件目录
├── access.log # 访问日志
└── error.log # 错误日志
Debian/Ubuntu官方仓库
通过apt安装
虽然apt也是调用dpkg安装.deb包,但debian/ubuntu维护的.deb包和官方打包的不太一样,因此web根目录等等路径和直接使用官方包安装会有所差别
一般偏向Debian系列的“sites-available / sites-enabled” 设计,也就是apache那样
/
├── etc/
│ └── nginx/ # 配置文件目录
│ ├── nginx.conf # 主配置文件
│ ├── mime.types # MIME类型映射表
│ ├── conf.d/ # 可选,通常空的,给用户放额外配置
│ ├── sites-available/ # 所有站点配置(未必启用)
│ │ └── default # 默认网站配置文件
│ └── sites-enabled/ # 启用的站点(使用符号链接到sites-available即为启用)
│ └── default -> ../sites-available/default
│
├── usr/
│ └── sbin/
│ └── nginx # 主程序二进制文件
│
├── var/
│ ├── www/
│ │ └── html/ # 默认 Web 根目录
│ │ ├── index.nginx-debian.html # 默认欢迎页面
│ │ └── 50x.html # 服务器错误页面
│ └── log/
│ └── nginx/ # 日志文件目录
│ ├── access.log
│ └── error.log
│
└── lib/
└── systemd/
└── system/
└── nginx.service # systemd 单元文件
CentOS/RHEL官方仓库
通过yum/dnf安装
路径、配置和布局就是官方Nginx提供的.rmp包规范的结构,所以和第一种很像
/ # 根目录
├── etc/
│ └── nginx/ # 配置文件目录
│ ├── nginx.conf # 主配置文件
│ ├── mime.types # MIME类型映射表
│ └── conf.d/ # 子配置目录(用于虚拟主机)
│ └── default.conf # 默认网站配置文件
│
├── usr/
│ └── sbin/
│ └── nginx # 主程序二进制文件
│
├── usr/
│ └── share/
│ └── nginx/
│ └── html/ # 默认 Web 根目录
│ ├── index.html # 默认欢迎页面
│ └── 50x.html # 服务器错误页面
│
├── var/
│ └── log/
│ └── nginx/ # 日志文件目录
│ ├── access.log # 访问日志
│ └── error.log # 错误日志
│
└── usr/
└── lib/
└── systemd/ # 服务管理相关
└── system/ # Nginx systemd 配置
└── nginx.service
Docker镜像(官方 Nginx)
没有固定结构
因为Docker镜像只是一个文件系统的快照,完全取决于镜像制作者,具体看上面四种
这里以官方镜像为例
/ # 根目录
├── etc/
│ └── nginx/ # 配置文件目录
│ ├── nginx.conf # 主配置文件(Nginx 的核心配置)
│ ├── mime.types # MIME类型映射表
│ └── conf.d/ # 子配置目录(用于虚拟主机)
│ └── default.conf # 默认网站配置文件
│
├── usr/
│ ├── share/
│ │ └── nginx/
│ │ └── html/ # 默认的 Web 根目录
│ │ ├── index.html # 默认欢迎页面
│ │ └── 50x.html # 服务器错误页面(如 500、502)
│ └── sbin/
│ └── nginx # 主程序二进制文件
│
└── var/
└── log/
└── nginx/ # 日志文件目录(部分精简镜像无日志输出)
├── access.log # 访问日志(默认可能未启用)
└── error.log # 错误日志(默认可能未启用)
宝塔面板安装的Nginx
宝塔不会用系统默认路径,而是自定义了一套目录结构
这其实是方便面板统一管理多个网站、日志和SSL证书,也方便我们寻找
/ # 根目录
├── www/
│ ├── wwwroot/ # 网站根目录(每个站点一个子目录)
│ │ ├── example.com/ # example.com 站点目录
│ │ │ ├── index.html # 网站首页
│ │ │ └── ... # 网站源码/文件
│ │ └── OtherSite.com/ # 其他站点
│ │
│ ├── wwwlogs/ # 所有网站的日志(不区分文件夹)
│ │ ├── example1.com.log
│ │ └── example1.com.error.log
│ │ └── example2.com.log
│ │ └── example2.com.error.log
│ │ └── ...
│ │
│ └── server/ # 宝塔自身运行目录
│ ├── panel/ # 宝塔面板核心
│ ├── nginx/ # Nginx 配置与二进制
│ │ ├── conf/nginx.conf # 主配置文件
│ │ ├── conf/vhost/ # 各站点虚拟主机配置
│ │ │ ├── example.com.conf
│ │ │ └── other-site.com.conf
│ │ └── logs/ # Nginx 全局日志
│ │ ├── access.log
│ │ └── error.log
│ └── php/ # PHP 版本管理
│ ├── 74/ # PHP 7.4
│ └── 81/ # PHP 8.1
│
└── etc/
└── init.d/nginx # 启动脚本(也可能链接到 systemd)
一些问题
日志文件
在编译安装(make install)的传统方式中,Nginx日志会记录在:
/usr/local/nginx/logs/
在.deb包和系统包安装(apt/yum)的方式中,Nginx日志会记录在:
/var/log/nginx/
在宝塔面板安装方式中,日志位置有两个:
/www/wwwlogs/ # 每个站点一个日志文件,包含访问和错误日志
/www/server/nginx/logs/ # Nginx全局日志(access.log、error.log)
访问日志:如果某个站点配置了自己的access_log,那么它的请求不会再写到全局access.log
错误日志:全局错误(服务器级别的错误,而不是单个站点的错误),以及所有站点的严重错误(crit、alert、emerg,具体看:错误等级 ),会进入全局error.log
在Docker官方镜像中,日志默认不写入上面的文件,而是输出到标准输出和标准错误
执行以下命令:
ls -l /var/log/nginx/
我们可以看到如下输出:
lrwxrwxrwx 1 root root 11 Jul 15 01:14 access.log -> /dev/stdout
lrwxrwxrwx 1 root root 11 Jul 15 01:14 error.log -> /dev/stderr
虽然nginx.conf配置的是/var/log/nginx/access.log,但由于它是个符号链接,等于就是输出到了标准输出
这些输出会被记录到docker容器的日志里,docker中查看日志的方式:
docker logs <容器ID>/<名称>
配置文件
Nginx的主配置文件一般在:
/etc/nginx/nginx.conf
在这个文件中常见的一行是:
include /etc/nginx/conf.d/*.conf;
include是Nginx配置语言中的指令,用于引入其他配置文件
不同安装方式下,include的目录略有差异:
-
CentOS / RHEL / Fedora官方包
include /etc/nginx/conf.d/*.conf; -
Debian / Ubuntu官方包
include /etc/nginx/sites-enabled/*;配置分两部分:
/etc/nginx/sites-available/:存放所有站点配置/etc/nginx/sites-enabled/:通过符号链接指向/etc/nginx/sites-available/下的配置,以启用站点 -
源码编译安装
通常不会使用
include,也不会自动拆分conf.d/或sites-enabled/,默认是:/usr/local/nginx/conf/nginx.conf -
宝塔面板安装
宝塔为了方便面板管理,使用了自己的路径布局,主配置文件:
/www/server/nginx/conf/nginx.conf在这个文件中,宝塔会包含:
include /www/server/panel/vhost/nginx/*.conf;每个站点在
/www/server/panel/vhost/nginx/下生成一个独立的.conf文件,例如:/www/server/panel/vhost/nginx/example.com.conf /www/server/panel/vhost/nginx/test.com.conf这就是为什么宝塔能通过Web面板为每个站点单独管理配置
Web根目录
不同安装方式下,Nginx的默认Web根目录不同
在编译安装(make install)的传统方式中,Web根目录在:
/usr/local/nginx/html/
默认包含以下文件:
index.html # 默认欢迎页面
50x.html # 服务器错误页面
在Debian/Ubuntu系统包安装方式中,Web根目录在:
/var/www/html/
在官方包和CentOS/RHEL系统包安装方式中,Web根目录在:
/usr/share/nginx/html/
在宝塔面板安装方式中,Web根目录在:
/www/wwwroot/<domain>/
domain是不同的站点,宝塔会为他们各自创建一个根目录,例如:
/www/wwwroot/example.com/
/www/wwwroot/www.gSJKsu2kig.com/
可以通过修改Nginx配置文件中的root指令来改变Web根目录:
server {
listen 80;
server_name localhost;
root /path/to/your/webroot;
index index.html;
}
指定根目录在管理的时候很有用,因为默认都挤在一起了,关于server块我们后面会细讲
二进制文件位置
系统包安装(apt/yum/dnf 安装)
一般放在sbin,而不是bin
/usr/sbin/nginx
源码编译安装
编译安装时位置完全取决于--prefix参数,下面是默认位置:
/usr/local/nginx/sbin/nginx
宝塔安装
/www/server/nginx/sbin/nginx
/www/server/nginx/conf/nginx.conf
但是它会在/usr/bin/nginx或/usr/sbin/nginx下做一个软链接,指向上面的位置,方便在命令行里直接运行 nginx
systemd的位置
其实这个不是nginx的问题,而是一个历史问题 —— linux文件系统的演进
**FHS,全称Filesystem Hierarchy Standard(文件系统层次结构标准)**是 Linux 系统的一个标准,规定了 目录结构、文件存放位置及用途,让不同 Linux 发行版在文件布局上保持一致性
在早期,FHS规定/lib/用于存放核心库文件和与系统启动紧密相关的可执行文件(比如init系统所需的库、二进制),而/usr/lib/用于存放非核心、额外安装的软件库
但在systemd出现后,由于systemd的服务单元文件属于系统服务配置,与系统启动密切相关,但又不是二进制程序本身,于是他的位置有了一些不同:
-
Debian/Ubuntu
选择
/lib/systemd/system/放置核心包的单元文件(比如nginx、sshd),保证系统启动时总能找到 -
CentOS/RHEL/Fedora
更倾向使用
/usr/lib/systemd/system/,虽然/lib/systemd/system/也存在,但它通常是指向前者的符号链接 -
宝塔面板
遵循上面两条,但一般会在
/etc/init.d/nginx或/etc/init.d/bt下放置额外的启动脚本,以便面板通过Web界面统一管理
现在/lib/和/usr/lib/往往通过软链接或包管理器自动管理,保证旧路径和新路径都能访问到服务单元文件,所以我们会看到同样的Nginx服务在不同发行版或者不同安装方式下,路径可能不同
常用命令
Nginx直接安装在本机时,使用systemctl或者service命令控制
通过Docker容器使用时,就按照Docker容器的使用方法(没用过看这里:快速上手Docker )
下面介绍一些Nginx本身的命令:
查看版本
nginx -v
输出当前安装的Nginx版本号,用于确认是否正确安装Nginx及其版本信息
检查配置文件语法
nginx -t
在每次修改配置文件后必须执行
使用后,主进程会fork一个临时子进程,子进程尝试解析所有配置文件,但不会绑定端口或真正启动服务
若语法无误,会提示syntax is ok和 test is successful
若有错误,会明确指出是哪一行、哪个文件配置有误
重新加载配置文件
nginx -s reload
强制停止
nginx -s stop
配置文件
Nginx的所有功能都是通过配置文件nginx.conf来驱动的
该文件主要由多个配置块组成,这些块由花括号{}界定,可以嵌套,形成了层级分明的结构
一个典型的nginx.conf结构如下:
nginx.conf
├── 全局块
├── events块
└── http块
└── server块(可多个)
└── location块(可多个)
具体示例如下:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
}
全局块
位于配置文件顶部,未嵌套于任何块中
用于配置Nginx整体运行环境,影响所有子模块
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
-
user:指定运行工作进程的用户和用户组 -
worker_processes:指定工作进程数量auto表示自动与 CPU 核心数一致 -
error_log:设置错误日志的路径和日志级别每条错误日志条目都会带有一个严重性等级,这些等级是代码里写死的,作为用户无法改变某个错误属于什么等级
Nginx支持的日志级别(从高到低)如下:
级别 描述 debug记录所有信息,主要用于开发或调试,信息量极大,通常需编译时启用 info普通信息,如配置加载成功、进程启动、连接创建等 notice正常但重要的事件,如配置文件重载、进程关闭等 warn警告信息,非致命错误,比如配置中存在问题但可以忽略或继续运行 error运行过程中出现的错误,如连接失败、服务不可达等 crit严重错误,Nginx可能无法继续运行 alert必须立刻处理的严重问题 emerg紧急状态,比如系统崩溃,Nginx无法启动 是否写入日志文件取决于
LogLevel设置的阈值,只会写入大于等于当前设置等级的错误事件生产环境通常设置为
warn以捕捉重要问题,同时避免日志泛滥 -
pid:指定记录主进程PID的文件路径
events块
紧随全局块之后,一级块
用于配置与网络连接处理相关的指令,影响工作进程的并发性能
events {
worker_connections 1024;
}
-
worker_connections:每个工作进程可同时处理的最大连接数总并发连接数约为
worker_processes * worker_connections
http块
紧随events块之后,一级块
用于配置HTTP协议相关的指令,是配置Web服务的核心部分
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
server {
...
}
}
-
include:引入外部文件这里
/etc/nginx/mime.types用于MIME类型配置 -
default_type:未明确 MIME 类型时的默认类型 -
log_format:定义日志名称与格式 -
access_log:指定访问日志的输出路径及格式 -
sendfile:启用高效文件传输方式 -
tcp_nopush:优化网络传输性能,避免频繁发送小包 -
keepalive_timeout:设置长连接超时时间
server块
嵌套于http块内部,二级块,可存在多个,用于配置多个虚拟主机
用于定义单个虚拟主机的配置,通过监听端口和服务器名称对请求进行分发
server {
listen 80;
server_name localhost;
location / {
...
}
}
-
listen:设置监听的端口只有访问主机该端口的请求才会被接受
不过得注意,如果使用docker,docker容器一般会把这个端口转发到主机的某个端口上
基本用法:
listen <地址>:<端口> [参数];地址(可选):指定绑定的 IP(默认是所有IP)端口:监听的端口参数(可选):例如default_server、ssl、http2等(我们后面都会提到)有一个比较特殊的参数:
default_server,用来指定默认的server块,当Nginx收到请求但找不到任何server_name匹配时,就会使用这个默认配置来处理请求介绍几种常见
listen字段写法:80:监听所有IP的80端口(80是默认HTTP端口),相当于0.0.0.0:8080127.0.0.1:8080:仅监听本地回环地址的8080端口[::]:80:IPv6地址监听80端口 -
server_name:匹配请求的主机名或域名只有请求头中的Host字段符合,Nginx才会把请求交给这个
server块处理可以在
server_name中写多个值,也支持通配符*,也可以使用_表示任何Host值如果不写这个字段,http块又只有一个server,那么这个server块会所有请求都匹配(包括 IP)
如果不写这个字段,http块又有多个server,那么这个server块会作为默认server,处理未匹配的请求
location块
嵌套于server块内部,三级块
用于对URI(注意不是URL)进行匹配,制定请求的处理方式,如资源路径、代理规则等
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
-
location:用于匹配客户端请求的URI示例这里是匹配以
/开头的URI(实际上就是匹配所有请求,因为URI就是以/开头的)此外,Nginx提供多种匹配方式,优先级不同:
类型 语法 示例 说明 精确匹配 =location = /login {}仅匹配 URI 等于 /login的请求前缀匹配 /location /images/ {}匹配以 /images/开头的 URI正则匹配 ~(区分大小写)~*(不区分大小写)location ~ \.php$ {}使用正则表达式匹配 URI 优先匹配 ^~location ^~ /static/ {}前缀匹配优先于正则匹配 -
root:设置请求资源的根目录路径当一个请求到达时,Nginx会将请求的URI拼接到这个路径后面,以确定文件的具体位置
如果客户端访问
/index.html,那么实际访问的是服务器上的/usr/share/nginx/html/index.html文件 -
index:指定访问目录时的默认文件名称如果用户访问的是目录而不是具体文件,Nginx会在
/usr/share/nginx/html/目录下按顺序查找:index.htmlindex.htm
只要找到其中一个文件,就立即返回该文件作为响应;如果都找不到,会返回403(禁止访问)或404(未找到),视配置而定
root和index也可以直接写在server块里面,作为所有location块的默认值
说了这么多,我们最后总结一下listen、server_name、location 三者是如何配合工作的:
用户访问浏览器输入:
http://localhost/images/logo.png
Nginx内部处理流程如下:
-
看端口: 该请求是发给哪个端口,是80,那就匹配所有listen 80的server
-
看Host头: Host是localhost,找哪个server_name匹配上了,进入该server块
-
看URI: URI是/images/logo.png,在这个server中找哪个location匹配得最好,执行该location的规则
核心功能 —— Web服务
Web 服务是 Nginx 最基础,也是最核心的功能,它能够高效地处理客户端对静态资源的请求
这主要通过server块中的root、index、location来协同完成
部署静态资源
可以把Nginx作为静态资源服务器,当用户请求时,Nginx从服务器的指定文件路径中查找并返回对应的文件:
server {
listen 80;
server_name example.com;
root /var/www/my-project/public;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp)$ {
expires 30d;
}
}
-
try_files $uri $uri/ =404这是一个非常强大的指令,常用于处理单页应用(SPA)的路由,它的语法是:
try_files file1 file2 ... final_action;file1, file2, ...:依次判断路径(支持变量),是否存在有效文件或目录final_action:如果前面都没找到,则执行的动作,常用的有:=404:返回404错误@named_location:跳转到命名的location块uri:重定向到指定URI
现在我们看看示例里面的是什么意思:
try_files $uri $uri/ =404-
$uri:尝试将URI直接作为文件名进行查找 -
$uri/:如果上一步失败,则尝试将URI作为一个目录名,并在该目录下查找由index指令定义的文件 -
=404:如果前两步都失败,则返回一个404 Not Found错误
-
location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp)$这是一个正则匹配,
~*表示不区分大小写的正则,它会匹配所有以上述后缀结尾的请求 -
expires 30d用于设置HTTP响应头中的
Expires和Cache-Control告诉客户端浏览器可以将这些静态资源缓存30天,从而减少不必要的请求,提升后续访问速度
URL重写与跳转
Nginx支持在location块内实现URL重写和跳转,常用于:
- 伪静态(SEO优化)
- 页面重定向
- 接口调整兼容
- HTTPS 强制跳转等场景
rewrite
rewrite是Nginx实现伪静态、内部跳转的主要方式
它的本质是把用户请求的路径改写成另一个内部路径,再由Nginx重新处理
基本语法
rewrite <正则匹配> <重写路径> [flag];
-
正则匹配:对当前请求URI进行匹配(不包含域名)
-
重写路径:替换匹配url的新URI,可引用正则中的子表达式
-
flag 可选标志:
重写方式 用法 含义 last使用新URI,从头开始匹配 location用于常规重写,不会改变浏览器地址栏 break使用新URI,但不再重新匹配 location跳出 rewrite,在当前location内继续执行
不会改变浏览器地址栏permanent返回301,永久重定向 会改变浏览器地址栏 redirect返回302,临时重定向 会改变浏览器地址栏
伪静态处理
将 /product/123.html 重写为 /product.php?id=123:
rewrite ^/product/([0-9]+)\.html$ /product.php?id=$1 last;
隐藏真实路径
location /user/ {
rewrite ^/user/([a-z]+)$ /profile.php?name=$1 last;
}
return
return用于向客户端返回一个特定的状态码和内容,它不会再走rewrite的内部处理流程
比起rewrite,它更适合用于明确的重定向、报错处理等
基本语法
return <状态码> [文本|URL];
- 状态码:如301(永久重定向)、302(临时重定向)、403(禁止访问)、404(页面不存在)
- 文本或URL:可返回跳转地址,也可直接返回文本内容(仅在状态码为200时有效)
HTTP强制跳转HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
这一点我们下面会详细说明
禁止访问某个目录
location /private/ {
return 403;
}
简洁跳转到首页
location = / {
return 301 /home/index.html;
}
纯文本响应
location = /test {
return 200 'Hello.';
}
配置HTTPS服务
为网站启用HTTPS可以对客户端和服务器之间的传输数据进行加密,确保数据安全
配置前,我们需要从证书颁发机构(CA)获取SSL/TLS证书文件和私钥文件
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384';
ssl_prefer_server_ciphers off;
root /var/www/my-project/public;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
-
listen 443 ssl http2-
443是HTTPS的标准端口 -
ssl参数表示在此端口上启用SSL/TLS加密 -
http2参数表示同时启用HTTP/2协议,它可以显著提升页面加载性能
-
-
ssl_certificate指定证书文件的路径,这通常是
.pem或.crt格式的公钥证书链文件 -
ssl_certificate_key指定与证书配对的私钥文件的路径,通常是
.key格式 -
ssl_session_cache开启SSL会话缓存,用于复用TLS握手过程,提升性能
-
ssl_session_timeout设置SSL会话的有效时间
-
ssl_protocols TLSv1.2 TLSv1.3指定只使用安全的TLS协议版本,废弃老旧且不安全的SSLv3, TLSv1.0, TLSv1.1
-
ssl_ciphers指定加密时使用的加密套件列表,配置安全的套件可以防止降级攻击
-
ssl_prefer_server_ciphers是否优先使用服务器端指定的加密套件,
off表示客户端优先 -
return 301 https://$server_name$request_uri发送一个永久重定向的HTTP状态码
$server_name和$request_uri是Nginx变量,分别代表当前请求的域名和完整的 URI(包含参数)这能确保用户访问任何HTTP页面时都能被准确地重定向到对应的HTTPS版本
核心功能 —— 反向代理
反向代理是Nginx的一个核心功能
Nginx作为一个中间层,接收客户端的请求,再将请求转发给后端服务器处理,然后将响应返回给客户端
这种方式的好处包括:
- 可以隐藏后端服务器的真实地址和结构
- 可以统一对外的入口,便于安全控制和运维管理
- 可以实现负载均衡、高可用等复杂功能(后续再讲)
下面是一个最简单的Nginx配置示例,只实现把请求转发到后端服务器的功能:
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
-
proxy_pass http://127.0.0.1:3000;这是反向代理的核心语句,即将匹配到的请求转发到本机的3000端口,由本地某个服务来实际处理请求
默认情况下,Nginx把请求转发给后端时,不会携带原始客户端的信息
如果后端服务要获取用户的真实IP、请求协议、原始域名等信息,就需要使用proxy_set_header来手动设置:
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}
这些proxy_set_header是给后端应用用的:
Host $host:原始请求中的主机名X-Real-IP $remote_addr:客户端的真实 IPX-Forwarded-For $proxy_add_x_forwarded_for:经过的所有代理 IP 列表X-Forwarded-Proto $scheme:原始请求的协议(http 或 https)
如果我们有多个后端服务(比如多个进程、多个机器),可以用upstream来统一管理后端服务器组:
upstream backend {
server 127.0.0.1:3000;
# server 127.0.0.1:3001; #可以添加多个
}
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://backend; #<--这里不一样了,使用的是upstream
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;
}
}
现在,我们只需要在proxy_pass中写 http://backend即可
具体的地址都集中写在upstream backend块中,方便管理和负载均衡
核心功能 —— 负载均衡
当upstream块中定义了多台server时,Nginx就自然地成为了一个负载均衡器
它会根据指定的策略将请求分发到不同的服务器,以分担流量压力
HTTP负载均衡配置
upstream backend_cluster {
server backend1.example.com weight=5;
server backend2.example.com;
server backend3.example.com backup;
server backend4.example.com down;
}
server {
listen 80;
location / {
proxy_pass http://backend_cluster;
proxy_connect_timeout 5s;
proxy_read_timeout 10s;
}
}
-
weight=5为服务器设置权重,在默认的轮询策略下,权重越高的服务器接收到的请求比例就越高
这里
backend1的请求量大约是backend2的5倍 -
backup将服务器标记为备用服务器
只有当所有非备用的主服务器都无法访问时,Nginx才会将请求转发给它
-
down将服务器标记为永久下线,Nginx不会向它转发任何请求
这通常用于服务器维护
-
proxy_connect_timeout&proxy_read_timeout设置Nginx与后端服务器建立连接的超时时间和读取响应的超时时间
可以防止因个别后端服务响应缓慢而导致请求长时间挂起
负载均衡策略
轮询
默认策略,无需任何指令
请求被按顺序、轮流分发到每台服务器,并考虑权重
权重(weight)
就像我们之前提到过的例子,weight越高,分配的请求越多
此策略适合后端性能不一致的情况(强机多干活)
upstream backend_cluster {
server 192.168.0.1 weight=3;
server 192.168.0.2 weight=1;
}
上例中,第一个服务器将获得大约75%的请求,第二个约25%
IP哈希(ip_hash)
根据客户端IP地址的哈希值来选择服务器,这能确保来自同一个客户端的请求始终被定向到同一台后端服务器
此策略对于需要保持用户会话状态的应用非常重要
upstream backend_cluster {
ip_hash;
server backend1.example.com;
server backend2.example.com;
}
最少连接 (least_conn)
将新请求发送到当前活动连接数最少的服务器
此策略在处理耗时不同或长连接较多的请求时,能使负载分布得更加均匀
upstream backend_cluster {
least_conn;
server backend1.example.com;
server backend2.example.com;
}
核心功能 —— TCP/UDP代理
Nginx不仅能代理HTTP,还能通过stream模块在更底层的传输层(TCP/UDP)进行代理
例如代理数据库连接、游戏服务器、DNS 查询等
stream块必须配置在http块之外,位于主配置文件的顶层,和http是平级的
stream {
upstream mysql_cluster {
server 192.168.1.101:3306 weight=2;
server 192.168.1.102:3306;
}
server {
listen 3307;
proxy_pass mysql_cluster;
proxy_timeout 20s;
}
server {
listen 53 udp;
proxy_pass 8.8.8.8:53;
}
}
可以看到,stream块和http块的写法基本上是一样的,但是细节上还是有些不同:
stream块不支持location,没有路径匹配stream块不解析协议内容,没有proxy_set_header,只转发原始TCP/UDP数据流stream块不支持ip_hash的负载均衡策略
日志分析
为何要分析Nginx日志
在现代 Web 架构中,Nginx 作为流量的入口,记录着大量有用的信息
虽然ELK等日志平台功能强大,但并非所有环境都配备
掌握底层的命令行分析技巧,能让我们在任何服务器上快速、灵活地定位问题
注意:
在处理大型日志文件(如 >1GB)时,应避免直接使用cat读取整个文件,这会消耗大量内存
应使用head,tail,grep,sed,awk等可以流式处理的工具进行查看修改,或先对日志进行切割
实在想要看全部,也应该选择使用less或more
日志类型与格式
访问日志(access.log)
access.log记录了每一个对Nginx服务器的HTTP请求的详细信息
默认格式:combined / main
Nginx官方默认格式是combined,不过现在常见的更多是main(包括之前我们给出的docker容器的nginx)
其实main只是自定义的名字,本质和combined是类似的,甚至很多公司就是直接copy默认的combined格式起个名叫main,但更多的则是增加了一些字段(比如新增$http_x_forwarded_for字段)
我们就以上文提到的配置文件示例里的main格式为例,这也是最为常见的格式之一,理解了就行
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
变量名是Nginx内置的变量,不能随意改名字
| 变量 | 解释 |
|---|---|
$remote_addr |
客户端IP地址 |
$remote_user |
客户端用户名称(用于HTTP认证,通常为-) |
[$time_local] |
服务器本地的请求处理完成时间 |
"$request" |
完整的原始请求行,如 "GET /index.html HTTP/1.1" |
$status |
HTTP响应状态码 |
$body_bytes_sent |
发送给客户端的响应体大小(字节) |
"$http_referer" |
请求的来源页面URL |
"$http_user_agent" |
客户端的浏览器、操作系统等信息 |
"$http_x_forwarded_for" |
(重要) 记录了通过代理服务器访问的客户端真实IP地址 |
来看一个实际的main格式的access.log的条目:
111.111.111.111 - - [28/Apr/2021:14:44:11 +0800] "GET /api/channel/unread.jsp HTTP/1.1" 200 65 "https://www.xxxx.cn/" "Mozilla/5.0..." "123.123.123.123"
-
$remote_addr:111.111.111.111这是Nginx看到的客户端IP,可能是负载均衡/代理的IP,不一定是真实用户的IP
-
$remote_user:- -如果客户端经过了HTTP认证,这里会记录认证用户名,否则为-- 第一个
-:远程用户名(几乎不用,常为空) - 第二个
-:认证用户名(如果用 Basic Auth 才会有)
- 第一个
-
$time_local:28/Apr/2021:14:44:11 +0800这是Nginx记录的本地时间,时区为+0800(北京时间) -
$request:GET /api/channel/unread.jsp HTTP/1.1包含请求方法、URL路径和HTTP协议版本 -
$status:200HTTP状态码,表示请求成功 -
$body_bytes_sent:65响应体大小(字节数),不包含响应头 -
$http_referer:https://www.xxxx.cn/请求来源页面,如果用户是从某个页面跳转过来的,这里会显示该页面URL,若没有则为- -
$http_user_agent:Mozilla/5.0...客户端的User-Agent,表示浏览器或客户端的标识信息 -
$http_x_forwarded_for:123.123.123.123代理服务器通过HTTP头传递的真实客户端IP,用来识别经过代理时的原始用户地址
这条日志告诉我们:
在北京时间021-04-28 14:44:11,有一个客户端访问了接口/api/channel/unread.jsp,使用的是GET请求,HTTP版本是1.1,返回状态码200(成功),响应大小65字节,请求来自页面https://www.xxxx.cn/,使用的客户端是某个Mozilla浏览器/模拟器,其真实IP是123.123.123.123,但请求是经过代理,Nginx看到的源IP是111.111.111.111
增强的自定义格式:JSON
为了便于程序(如 Logstash, Fluentd)解析,JSON格式也是目前的主流选择,属于典型的非默认格式
它的结构清晰,键值对明确,机器解析友好,避免了awk和cut对字段位置的强依赖
log_format json_analytics escape=json
'{'
'"timestamp": "$time_iso8601",'
'"client_ip": "$remote_addr",'
'"request": "$request",'
'"status": $status,'
'"bytes_sent": $body_bytes_sent,'
'"http_referer": "$http_referer",'
'"http_user_agent": "$http_user_agent",'
'"real_ip": "$http_x_forwarded_for",'
'"request_time": $request_time,'
'"upstream_time": "$upstream_response_time"'
'}';
access_log /var/log/nginx/access.log json_analytics;
新增字段:
-
$request_timeNginx从接收到请求第一个字节到发送完最后一个字节的总时长(秒),性能指标
-
$upstream_response_time从Nginx连接到上游后端服务到接收完响应头的时长,用于判断是 Nginx 慢还是后端服务慢
上面一个例子的JSON格式示例如下:
{"timestamp": "2025-08-16T14:30:00+09:00", "client_ip": "111.111.111.111", "request": "GET /api/users HTTP/1.1", "status": 200, "bytes_sent": 1500, "http_referer": "-", "http_user_agent": "curl/7.68.0", "real_ip": "123.123.123.123", "request_time": 0.025, "upstream_time": "0.024"}
JSON日志使用jq工具可以轻松解析:
统计状态码为500的请求数量:
cat access.log | jq 'select(.status == 500)' | wc -l
找出请求时间最长的10个请求:
cat access.log | jq '.request_time' | sort -nr | head -n 10
Docker环境下的日志
上文已经提到过,在Docker中,nginx日志输出到标准输出和标准错误,记录在docker的日志中
虽然日志本身的格式不会变,但Docker的日志驱动可能(取决于设置)会在每行日志前添加额外信息:
[时间戳] [日志流] [原始日志内容]
| 字段 | 含义 | 示例 |
|---|---|---|
| 时间戳 | Docker记录该日志的时间,精确到纳秒 | 2025-08-20T02:30:00.123456789Z |
| 日志流 | 指明日志来源: 标准输出: stdout,对应访问日志标准错误:stderr`,对应错误日志 |
stdout或stderr |
实际Docker日志示例(还是上面的例子):
2025-08-16T05:45:01.123456789Z stdout F 111.111.111.111 - - [28/Apr/2021:14:44:11 +0800] "GET /api/channel/unread.jsp HTTP/1.1" 200 65 "https://www.xxxx.cn/" "Mozilla/5.0..." "123.123.123.123"
-
2025-08-16T05:45:01.123456789ZDocker捕获日志的UTC时间(纳秒精度)
-
stdout F日志来源是stdout,并且标记为F,表示一行完整日志
此外还有P标记,示这是多行日志的一部分,被拆分了,需要和后续/前面的片段合并
可以直接通过管道分析docker logs的输出,但通常会配置Docker的日志驱动将这些日志流自动转发到集中的日志管理平台进行统一分析
比如,统计客户端IP出现次数的前10:
docker logs nginx | awk '{print $4}' | sort | uniq -c | sort -nr | head -n 10
错误日志(error.log)
error.log记录Nginx启动和运行过程中的诊断信息,是排查问题的关键
一般格式
时间戳 [错误级别] pid#tid: *cid 错误描述, client: 客户端IP, server: 服务端, request: "请求行", host: "Host"
| 字段 | 说明 |
|---|---|
| 时间戳 | 错误发生时间 |
| [错误级别] | 当前条目错误的错误等级,具体看:错误等级 |
| pid#tid | Nginx工作进程ID和线程ID |
| *cid | 客户端连接序列号 |
| 错误描述 | 具体的错误信息,例如connect() to 127.0.0.1:9000 failed (13: Permission denied) |
| client | 发生错误的客户端IP |
| server | 请求的服务端 |
| request | 请求行,如"GET /api HTTP/1.1"` |
| host | 客户端请求的目标主机名 |
具体示例:
2025/08/16 14:35:00 [crit] 12345#12345: *6789 connect() to 127.0.0.1:9000 failed (13: Permission denied) while connecting to upstream, client: 192.168.1.10, server: example.com, request: "GET /api HTTP/1.1", host: "example.com"
- 时间戳:
2025/08/16 14:35:00 - 错误级别:
crit(严重错误) - pid#tid:
12345#12345 - *cid:
*6789 - 错误描述:
connect() to 127.0.0.1:9000 failed (13: Permission denied) while connecting to upstream - 客户端:
192.168.1.10 - 服务端:
example.com - 请求:
"GET /api HTTP/1.1" - Host:
example.com
分析:
客户端192.168.1.10请求 /api接口时,Nginx尝试连接上游服务127.0.0.1:9000失败,原因是权限被拒绝(Permission denied)。日志级别为 crit,说明这是严重错误,需要检查Nginx或上游服务的权限设置
分析技巧
网站流量核心指标分析 (PV & UV)
PV (Page View,页面浏览量) 和 UV (Unique Visitor,独立访客) 是衡量网站价值和用户行为趋势的重要指标
统计总UV (根据IP)
提取第1个字段(IP),排序,去重,最后统计独立IP总数:
awk '{print $1}' access.log | sort | uniq | wc -l
统计总PV
直接统计日志总行数即为PV:
wc -l access.log
统计某时间段的UV / PV
统计2021年4月28日14:00-14:59的UV:
grep "28/Apr/2021:14" access.log | awk '{print $1}' | sort | uniq | wc -l
使用sed进行更精确的时间段筛选 (14点到20点):
sed -n '/28\/Apr\/2021:14/,/28\/Apr\/2021:20/p' access.log | awk '{print $1}' | sort | uniq | wc -l
访问来源与异常排查 (IP & URL)
用于判断是否被攻击、接口是否存在Bug或排查流量报警
查询访问最频繁的Top 10 URL
提取第7字段(URL),排序,去重并计数,按计数值倒序排列:
awk '{print $7}' access.log | sort | uniq -c | sort -k1 -nr | head -n 10
查询访问最频繁的Top 10 IP
awk '{print $1}' access.log | sort | uniq -c | sort -k1 -nr | head -n 10
查看指定IP在某天访问了哪些 URL (Top 10)
grep "111.111.111.111" access.log | grep "28/Apr/2021" | awk '{print $7}' | sort | uniq -c | sort -nr | head -n 10
性能与爬虫分析
统计爬取次数
grep -i 'spider\|bot' access.log | wc -l
统计每分钟/每秒的请求数 (Top 20)
每分钟: 截取时间字段($4)的第2到18位(到分钟)
awk '{print $4}' access.log | cut -c 2-18 | sort | uniq -c | sort -nr | head -n 20
每秒: 截取时间字段($4)的第2到21位(到秒)
awk '{print $4}' access.log | cut -c 2-21 | sort | uniq -c | sort -nr | head -n 20
分析请求响应时间 (需自定义日志格式)
前提: 确保log_format中包含了$request_time,我们假设它在最后一个字段
列出传输时间超过3秒的Top10请求
$NF代表最后一个字段,此命令筛选出最后一个字段值大于3的行:
awk '$NF > 3 {print $NF, $7}' access.log | sort -k1 -nr | head -n 10
列出最耗时的Top10请求
awk '{print $NF, $7}' access.log | sort -k1 -nr | head -n 10
+++
暂时就先讲到这,毕竟只是入个门,详细使用就在实践里面学习吧
如果之后我需要更加深入使用,这篇文章也会更新的~