Appearance
Nginx
反向代理、location 匹配、HTTPS 配置、性能调优等核心知识。
基本概念
Nginx 配置由指令组成,分为简单指令(以 ; 结尾)和块指令(以 {} 包裹)。配置文件层级如下:
| 块 | 作用范围 | 常见指令 |
|---|---|---|
main | 全局(文件顶层) | worker_processes、error_log |
events | 连接处理模型 | worker_connections、use epoll |
http | HTTP 服务全局 | gzip、ssl_protocols、limit_req_zone |
server | 单个虚拟主机 | listen、server_name、root |
location | 匹配特定请求路径 | proxy_pass、return、rewrite |
upstream | 后端服务器组 | server、keepalive、ip_hash |
子块可以继承父块指令,但 add_header 等部分指令不继承,需在子块重新声明。
安装
bash
# Debian / Ubuntu
sudo apt update
sudo apt install nginx
# CentOS / RHEL(先添加官方源,获取新版本)
sudo yum install epel-release
sudo yum install nginx
# 启动并设置开机自启
sudo systemctl enable --now nginx
# 验证安装
nginx -vlocation 匹配规则
精确匹配 (=)
只有当请求的 URI 完全匹配指定字符串时才生效,优先级最高,匹配后立即停止搜索。
nginx
location = / {
# 只匹配根路径 "/"
root /usr/share/nginx/html;
index index.html index.htm;
}请求示例:http://example.com/ ✓ http://example.com/index.html ✗
前缀匹配 (^~)
URI 以指定字符串开头时匹配,匹配成功后停止搜索正则规则(不会被正则覆盖)。
nginx
location ^~ /img/ {
# 匹配以 "/img/" 开头的请求,停止搜索其他规则
root /data;
}请求示例:/img/a.jpg ✓ /img/b.mp4 ✓ /Img/a.jpg ✗(大小写敏感)
正则匹配 (~, ~*)
nginx
# 区分大小写(~)
location ~ /Example/ {
# 匹配路径中含 "/Example/" 的请求
root /data;
}
# http://example.com/Example/logo.png ✓
# http://example.com/example/logo.png ✗
# 不区分大小写(~*)
location ~* /Example/ {
root /data;
}
# http://example.com/Example/logo.png ✓
# http://example.com/example/logo.png ✓多个正则规则按配置文件顺序依次匹配,先匹配到的先生效。
通用匹配 (/)
匹配任何请求,优先级最低,其他规则都不匹配时才走此规则。
nginx
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}内部重定向 (@)
通常用于错误处理的内部跳转,@名称不对外暴露,客户端不可直接访问。
nginx
location /img/ {
error_page 404 = @img_err; # 找不到资源时跳转
}
location @img_err {
return 503;
}优先级总结
| 类型 | 符号 | 优先级 |
|---|---|---|
| 精确匹配 | = | 最高 |
| 前缀匹配(停止搜索) | ^~ | 次高 |
| 正则匹配(大小写敏感) | ~ | 按顺序 |
| 正则匹配(大小写不敏感) | ~* | 按顺序 |
| 普通前缀匹配 | 无符号 | 次低 |
| 通用匹配 | / | 最低 |
rewrite 规则
正则常用符号
rewrite 规则依赖正则表达式匹配和捕获分组,常用符号:
| 符号 | 含义 |
|---|---|
. | 匹配除换行符外的任意单个字符 |
* | 匹配前面元素零次或多次 |
+ | 匹配前面元素一次或多次 |
? | 匹配前面元素零次或一次 |
^ | 匹配字符串开头 |
$ | 匹配字符串结尾 |
() | 捕获分组,在替换串中用 $1、$2 引用 |
[] | 定义字符集,如 [0-9] |
\ | 转义特殊字符 |
标志位
| 标志位 | 说明 |
|---|---|
last | 停止当前 location 的其他指令,重新查找 location |
break | 终止当前 rewrite 规则,不重新查找 location |
redirect | 返回 302 临时重定向(客户端可见地址变化) |
permanent | 返回 301 永久重定向(浏览器会缓存,慎用) |
rewrite 指令与 if 指令
nginx
# 语法
rewrite <正则> <替换URL> [标志位];
# if 按条件触发 rewrite
if (条件) {
rewrite ...;
}实践案例
nginx
# URL 美化:将查询参数转为静态风格 URL
# /article/123/my-title → /index.php?id=123&title=my-title
location /article/ {
rewrite ^/article/([0-9]+)/([^/]+)$ /index.php?id=$1&title=$2 last;
}
# 移动端重定向:检测 UA 跳转到 m. 子域
if ($http_user_agent ~* "(android|iphone|ipad)") {
rewrite ^(.*)$ http://m.example.com$1 permanent;
}
# 隐藏 .html 扩展名(所有 .html 请求 301 到无扩展名版本)
location / {
rewrite ^/(.*)\.html$ /$1 permanent;
}
# 维护页面:所有请求返回 503 并展示维护页
location / {
error_page 503 = @maintenance;
return 503;
}
location @maintenance {
rewrite ^(.*)$ /maintenance.html break;
}HTTPS / TLS 配置
证书申请(Let's Encrypt)
bash
# 安装 certbot
sudo apt install certbot python3-certbot-nginx
# 申请证书(--nginx 参数自动修改 nginx 配置)
sudo certbot --nginx -d example.com -d www.example.com
# 测试自动续期(certbot 会自动配置 cron/systemd timer)
sudo certbot renew --dry-runTLS 推荐配置
nginx
# 在 http 块中定义全局 SSL 参数
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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off; # TLS 1.3 下应为 off
# Session 缓存(减少握手开销)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # 禁用以增强前向保密
# OCSP Stapling(加速证书验证)
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
# DH 参数(防止 Logjam 攻击)
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
# 生成:openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
}
# HTTP 强制跳转 HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}安全响应头
nginx
server {
# 防止点击劫持
add_header X-Frame-Options "SAMEORIGIN" always;
# 防止 MIME 类型嗅探
add_header X-Content-Type-Options "nosniff" always;
# XSS 过滤(旧浏览器兼容)
add_header X-XSS-Protection "1; mode=block" always;
# Referrer 策略
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 内容安全策略(按业务调整)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;
# HSTS(仅 HTTPS,max-age 单位秒)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# 禁用不必要的浏览器特性
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}
add_header在子 location 不继承父块配置,建议统一在server块使用always参数。
HTTP/2 与 HTTP/3
启用 HTTP/2
nginx
server {
listen 443 ssl http2;
# ...
}启用 HTTP/3(需要 nginx >= 1.25.0)
nginx
server {
listen 443 ssl;
listen 443 quic reuseport; # UDP,用于 HTTP/3
http2 on;
http3 on;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 告知客户端支持 HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400';
}负载均衡
基本配置
nginx
upstream backend {
# 默认轮询(Round Robin)
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
location / {
proxy_pass http://backend;
}
}负载均衡策略
nginx
upstream backend {
# 加权轮询
server 192.168.1.10:8080 weight=3;
server 192.168.1.11:8080 weight=1;
# ip_hash(同一 IP 始终转发到同一后端,适合有状态应用)
# ip_hash;
# least_conn(转发到当前连接数最少的后端)
# least_conn;
# 被动健康检查
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
# 备用节点(主节点全部不可用时才启用)
server 192.168.1.12:8080 backup;
# keepalive 连接池(减少 TCP 握手开销)
keepalive 32;
}反向代理
基本配置
nginx
server {
location /api/ {
proxy_pass http://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;
# 超时设置
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲设置(减少后端压力)
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
# 隐藏后端响应头
proxy_hide_header X-Powered-By;
}
}代理缓存
nginx
http {
# 缓存路径(在 http 块定义)
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
server {
location /api/ {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
}
}
}WebSocket 代理
nginx
http {
# WebSocket 升级映射
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
location /ws/ {
proxy_pass http://backend;
# WebSocket 必需头部
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 长连接超时
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}性能调优
nginx
# 工作进程数(建议与 CPU 核心数一致)
worker_processes auto;
# 每个 worker 允许打开的最大文件描述符数
worker_rlimit_nofile 65535;
events {
worker_connections 4096; # 每个 worker 最大并发连接数
use epoll; # Linux 下使用 epoll 提升性能
multi_accept on; # 允许 worker 一次接受多个连接
}
http {
sendfile on; # 零拷贝传输
tcp_nopush on; # 配合 sendfile,批量发送
tcp_nodelay on; # 减少延迟(长连接场景)
keepalive_timeout 75s;
keepalive_requests 1000;
# 打开文件缓存(减少磁盘 I/O)
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# 客户端请求限制
client_max_body_size 50m;
client_body_buffer_size 128k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
client_body_timeout 15s;
client_header_timeout 15s;
send_timeout 15s;
# 隐藏版本信息
server_tokens off;
}Gzip / Brotli 压缩
Gzip
nginx
http {
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6; # 1-9,6 是速度与压缩比的平衡点
gzip_min_length 1024; # 小于 1KB 的响应不压缩
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_disable "msie6";
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml
font/truetype
font/opentype
application/vnd.ms-fontobject;
}Brotli(需编译 ngx_brotli 模块)
nginx
http {
brotli on;
brotli_comp_level 6;
brotli_static on; # 优先使用预压缩的 .br 文件
brotli_types
text/plain
text/css
application/javascript
application/json
image/svg+xml;
}Brotli 压缩率比 Gzip 高约 20-30%,推荐在支持的环境中启用。
限速与连接限制
limit_req_zone和limit_conn_zone必须定义在http块,不能放在server或location块内。
nginx
http {
# 基于 IP 的请求速率限制(10MB 内存,每 IP 每秒 10 个请求)
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
# 基于 IP 的并发连接限制
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# 限速状态码改为 429(默认 503)
limit_req_status 429;
limit_conn_status 429;
server {
# API 接口限速
location /api/ {
limit_req zone=req_limit burst=20 nodelay;
limit_conn conn_limit 10;
proxy_pass http://backend;
}
# 登录接口更严格
location /auth/login {
limit_req zone=req_limit burst=5 nodelay;
proxy_pass http://backend;
}
}
}OpenAI 接口转发
代理 OpenAI API 时需注意 SNI、Host 头部、流式响应(SSE)的缓冲设置。
nginx
location ^~ /v1/ {
proxy_pass https://api.openai.com;
proxy_ssl_server_name on; # 启用 SNI,多域名共享 IP 时必须开启
proxy_ssl_protocols TLSv1.2 TLSv1.3;
proxy_redirect off;
proxy_set_header Host api.openai.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
# 流式响应(SSE)需关闭缓冲
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}防盗链
nginx
server {
location ~* \.(jpg|jpeg|png|gif|webp|svg|mp4|mp3|pdf)$ {
valid_referers none blocked server_names
*.yourdomain.com yourdomain.com;
if ($invalid_referer) {
return 403;
# 也可替换为占位图:
# rewrite ^/.*$ /403.png break;
}
expires 7d;
add_header Cache-Control "public";
}
}none:允许直接在地址栏访问(Referer 为空)blocked:允许防火墙过滤后 Referer 被清除的请求server_names:允许来自本域的请求
隐藏版本信息
nginx
http {
server_tokens off; # 隐藏响应头中的 nginx 版本号
}若使用 nginx-headers-more 模块,可进一步隐藏 Server 字段:
nginx
more_clear_headers Server;日志切割(logrotate)
配置文件
创建 /etc/logrotate.d/nginx:
bash
/var/log/nginx/*.log {
daily # 每天轮转
rotate 30 # 保留 30 天
compress # gzip 压缩旧日志
delaycompress # 延迟一天压缩,确保写入完整
missingok # 文件不存在时不报错
notifempty # 空文件不轮转
dateext # 文件名附加日期(如 access.log-20260101.gz)
create 0640 nginx nginx # 新日志文件权限及属主
sharedscripts # 多文件时只运行一次 postrotate
postrotate
# 发送 USR1 信号让 nginx 重新打开日志文件(不重启进程)
if [ -f /var/run/nginx.pid ]; then
kill -USR1 $(cat /var/run/nginx.pid)
fi
endscript
}测试与触发
bash
# 测试配置语法(不执行实际轮转)
logrotate -d /etc/logrotate.d/nginx
# 强制立即执行一次
sudo logrotate -f /etc/logrotate.d/nginx
# 确认 systemd timer 是否已配置
systemctl status logrotate.timer最佳实践
前端项目部署(Vue / React SPA)
Vue 和 React 构建产物结构相同(index.html + 静态资源),配置可以共用。核心要解决两个问题:静态资源长期缓存 + 前端路由支持。
nginx
# ───────────────────────────────────────────────────────────────────────────
# 方案一:不使用 certbot webroot(手动续签 / 使用 certbot --standalone 模式)
# 优点:配置简单。缺点:续签时需先停止 nginx。
# ───────────────────────────────────────────────────────────────────────────
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri; # 所有 HTTP 请求直接跳 HTTPS
}
# ───────────────────────────────────────────────────────────────────────────
# 方案二:使用 certbot webroot(推荐,自动续签不停机)
# certbot 续签时通过 HTTP 访问 /.well-known/acme-challenge/ 验证域名所有权。
# 如果 server 块顶层写 return 301,顶层 return 在 location 匹配前执行,
# 会把验证请求也一并 301,导致续签失败。
# 正确做法:把验证路径展开为独立 location,其余才跳转。
# ───────────────────────────────────────────────────────────────────────────
server {
listen 80;
server_name example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot; # certbot --webroot -w 指定的目录与此一致
}
location / {
return 301 https://$host$request_uri; # 验证路径之外才跳 HTTPS
}
}
server {
listen 443 ssl http2; # 启用 HTTPS,http2 减少请求延迟
server_name example.com;
# TLS 证书(Let's Encrypt 路径,certbot 申请后自动生成)
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3; # 禁用 TLS 1.0/1.1,老版本有已知漏洞
ssl_ciphers HIGH:!aNULL:!MD5; # 排除空加密和 MD5 弱算法
root /usr/share/nginx/html; # 前端构建产物目录(vite 默认为 dist/)
index index.html;
# 带 hash 的静态资源(JS/CSS/图片/字体)—— 永久缓存
# Vite/Webpack 构建的文件名自带 hash,内容变则文件名变,可永久缓存
location ~* \.(js|css|woff2?|ttf|svg|png|jpg|jpeg|gif|webp|ico)$ {
expires 1y;
add_header Cache-Control "public, immutable"; # immutable 告知浏览器跳过 revalidation
access_log off; # 静态资源无需记录访问日志
}
# SPA 前端路由支持
# 若请求的文件/目录不存在,统一返回 index.html,由前端路由接管
location / {
try_files $uri $uri/ /index.html;
}
# 若后端 API 与前端同域,在此代理转发(不同域可省略)
location /api/ {
proxy_pass http://127.0.0.1:3000; # 后端服务地址
proxy_http_version 1.1; # 使用 HTTP/1.1 以支持 keepalive
proxy_set_header Connection ""; # 清空 Connection 头,启用 upstream keepalive
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; # 告知后端原始协议(https)
}
}Node.js 服务部署
Node.js 服务以反向代理方式部署,Nginx 负责 TLS 终止、安全响应头和请求转发,Node.js 只需监听本地端口。
nginx
# upstream 定义后端服务节点(单节点也建议写 upstream,方便后续扩展)
upstream node_app {
server 127.0.0.1:3000;
keepalive 16; # 与 upstream 保持长连接池,减少连接建立开销
}
# ───────────────────────────────────────────────────────────────────────────
# 方案一:不使用 certbot webroot(手动续签 / 使用 certbot --standalone 模式)
# 优点:配置简单。缺点:续签时需先停止 nginx。
# ───────────────────────────────────────────────────────────────────────────
server {
listen 80;
server_name api.example.com;
return 301 https://$host$request_uri; # 所有 HTTP 请求直接跳 HTTPS
}
# ───────────────────────────────────────────────────────────────────────────
# 方案二:使用 certbot webroot(推荐,自动续签不停机)
# certbot 续签时通过 HTTP 访问 /.well-known/acme-challenge/ 验证域名所有权。
# 如果 server 块顶层写 return 301,顶层 return 在 location 匹配前执行,
# 会把验证请求也一并 301,导致续签失败。
# 正确做法:把验证路径展开为独立 location,其余才跳转。
# ───────────────────────────────────────────────────────────────────────────
server {
listen 80;
server_name api.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot; # certbot --webroot -w 指定的目录与此一致
}
location / {
return 301 https://$host$request_uri; # 验证路径之外才跳 HTTPS
}
}
server {
listen 443 ssl http2;
server_name api.example.com;
# TLS 证书(Let's Encrypt 路径)
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# 安全响应头(防点击劫持、XSS、MIME 嗅探)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 仅 HTTPS 场景才启用 HSTS(max-age=1年,浏览器不再发送 HTTP 请求)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 限制请求体大小(Node.js 上传接口按业务调整,默认 1m 太小)
client_max_body_size 50m;
location / {
proxy_pass http://node_app;
proxy_http_version 1.1; # 使用 HTTP/1.1 配合 keepalive
proxy_set_header Connection ""; # 清空后可复用 keepalive 连接
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;
# 超时设置(默认 60s,可根据接口最长响应时间调整)
proxy_read_timeout 60s;
# 隐藏后端框架信息,避免泄漏技术栈
proxy_hide_header X-Powered-By;
}
# 若服务有 WebSocket 端点(如 /ws 或 /socket.io)
location /ws {
proxy_pass http://node_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; # WebSocket 升级协议
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
# WebSocket 是长连接,超时需要设长(否则 Nginx 会断开)
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}