文章

Nginx 学习笔记:反向代理与部署实战

牛耕田

暂存笔记,持续补充中。Nginx 是后端部署的「第一道门」,几乎每个 Java 项目都跑在它后面。

一、Nginx 是什么、为什么快

Nginx 是一个事件驱动、异步非阻塞的高性能 Web 服务器 / 反向代理服务器。

对比 Apache Nginx
并发模型 进程/线程 per connection epoll 事件驱动
内存占用
静态资源 一般 极强
高并发 一般 优秀(单机轻松万级)

为什么快

  1. epoll 多路复用:一个 worker 进程用事件循环处理成千上万连接,无线程切换开销。
  2. master-worker 架构:master 管配置与 worker,worker 处理请求,worker 数通常设为 CPU 核数
  3. 零拷贝sendfile):静态文件直接从内核缓冲区发送,不经用户态。

二、配置文件结构

# 全局块
user  nginx;
worker_processes  auto;              # 通常 = CPU 核数
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

# events 块
events {
    worker_connections  10240;       # 单 worker 最大连接数
    use epoll;
    multi_accept on;
}

# http 块
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile      on;                # 开启零拷贝
    keepalive_timeout  65;
    client_max_body_size 50m;        # 上传大小限制

    # server 块(一个虚拟主机)
    server {
        listen       80;
        server_name  sxz-blog.xyz;

        location / {
            root   /usr/share/nginx/html;
            index  index.html;
        }
    }
}

匹配优先级(location)

= 精确匹配 > ^~ 前缀匹配 > ~ / ~* 正则匹配 > / 通用前缀
location = /favicon.ico { }        # 只匹配 /favicon.ico
location ^~ /static/    { }        # 匹配 /static/ 开头,不再走正则
location ~  \.php$      { }        # 区分大小写正则
location ~* \.(png|jpg)$ { }       # 不区分大小写
location /              { }        # 兜底

三、反向代理 vs 正向代理

类型 代理谁 典型场景
正向代理 代理客户端 科学上网、公司出网代理
反向代理 代理服务端 负载均衡、隐藏真实后端、统一入口
正向代理:客户端 → 代理 → 目标服务器   (服务端不知道真实客户端)
反向代理:客户端 → 代理 → 后端服务器   (客户端不知道真实后端)

Java 后端典型配置

server {
    listen 80;
    server_name api.sxz-blog.xyz;

    location /api/ {
        proxy_pass http://127.0.0.1:8080/;      # 注意结尾的 /
        proxy_http_version 1.1;
        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_read_timeout    60s;
        proxy_send_timeout    60s;
    }
}

⚠️ proxy_pass 结尾斜杠的坑

  • proxy_pass http://backend/; → 请求 /api/user 转发为 /user去掉 /api)。
  • proxy_pass http://backend; → 请求 /api/user 转发为 /api/user保留原路径)。

这一条配错会导致全部 404,是新手最常见的坑。

四、负载均衡

upstream backend {
    # 策略(默认轮询,无需关键字)
    # 权重轮询:weight
    # IP 哈希:ip_hash(同一 IP 固定到同一台)
    # 最少连接:least_conn
    # 一致性哈希:hash $request_uri consistent

    server 127.0.0.1:8081 weight=3;
    server 127.0.0.1:8082 weight=1;
    server 127.0.0.1:8083 backup;               # 备用机
    server 127.0.0.1:8084 down;                 # 手动下线

    keepalive 32;                               # 与后端保持长连接
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";         # 配合 keepalive
    }
}

健康检查:开源版 Nginx 只做被动检查max_fails + fail_timeout),主动健康检查需商业版或 nginx_upstream_check_module

server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;

五、静态资源与缓存

# 图片/字体等强缓存
location ~* \.(jpg|jpeg|png|gif|ico|svg|woff2?)$ {
    root /var/www/static;
    expires 30d;
    add_header Cache-Control "public, immutable";
}

