Nginx 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器,以高并发处理能力和低资源消耗著称。安装 Nginx 只需要几条命令,几分钟就能跑起来;但一个网站真正的行为——它怎么分发请求、怎么代理到后端、怎么处理旧链接——全都写在配置文件里。本文只会简单带过安装,把大部分篇幅留给配置:配置文件是怎么组织的、location 匹配的优先顺序是什么、反向代理怎么写、静态资源怎么缓存、旧的 URL 怎么迁移。最后一节会给出本站生产环境实际在用的配置,作为一个可以直接对照阅读的完整例子。
1. 安装 Nginx
日常使用直接装包管理版本就够了:
sudo apt install nginx && sudo systemctl enable --now nginx # Debian/Ubuntu
sudo dnf install nginx && sudo systemctl enable --now nginx # CentOS/Fedora
配置文件在 /etc/nginx/nginx.conf,站点相关的配置通常放在 /etc/nginx/conf.d/*.conf(RPM 系)或 sites-available/ + sites-enabled/(Debian 系)。只有需要特定版本、或者需要包管理版本没编译进去的模块时,才值得从源码编译:装好编译依赖后 ./configure 加上要开启的模块、make && make install 即可,./configure 的参数只在编译时生效,以后想加模块就得重新编译整个二进制:
sudo apt install build-essential libpcre3-dev zlib1g-dev libssl-dev
wget http://nginx.org/download/nginx-<version>.tar.gz && tar -zxvf nginx-<version>.tar.gz
cd nginx-<version> && ./configure --prefix=/usr/local/nginx --with-http_ssl_module && make && sudo make install
2. 配置文件是怎么组织的
Nginx 的配置由一系列**指令(directive)和块(context)**构成,块之间是嵌套关系,内层块会继承外层的配置,也可以覆盖它。核心的几层是:
main(全局)
├─ events { ... } 连接处理相关,比如 worker_connections
├─ http { ... } HTTP 服务的总配置
│ ├─ server { ... } 一个虚拟主机
│ │ └─ location { ... } 一条 URL 匹配规则
│ └─ server { ... } 另一个虚拟主机
├─ mail { ... } 邮件代理相关(用得较少)
└─ stream { ... } TCP/UDP 四层代理
- main 层的指令直接写在文件最外层,例如
worker_processes(worker 进程数,一般设成auto让 Nginx 按 CPU 核心数自动决定)、pid(主进程 PID 文件位置)。 - events 块只关心连接层面的参数,最常调的是
worker_connections,即单个 worker 能同时维持的连接数上限。 - http 块是绝大多数配置真正发生的地方:日志格式、gzip、超时,以及下面挂的所有
server。 - 一个
http块里可以有多个server块,靠listen和server_name区分不同的虚拟主机;一个server块里可以有多个location块,靠 URL 路径区分不同的处理方式。
实际项目里,http 块通常不会把所有内容写在一个文件里,而是用 include 把配置拆开:
http {
include /etc/nginx/mime.types;
include /etc/nginx/conf.d/*.conf;
...
}
这样每个站点、每个功能可以单独维护一个文件,改动互不干扰,也方便按需 include 一些独立的映射表(下面第 6 节会用到)。
3. server 块:一个虚拟主机长什么样
server {
listen 80;
server_name example.com www.example.com;
location / {
root /var/www/html;
index index.html index.htm;
}
error_page 404 /404.html;
location = /404.html {
root /var/www/html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/html;
}
}
listen 决定监听的端口(和可选的 IP),server_name 决定这个块响应哪些域名——同一个端口上可以挂很多个 server_name 不同的 server 块,Nginx 会按 Host 请求头匹配到对应的一个。root 指定这个虚拟主机的文件根目录,index 指定目录请求时依次尝试的默认文件。error_page 把特定状态码的响应重写到指定路径,常用来做统一的 404/50x 页面。
4. location 匹配规则与优先顺序
location 决定”这个 URL 该怎么处理”,Nginx 支持好几种匹配写法,理解它们的优先顺序比记住语法本身更重要:
location = /path精确匹配,只有 URL 完全等于/path才命中,优先顺序最高。location ^~ /prefix前缀匹配,一旦匹配上就不再往下检查正则,优先顺序高于普通正则。location ~ /pattern/location ~* /pattern正则匹配(~区分大小写,~*不区分),按配置文件中出现的顺序依次尝试。location /prefix普通前缀匹配,如果没有更优先的规则命中,取匹配最长的那个前缀。
也就是说,Nginx 不是”从上到下第一个匹配就用”,而是按这四类的优先顺序依次筛选,只有在精确匹配和 ^~ 前缀都没命中时才会去看正则。写多个 location 时最容易踩的坑就是以为它们是顺序生效的——实际上除了同类正则之间是顺序生效,其他类型之间是按优先顺序,不是按书写顺序。
location = /healthz {
return 200 "ok\n";
}
location ~* \.(?:jpg|jpeg|png|svg|webp)$ {
expires 30d;
add_header Cache-Control "public";
}
location / {
try_files $uri $uri/ =404;
}
try_files 是 location 里常用的一个指令:按顺序尝试后面列出的路径,第一个存在的就用它响应,全部不存在则用最后一个(这里是返回 404)。单页应用常见的写法是 try_files $uri $uri/ /index.html;,找不到静态文件时统一 fallback 到入口页面,交给前端路由处理。
5. 反向代理与 upstream
把 Nginx 放在后端服务前面做反向代理,核心指令是 proxy_pass:
upstream app_backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
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;
}
}
upstream 块定义一组后端,默认按顺序轮询(round-robin),也可以加权重(server 127.0.0.1:3000 weight=3;)或换成 least_conn 等策略。转发请求时,后端默认只能看到来自 Nginx 的连接,看不到真实客户端信息,所以要靠 proxy_set_header 把原始的 Host、客户端 IP(X-Real-IP/X-Forwarded-For)和协议(X-Forwarded-Proto)透传过去,后端应用需要读这些头才能拿到真实的访问者信息,尤其是在做访问日志或按协议跳转时。
TLS 终止通常也放在反向代理这一层:证书配置在面向公网的 server 块(listen 443 ssl;、ssl_certificate、ssl_certificate_key),Nginx 到后端之间可以继续用明文 HTTP,减少后端管理证书的负担。
6. 静态资源、压缩与缓存
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
location ~* ^/(?:_astro|assets)/ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
gzip 系列指令按类型压缩响应体,gzip_min_length 避免对本来就很小的响应做无意义的压缩。对于文件名里带 hash(构建产物常见做法)的静态资源,可以放心地给一个很长的 expires 加上 immutable:既然内容变了文件名一定会变,浏览器长期缓存旧文件名也不会拿到过期内容。没有 hash 的资源就不能这样设置,否则更新后用户可能长期看到旧版本。
7. 重定向与遗留 URL 迁移
网站改版、URL 结构调整之后,旧的描述性 slug 需要迁移到新 slug。做法是根据构建产物的 canonical URL 生成”旧路径 → 新路径”映射表,再由 Nginx 统一返回 301。本站的 canonical-paths.map 也处理尾部斜线、index.html、语言后缀和已淘汰的列表路径:
map $request_uri $request_path {
~^([^?]*) $1;
}
map $uri $canonical_path {
default $request_path;
include /etc/nginx/canonical-paths.map;
}
server {
...
if ($request_path != $canonical_path) {
return 301 https://aoi.ai$canonical_path$is_args$args;
}
}
map 本身不做跳转,只是根据请求路径算出 canonical 路径;真正跳转的是后面那句 return 301。映射表由实际生成的 HTML 自动建立,因此新增页面不需要手写 location。$is_args$args 保留原始查询字符串,绝对的 HTTPS apex 目标则同时收敛协议、主机名和路径,避免重定向链。
8. 一份完整的生产配置
把上面几节拼起来,就是本站生产环境实际在用的配置(略去了证书相关配置):
map $request_uri $request_path {
~^([^?]*) $1;
}
map $uri $canonical_path {
default $request_path;
include /etc/nginx/canonical-paths.map;
}
server {
listen 8000;
listen [::]:8000;
server_name _;
root /usr/share/nginx/html;
index index.html;
charset utf-8;
absolute_redirect off;
server_tokens off;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml application/javascript application/json application/rss+xml image/svg+xml;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
if ($request_path != $canonical_path) {
return 301 https://aoi.ai$canonical_path$is_args$args;
}
location ~* ^/(?:_astro|article-assets)/ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
location / {
try_files $uri $uri/ $uri/index.html =404;
}
}
几个细节值得单独说一下:
server_tokens off;让错误页面和响应头不再暴露 Nginx 的具体版本号,减少一点被针对性扫描的信息面。add_header ... always;里的always表示即使响应是错误页(4xx/5xx)也要带上这个头,默认情况下add_header在某些错误响应里会被跳过。/healthz单独关闭了access_log,因为健康检查通常是几秒一次的高频请求,全部记录只会让日志被噪音淹没。- 最后的
try_files $uri $uri/ $uri/index.html =404;是静态站点常见写法:先找精确文件,再找目录,再找目录下的index.html,都找不到才返回 404——这是为了兼容 Astro 之类静态站点生成器输出的about/index.html这种目录结构。
9. 校验与重新加载
改完配置不要直接重启,先校验语法:
sudo nginx -t
确认没有报错之后,用 reload 而不是 restart:
sudo systemctl reload nginx
# 或者
sudo nginx -s reload
reload 会让 Nginx 平滑地用新配置启动新的 worker 进程,等旧 worker 处理完当前连接后再退出,中间不会丢请求;restart 则会先杀掉进程再重新启动,短时间内会有服务中断。日常改配置、换证书、调整超时,都应该用 reload。