Nginx
这一课你将能做到
- 配置 Nginx 反向代理:把 443 端口的 HTTPS 流量转发给本机 8080 的 Spring Boot
- 配置静态资源服务、gzip 压缩、访问日志与限流等生产必备项
- 用
nginx -t 验证配置并优雅重载,定位 502/504 等常见网关错误
为什么要学
你的 Spring Boot 应用监听 8080 端口,但用户访问的是 https://api.example.com 的 443 端口。中间的转换——HTTPS 卸载、域名匹配、请求转发、静态文件直出、负载均衡——就是 Nginx 干的活。它是几乎所有 Java 后端的「门面」:一台低配 Nginx 能扛几万并发连接,而它身后的 Java 应用只需要专注业务。
人话:Nginx 是餐厅的前台。顾客(用户)不直接进厨房(Java 应用),而是先找前台:前台收下点单(接收请求)、转给厨房(反向代理)、把菜端出去(返回响应)。加菜加人(扩容)时,前台还能把顾客均匀分到多个厨房窗口(负载均衡)。
先建立图像
用户 HTTPS 请求
│ 443
▼
┌─────────────────────────────────────────┐
│ Nginx │
│ ┌──────────────┐ ┌────────────────┐ │
│ │ TLS 卸载 │ → │ location 路由 │ │
│ │ (证书/密钥) │ │ /api/* → 后端 │ │
│ └──────────────┘ │ /* → 静态文件│ │
│ └───────┬────────┘ │
└──────────────────────────────┼──────────┘
│ proxy_pass http://127.0.0.1:8080
▼
Spring Boot (8080)
│
┌──────────┴──────────┐
▼ ▼
MySQL (3306) Redis (6379)
Nginx 配置的核心结构:
nginx.conf
├── events { worker_connections } ← 全局:连接模型
└── http { ← HTTP 层:gzip、日志格式、限流区
├── upstream { 后端列表 } ← 负载均衡池
└── server { ← 虚拟主机:按域名区分站点
├── listen 443 ssl
├── server_name
└── location { proxy_pass } ← URL 路由规则
}
跟着做
步骤 1:安装与基础命令
# Ubuntu
sudo apt install nginx -y
# 配置文件位置
ls /etc/nginx/ # nginx.conf 主配置,conf.d/ 和 sites-enabled/ 放站点配置
# 四个保命命令
sudo nginx -t # 改完配置先验证语法!
sudo systemctl reload nginx # 优雅重载:不断开现有连接
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
步骤 2:反向代理 Spring Boot(核心配置)
创建 /etc/nginx/conf.d/myapp.conf:
server {
listen 80;
server_name api.example.com;
# HTTP 全部 301 到 HTTPS(证书配好后启用)
# return 301 https://$host$request_uri;
location / {
proxy_pass http://127.0.0.1:8080;
# 这四个头必须传,否则后端拿不到真实信息
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 5s;
proxy_read_timeout 60s;
}
}
sudo nginx -t && sudo systemctl reload nginx
curl -H "Host: api.example.com" http://127.0.0.1/actuator/health
步骤 3:接入 HTTPS(结合第 5 课的 certbot)
sudo certbot --nginx -d api.example.com
# certbot 会自动改写配置,加入 ssl_certificate 等指令
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# TLS 安全基线
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
location / { ... } # 同步骤 2
}
步骤 4:静态资源直出 + gzip(给后端减负)
本站前端打包后是一堆 JS/CSS/图片,完全没必要让 Java 处理:
server {
listen 443 ssl;
server_name www.example.com;
root /var/www/frontend/dist;
index index.html;
# gzip 压缩,文本资源体积减 70%
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1k;
# 静态资源强缓存(带 hash 的文件名放心缓存一年)
location /assets/ {
expires 365d;
add_header Cache-Control "public, immutable";
}
# SPA 路由:找不到的文件都回退到 index.html
location / {
try_files $uri $uri/ /index.html;
}
# API 请求转发给后端
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
步骤 5:负载均衡(两台 Spring Boot 分摊流量)
upstream myapp_backend {
least_conn; # 最少连接优先(默认是轮询)
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
}
server {
listen 443 ssl;
server_name api.example.com;
location / {
proxy_pass http://myapp_backend; # 指向 upstream 而非单个地址
...
}
}
步骤 6:限流——防刷接口的基础手段
http {
# 定义限流区:按客户端 IP,每秒 10 个请求
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/auth/ { # 只给登录注册等敏感接口限流
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:8080;
}
}
}
讲清楚要点
- 反向代理是 Nginx 的第一职责:对外统一入口(443)、对内分发到后端(8080)。好处:后端不暴露公网、HTTPS 集中在 Nginx 处理、后端重启时 Nginx 返回友好错误页。
proxy_set_header 四件套不能省:不配的话,Java 里 request.getRemoteAddr() 拿到的永远是 127.0.0.1,审计日志、限流、风控全部失效。X-Forwarded-For 是真实客户端 IP。
nginx -t 再 reload 是铁律:配置有语法错误时 reload 会失败(但旧进程还在跑,不影响线上),直接 restart 有错误配置就会挂掉。养成习惯:改配置 → -t → reload。
- 静态资源让 Nginx 直出:Nginx 用 sendfile 零拷贝发送文件,性能比 Java 高一个数量级,还能直接配缓存头。Java 只处理动态 API。
- 错误日志优先看 error.log:502(后端挂了/端口不通)、504(后端处理超时)、499(客户端主动断开),错误日志里都有明确记录,别对着 access.log 瞎猜。
你可能会懵的地方
-
502 和 504 怎么区分?
502 Bad Gateway:Nginx 根本连不上后端(Java 进程挂了、端口错了、防火墙拦了)。504 Gateway Timeout:连上了,但后端在 proxy_read_timeout 内没返回(慢 SQL、死循环)。先 502 查进程端口,再 504 查后端性能。
-
location 匹配规则太多记不住?
优先级:location = /exact(精确)> location ^~ /prefix(前缀优先)> location ~ \.php$(正则)> location /prefix(普通前缀)> location /(兜底)。记住最常用的:/api/ 前缀转发 + / 兜底给静态。
-
proxy_pass 结尾加不加 / 有什么区别?
proxy_pass http://127.0.0.1:8080;(无尾斜杠):location /api/ 收到 /api/users → 后端收到 /api/users。proxy_pass http://127.0.0.1:8080/;(有尾斜杠):/api/users → 后端收到 /users,前缀被剥掉。后端有没有 /api 前缀决定你选哪种。
-
Nginx 一台能扛多少并发?
官方数据单机上万并发很轻松,瓶颈通常在带宽和后端,而不是 Nginx。它的事件驱动模型(一个 worker 处理几千连接)和 Java 的「一个连接一个线程」完全不同。
-
为什么部署后前端页面显示旧版本?
两个缓存层:Nginx 的 expires 强缓存 + 浏览器缓存。发版时 index.html 必须 no-cache(每次都问服务器),带 hash 的 assets 才用长缓存。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 不传 X-Forwarded-For | 后端拿不到真实 IP | proxy_set_header 四件套 |
| 改完配置直接 restart | 配置错误时全站挂掉 | 先 nginx -t,再 reload |
| 8080 端口对公网开放 | 绕过 Nginx 直连后端,HTTPS 失效 | 安全组只放 80/443,8080 只绑 127.0.0.1 |
| proxy_read_timeout 不设 | 慢请求把 worker 占满 | 显式设置超时时间 |
| index.html 也缓存一年 | 发版后用户看到旧页面 | html 设 no-cache,hash 文件设长缓存 |
卡点急救
- reload 后还是旧配置:确认
nginx -t 通过且 reload 无报错;确认改的是实际加载的文件(nginx -T 看完整生效配置);多 server_name 冲突时第一个匹配的生效。
- 大文件上传 413 或 502:默认 body 限制 1MB,加
client_max_body_size 50m;;上传慢还要调大 proxy_read_timeout。
- WebSocket 握手失败 400:需要额外两个头——
proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";,并把 proxy_read_timeout 调大到 3600s 防断连。
面试常问
-
Nginx 为什么能扛高并发?
事件驱动 + epoll 异步非阻塞模型:少量 worker 进程(通常 = CPU 核数),每个 worker 通过事件循环处理数千连接,遇到 IO 等待立刻切换处理其他请求,不阻塞。对比传统「一连接一线程」模型,内存和上下文切换成本低几个数量级。
-
正向代理和反向代理的区别?
正向代理代理的是客户端(科学上网工具:服务器不知道真实用户是谁);反向代理代理的是服务端(Nginx:用户不知道真实后端是哪台)。一句话:正向隐藏客户端,反向隐藏服务端。
-
Nginx 负载均衡有哪些策略?
轮询(默认)、weight 权重(性能好的机器多分担)、least_conn 最少连接(请求耗时不均时最优)、ip_hash(同一 IP 固定到一台,用于无共享 session 的老系统——新系统应用 Redis 共享 session 代替)。
自测三问
- 用户请求
https://api.example.com/api/users,写出请求经过 Nginx 到 Spring Boot 的完整路径,包括用到的配置指令。
- 线上突然出现大量 504,你的排查顺序是什么?
- 为什么发版后要用 hash 文件名 + 长缓存,而 index.html 必须 no-cache?
小结一句话
Nginx 是应用的统一门面:TLS、路由、静态资源、负载均衡、限流,一个配置全部搞定。
延伸