一、为什么需要动静分离?
一个典型的 Web 请求中,资源可以分为两类:
- 动态资源:需要后端程序(PHP、Java、Python 等)实时计算并返回的内容,例如 API 接口、用户登录后的页面、数据库查询结果。
- 静态资源:不需要后端参与、直接以文件形式存在于磁盘上的内容,例如 HTML、CSS、JavaScript、图片、字体、视频。
在小型项目中,所有请求都交给后端处理并无大碍。但随着流量增长,后端应用服务器既要处理业务逻辑,又要读取磁盘上的静态文件,CPU 和 I/O 都会成为瓶颈。动静分离的核心思路就是:让 Nginx 专门处理静态资源,后端只专注于动态请求,从而降低后端压力、提升整体响应速度。
二、架构原理
动静分离的典型架构如下:
用户请求 → Nginx
├── 静态资源请求(.css/.js/.jpg 等)→ Nginx 直接返回本地文件
└── 动态请求(.php/.jsp/api 等)→ 反向代理到后端应用服务器
Nginx 判断请求类型的方式主要有三种:
- 基于文件后缀:通过
location匹配.css、.js、.png等扩展名。 - 基于目录前缀:约定
/static/、/assets/等路径存放静态资源。 - 基于请求头或 Cookie:较少使用,一般用于灰度场景。
在实际生产中,通常将后缀匹配和目录前缀结合使用,既灵活又易于维护。
三、环境准备
本文以如下环境为例:
- 操作系统:CentOS 7 / Ubuntu 20.04
- Nginx:1.24.x
- 后端应用:PHP-FPM(也可替换为 Tomcat、Node.js 等)
- 静态资源目录:
/data/www/static - 后端服务地址:
127.0.0.1:9000
安装 Nginx:
# CentOS
yum install -y nginx
# Ubuntu
apt install -y nginx
四、核心配置
4.1 基础 server 块
server {
listen 80;
server_name www.example.com;
root /data/www;
index index.html index.php;
# 静态资源:按后缀匹配
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?|ttf|eot|mp4)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
}
# 静态资源:按目录前缀匹配
location /static/ {
alias /data/www/static/;
expires 7d;
}
# 动态请求:交给 PHP-FPM
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# 其他动态请求:反向代理到后端
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
4.2 关键指令说明
~*:表示不区分大小写的正则匹配,用于后缀匹配非常合适。expires 30d:告诉浏览器缓存 30 天,减少重复请求。alias与root的区别:alias会把location匹配的部分替换掉,root则会拼接完整路径。使用/static/前缀时建议用alias。access_log off:静态资源访问量大,关闭日志可减少磁盘 I/O。
五、踩坑记录
坑 1:root 与 alias 混用导致 404
最初配置为:
location /static/ {
root /data/www/static/;
}
请求 /static/logo.png 时,Nginx 实际查找的是 /data/www/static/static/logo.png,自然 404。
解决:使用 alias,或者把 root 改为 /data/www(去掉末尾的 static)。推荐用 alias,语义更清晰。
坑 2:正则 location 与普通 location 的优先级
Nginx 的 location 匹配顺序为:
=精确匹配,命中即停止。^~前缀匹配,命中后不再匹配正则。- 正则匹配(
~和~*),按配置文件中的顺序,第一个命中的生效。 - 普通前缀匹配,取最长匹配。
一开始我把 location /static/ 写在正则 location 之后,结果图片请求被正则规则抢先匹配,alias 完全没有生效。
解决:把 location /static/ 改为 location ^~ /static/,强制优先于正则匹配。
坑 3:静态资源缓存导致更新不生效
设置了 expires 30d 后,前端发版更新 CSS,用户浏览器仍然使用旧缓存,页面样式错乱。
解决:采用“文件名哈希”策略,构建时生成 app.a1b2c3.css 这样的文件名,同时把 HTML 设为不缓存。这样既享受长缓存,又能保证更新即时生效。
坑 4:Nginx 无权限读取静态目录
配置完成后访问静态资源返回 403。查看日志:
Permission denied
原因:Nginx 工作进程以 nginx 用户运行,而 /data/www/static 的属主是 root,权限为 700。
解决:
chown -R nginx:nginx /data/www/static
chmod -R 755 /data/www/static
同时确认 SELinux 未拦截(CentOS 下可临时 setenforce 0 验证)。
坑 5:反向代理丢失真实 IP
后端日志中所有请求的 IP 都是 127.0.0.1,无法做风控和统计。
解决:在 proxy_pass 的 location 中补充:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
后端通过 X-Forwarded-For 的第一个 IP 获取客户端真实地址。
六、性能优化建议
- 开启 sendfile:
sendfile on;让内核直接传输文件,减少用户态与内核态拷贝。 - 开启 gzip:对文本类静态资源压缩,通常可减少 60% 以上体积。
- 设置 open_file_cache:缓存文件描述符,降低频繁 open/close 的开销。
- 使用 CDN:将静态资源推到 CDN 边缘节点,进一步减轻源站压力。
- 合并小文件:减少 HTTP 请求数,配合 HTTP/2 效果更佳。
七、总结
动静分离并不是一项复杂的技术,但细节决定成败。核心在于三点:
- 匹配规则要清晰:善用
^~和正则的优先级,避免 location 冲突。 - 路径映射要准确:理解
root与alias的差异,防止 404。 - 缓存策略要合理:长缓存 + 文件名哈希,兼顾性能与更新。
把这套架构跑通之后,后端应用可以专注业务逻辑,Nginx 则成为静态资源的“高速公路”,整站性能会有肉眼可见的提升。
未经允许不得转载:任鹏个人博客 » 从零构建 Nginx 动静分离架构:原理、配置与踩坑记录


朋友圈点赞图在线生成源码