# HTML/JS/CSS 带 hash 的产物可长期缓存
location ~* \.(js|css)$ {
    expires 7d;
    add_header Cache-Control "public";
}

# HTML 不缓存,保证发版立即生效
location ~* \.html$ {
    add_header Cache-Control "no-cache, must-revalidate";
}

Gzip 压缩(放在 http 块):

gzip on;
gzip_min_length  1k;
gzip_comp_level  6;                    # 1-9,6 是性价比拐点
gzip_types text/plain text/css application/json
           application/javascript text/xml application/xml image/svg+xml;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";

注意:图片/视频已是压缩格式,gzip 反而浪费 CPU,不要加进 gzip_types

六、HTTPS 配置

server {
    listen 443 ssl;
    http2 on;
    server_name sxz-blog.xyz;

    ssl_certificate     /etc/nginx/ssl/sxz-blog.xyz.pem;
    ssl_certificate_key /etc/nginx/ssl/sxz-blog.xyz.key;

    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        root /var/www/blog;
        index index.html;
    }
}

# HTTP 强制跳转 HTTPS
server {
    listen 80;
    server_name sxz-blog.xyz;
    return 301 https://$host$request_uri;
}

Strict-Transport-Security(HSTS)会让浏览器强制只用 HTTPS,一旦开启测试不充分很难回退(需清理浏览器缓存或等 max-age 过期)。

七、SPA 与静态博客的路由处理

单页应用(React/Vue)或 Astro 静态站,刷新非首页路径会 404,需要 fallback:

location / {
    root /var/www/blog;
    index index.html;
    try_files $uri $uri/ /index.html;      # 找不到就回退到 index.html
}

Astro 静态构建产出的其实是多个 .html,配合 trailingSlash 配置,通常 try_files $uri $uri/ =404; 即可。若用了客户端路由,才需要 fallback 到 index.html

八、安全加固

# 隐藏版本号
server_tokens off;

# 限制请求方法与大小
if ($request_method !~ ^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$) {
    return 405;
}
client_max_body_size 50m;

# 防点击劫持 / XSS / 嗅探
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'" always;

# 限流(http 块定义,server/location 引用)
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
location /api/ {
    limit_req zone=api burst=40 nodelay;
    proxy_pass http://backend;
}

# 限并发连接
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn perip 20;

# 禁止访问隐藏文件
location ~ /\. {
    deny all;
}

九、常用运维命令

nginx -t                    # 测试配置语法(改配置后必做)
nginx -s reload             # 平滑重载(不中断连接)
nginx -s stop               # 快速停止
nginx -s quit               # 优雅停止(处理完当前请求)
nginx -v                    # 查看版本
nginx -T                    # 打印最终生效的完整配置(含 include)

reload 的原理:master 进程读取新配置、启动新 worker,再优雅关闭旧 worker。旧 worker 会处理完已建立的连接才退出,因此不断连接。但若配置有语法错误,reload 会失败且保持旧配置继续服务,是安全的。

十、日志与排查

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" rt=$request_time ut=$upstream_response_time';

access_log /var/log/nginx/access.log main;
  • $request_time:Nginx 收到请求到响应的总耗时。
  • $upstream_response_time:后端处理耗时。
  • 两者差距大 → 瓶颈在网络或 Nginx;两者都大 → 瓶颈在后端应用。
# 统计访问量 Top 10 IP
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

# 统计 5xx 错误
awk '$9 ~ /^5/ {print}' access.log | wc -l

# 实时观察
tail -f /var/log/nginx/access.log

十一、小结

  • Nginx 快的核心:epoll 事件驱动 + master-worker + 零拷贝
  • 反向代理是 Java 后端部署标配,注意 proxy_pass 结尾斜杠语义。
  • 负载均衡默认轮询,weight / ip_hash / least_conn 按场景选。
  • 静态资源按类型设缓存,HTML 不缓存,JS/CSS 长缓存。
  • 生产必备:server_tokens off + 安全响应头 + 限流 + Gzip + HTTPS
  • 排查性能问题看 $request_time$upstream_response_time 的差值