ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Nginx静态资源部署三件套:location、root/alias、try_files实战指南

Nginx静态资源部署三件套:location、root/alias、try_files实战指南 先把结论放前面用 nginx 做静态资源部署学三件事就够用了——location 匹配规则、root 和 alias 的区别、try_files 和缓存策略。只要把这三块吃透前端构建产物往服务器上一丢配好 nginx性能、缓存、防刷新 404、跨域这些问题基本都能一次解决。这篇文章就把整个静态资源部署的前因后果、配置细节、踩坑实录都摊开讲新手可以从零照抄老手也可以看看有没有自己忽略掉的盲区。1. 先想清楚再动手静态资源部署的整体设计很多刚接触部署的同学最容易犯的毛病是“配置靠抄、报错靠猜”。看到别人博客贴一段 location / {}自己也抄一段结果资源跑起来了但问一句“这个配置为什么有效”答不上来。要在 nginx 里把静态资源安排好真正重要的不是你敲了什么命令而是你得先想清楚架构。1.1 为什么静态资源要交给 nginx 而不是 Tomcat先说底层逻辑。静态资源指 HTML、CSS、JS、图片、字体这类不会动态变化的内容。它们的特点是没有业务逻辑不需要容器去执行 Java 代码或者调用数据库只需要一个能快速把文件返回给客户端的东西。Tomcat 这类应用服务器的主业是处理动态请求它每处理一个请求都要经过 Servlet 容器、过滤器链、线程池这些步骤对于“返回一个 JS 文件”来说完全是多余的。nginx 是事件驱动的异步架构一个 worker 进程能撑住几万并发连接内存占用还特别低。同样是返回一个静态文件Tomcat 在并发上来后线程会被占满nginx 可能连汗都不出。所以线上环境的常规做法是动静分离nginx 直接托管静态资源动态请求再反向代理到后端的 Tomcat 或 Node 服务。1.2 部署场景和目录规划静态资源部署的典型场景是前后端分离项目。前端打包后生成一个 dist 目录里面是 index.html、js/css 目录、图片目录等。你需要做的就是把 dist 目录里的内容放到服务器某个路径下再让 nginx 把请求指向这个路径。还有一类场景是 Vue 或 React 的 history 路由。这类项目只有一个 index.html 入口路由切换靠前端 JS 控制但用户刷新页面时浏览器会按 URL 去请求服务器比如访问 /about 时请求的是 /about 这个路径服务端没有这个文件就 404 了。这类场景也需要 nginx 配置 try_files 做回退。部署前的目录规划千万别省。我对服务器的建议是/data/www/ 下面按项目名建目录比如 /data/www/blog/里面再分 assets 和 conf。assets 放静态文件conf 放 nginx 的引入配置。千万别把东西乱堆到 /root 或 /tmp 下后面维护和回滚会很难受。1.3 动静分离的完整请求链路一次完整的请求在 nginx 下的流转逻辑大致是这样的用户访问 a.com - nginx 监听 80/443 端口 - 拿到请求 URI - 根据 location 规则匹配 - 如果是/js/、/css/、/img/ 这些静态路径直接读磁盘返回如果是 /api/ 路径反向代理给后端服务。这也就解释了为什么有人说“nginx 部署静态资源是最高效的”。它压根不把请求交给其他进程直接在自身的事件循环里就把文件读出来返回了整个过程没有进程切换、没有上下文切换开销。具备了这个整体认知再去看具体配置就不会觉得每个指令是孤立的。接下来进入核心location 匹配和 root/alias 的区别这是九成配置问题的根源。2. 核心配置细节解析location 匹配和路径拼接静态资源部署的所有玄机都集中在 server 块里的 location 配置上。location 负责把不同的 URI 路由到不同的处理逻辑但它的匹配规则有好几层优先级很多人在这里踩过坑。2.1 五种 location 写法一次讲透nginx location 后面跟的不同修饰符决定了匹配的方式和优先级。老老实实把这几个记住比你背十篇博客都管用。写法含义匹配方式优先级location /path精确匹配完全相等才匹配最高location ^~ /path前缀匹配且不再检查正则以指定前缀开头次高location ~ /path正则匹配区分大小写按正则表达式匹配低于 ^~location ~* /path正则匹配不区分大小写按正则表达式匹配低于 ^~location /path普通前缀匹配以指定前缀开头但后面还会继续检查正则最低匹配流程可以理解为先做精确匹配再做前缀匹配如果没有 ^~ 拦截再去看正则正则也没中就用最长匹配的普通前缀。一个容易被忽略的点是前缀匹配比的是“最长前缀”不是“最早定义”。比如 location /static/ 和 location /static/img/URI 是 /static/img/logo.png 时会优先选 /static/img/ 这个更长的前缀。正则这块容易踩坑的有两个点一是正则 location 一旦匹配到就立即生效不再看后面的正则二是如果你把 rewrite 写在正则 location 外面很可能会出现重写后再匹配导致的死循环但那是另一个话题了。2.2 root 和 alias路径拼接最容易绕晕的部分这是所有 nginx 新手必踩的坑。不少人配置完发现 CSS 能加载、JS 404或者图片路径不对回头一看十有八九是 root 和 alias 用混了。root 的拼接逻辑是root 指定根目录 完整 URI 路径。比如location /static/ { root /data/www/blog; }访问 /static/js/app.js 时nginx 会去查找 /data/www/blog/static/js/app.js 这个文件。注意这里的 /static/ 这段 URI 也被拼在了文件路径里。alias 的拼接逻辑是aliaa 指定的目录直接替换掉 location 前缀。比如location /static/ { alias /data/www/blog/assets/; }访问 /static/js/app.js 时nginx 会去查找 /data/www/blog/assets/js/app.js。也就是说location 前缀 /static/ 被 alias 后面的路径替换掉了。总结一句话root 是“叠加”alias 是“替换”。用 root 就要保证资源在服务器上的路径包含 URI 的完整目录结构用 alias 则可以让磁盘路径和 URI 路径完全不对应。实际经验里我几乎全部用 root除非遇到“URI 路径跟磁盘路径不一致”的场景才用 alias。因为 root 的语义更好理解也更不容易出错。alias 还有个细节location 用正则时alias 里一般要带上捕获组否则容易出现路径末尾少斜杠的问题。2.3 try_files解决单页应用刷新 404 的关键指令history 路由模式下的单页应用服务端只有一个 index.html。用户访问 /about、/user/1 这些路径服务器上并没有对应的 about 目录如果不处理直接 404。try_files 的作用就是依次尝试文件是否存在不存在就按你指定的规则回退。最常见的配置是location / { root /data/www/blog; index index.html; try_files $uri $uri/ /index.html; }这行的意思是收到请求先看 URI 对应的文件存不存在$uri再看 URI 对应的目录是否存在$uri/都不存在就返回 /index.html由前端路由接管。这里有几个细节值得注意$uri 是按 root 拼接后的绝对路径去匹配的不是凭印象猜的。不要写成 try_files $uri $uri/ 404除非你真的想让未知路径返回 404否则单页应用路由就废了。如果 index.html 又依赖了其他资源比如 JS 文件名带 hash你的 index.html 里引用的路径一定要写绝对路径/js/app.js不要写相对路径js/app.js。否则在 /about 这种嵌套路径下相对路径会解析成 /js 下的 js/app.js直接 404。2.4 页面文件和资源文件的访问控制区别页面文件和资源文件的配置在很多项目里是不相同的。页面文件index.html不能被缓存因为每次发布后内容都变缓存可能导致用户拿到旧页面。资源文件js/css/img文件名带 hash内容不变就可以永久缓存。所以规范的做法是把 index.html 和静态资源分开配置location /index.html { root /data/www/blog; add_header Cache-Control no-cache, no-store, must-revalidate; } location /assets/ { root /data/www/blog; expires 30d; add_header Cache-Control public, immutable; }这样 index.html 每次都回源重新拿而 JS/CSS 这些带 hash 的文件可以放心走浏览器缓存加载速度快很多。3. 实操过程与核心实现从零配置一个静态资源站点理论铺垫得差不多了这一节直接给一套能上线的完整配置。场景是前后端分离项目前端用 Vue 打包生成 dist 目录后端 API 跑在 8080 端口。目标是通过 nginx 实现静态托管、接口转发、压缩、缓存、跨域支持。3.1 完整配置示例可直接抄作业server { listen 80; server_name example.com; # 页面入口不缓存 location /index.html { root /data/www/example; add_header Cache-Control no-cache, no-store; } # 带 hash 的静态资源走浏览器缓存 location /assets/ { root /data/www/example; expires 30d; add_header Cache-Control public, immutable; access_log off; } # 首页和路由回退 location / { root /data/www/example; index index.html; 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-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 开启 gzip 压缩 gzip on; gzip_types text/plain text/css application/json application/javascript image/svgxml; gzip_min_length 1k; gzip_vary on; }这套配置覆盖了动静分离的常见需求。第 4 到 8 行把 index.html 单独拎出来目的是让它不缓存第 10 到 15 行把 assets 路径的资源设置为 30 天缓存第 17 到 20 行是普通的静态资源回退第 22 到 26 行是把 /api/ 请求转发到本地 8080 端口的后端服务。3.2 部署前的环境准备和目录操作先把打包产物上传到服务器。我的建议是不要覆盖式上传用版本目录的方式管理mkdir -p /data/www/example cd /data/www/example # 假设你上传了 example-20250115.tar.gz tar -zxvf example-20250115.tar.gz如果项目已经有多套版本可以采用软链方案ln -s /data/www/releases/example-20250115 /data/www/example这样发布新版本时只需要把软链切到新目录回滚就切回旧目录非常干净。很多人喜欢直接把文件传到 /usr/share/nginx/html 下不是不行但那个目录是系统管控的权限和路径都不太可控不建议在生产环境使用。3.3 用 gzip 和缓存把静态资源速度拉满gzip 对文本资源的压缩率通常在 60% 到 80%效果立竿见影。尤其是 JS、CSS、JSON 这些文件压缩后体积大幅缩小加载时长能明显缩短。配置里我写了 gzip_types这一步很关键。只开 gzip on 不够得告诉 nginx 对哪些 MIME 类型做压缩。要特别注意图片文件jpg/png/gif本身已经是压缩格式再开 gzip 不仅费 CPU体积也不会下降没必要加进去。缓存策略方面我建议细分一下index.htmlno-cache每次都回源避免用户看到旧页面。带 hash 的文件长缓存文件名一变URL 就变自动走新资源。图片/字体medium 缓存比如 7 到 30 天结合实际情况配置。配置生效后可以用 curl 检查响应头curl -I http://localhost/index.html curl -I http://localhost/assets/js/app.abc123.js看返回的 Cache-Control 和 Content-Encoding 是否符合预期。如果是 gzip 压缩生效响应头里会有 Content-Encoding: gzip。3.4 跨域问题和反向代理的联调前后端分离开发时前端页面在 localhost:8081后端接口在 example.com/api必然存在跨域问题。生产环境下我通常不直接用前端代码发请求去访问后端域名而是让 nginx 统一收敛所有请求都走同一个域名通过 location 区分是静态资源还是接口。这样就避免了对每个接口加跨域头的麻烦。如果有些接口确实需要第三方域名直接访问那就在 location 里加location /api/ { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods GET, POST, PUT, DELETE, OPTIONS; add_header Access-Control-Allow-Headers Content-Type, Authorization; if ($request_method OPTIONS) { return 204; } }注意跨域配置里的 OPTIONS 预检请求一定要处理否则浏览器会报“invalid CORS request”。nginx 本身没有专门的 CORS 模块所以处理方式就是在 location 里先拦截预检请求。热词里有人搜“nginx invalid cors request”基本就是没处理预检。3.5 静态资源站点的高并发调优参数如果你的站点访问量不小几个核心参数值得关注worker_processes auto; worker_connections 10240; sendfile on; tcp_nopush on; keepalive_timeout 65; server_tokens off;sendfile 开启后nginx 发送文件不再经过用户态的缓冲区直接在内核态完成性能提升非常明显。tcp_nopush 让数据包填满后再发送减少了网络交互次数。keepalive_timeout 设置的是客户端长连接的保持时间太短会频繁握手太长占用连接资源65 秒是一个比较平衡的值。server_tokens 建议关掉这样响应头里不暴露 nginx 版本号也更安全。如果你想进一步降低带宽压力还可以在根 location 下加 limit_rate限制单个连接的下载速度这个看你的实际运营需求。4. 常见问题与排查技巧实录部署静态资源的过程不会一帆风顺这一节把我实际工作中遇到的典型问题全部列出来按现象、原因、解决办法的方式整理方便当作排查手册用。4.1 404 和 403 常见原因速查表现象可能原因排查方法访问 / 返回 404root 路径配错或 index 文件不存在检查 root 路径下是否有 index.html看 nginx error.log访问 /about 返回 404没配 try_files 回退检查 location / 里是否有 try_files $uri $uri/ /index.htmlCSS 加载了但 JS 404JS 引用路径是相对路径路由嵌套时解析错改引用路径为绝对路径 /js/app.js或配置 try_files目录列表 403没配置 index且目录里没有 index.html指定 index 文件或确定文件确实存在上传后没生效文件权限或 nginx 缓存检查文件权限执行 nginx -s reload 而不是 restart403 里还有一个常见坑是文件权限。默认情况下 nginx worker 进程以 nginx 用户运行如果上传的文件 owner 是 root 且权限是 600nginx 根本没权限读取。我通常统一执行chown -R nginx:nginx /data/www/example4.2 单页应用刷新 404 的终极排查思路如果 try_files 配置了还 404我建议按下面的流程排查先确认解析到的服务器目录确实存在 index.html。在 location / 里手动 curl 一个不存在的路径比如curl -I http://localhost/nonexist看返回的是不是 index.html 的内容。如果返回的还是 404排查是否存在其他 location 拦截。比如你写了个location ~ \.php$或者location ~ \.html$的正则规则正则匹配优先级更高会先于普通前缀匹配执行把请求拦走。检查 try_files 的第三个参数是否写成了 index.html而不是 /index.html。少了前导斜杠会导致路径拼接错误。一个非常容易忽略的细节nginx 的根 location 里root 后面的路径要带 / 结尾。如果你的 root 是/data/www/example/try_files 回退到 /index.html 时实际查找的路径是 /data/www/example/index.html这是对的。但如果 root 写成了/data/www/example路径拼接时容易出现双斜杠或漏斜杠的情况虽然多数时候 nginx 能容错但最好还是统一带上末尾斜杠。4.3 SSL 证书更换不生效的排查要点热词里有“nginx 替换 ssl 证书不生效”这个问题我遇到过不止一次。换了证书后访问还是旧证书甚至浏览器提示证书无效原因通常有几个没有 reload nginx。很多同学替换了证书文件后以为会自动生效其实不会。一定要执行nginx -t nginx -s reload证书文件路径配置的是软链而软链指向了旧文件。排查方法是用ls -l看证书文件是不是软链。浏览器和系统缓存了旧证书信息。证书更换后如果测试环境开着代理或者本地 DNS 缓存可能会拿到旧证书。可以用在线检测工具查询证书链也可以临时把本地 hosts 指向服务器用 curl -v 查看实际返回的证书序列号。443 端口被其他进程占用nginx 实际没监听成功。这个用ss -ltnp | grep 443查看。证书替换的正确姿势是证书文件放到固定目录执行 reload 后用openssl s_client -connect example.com:443 -servername example.com查看输出的证书有效期和签发机构确认新的已经生效。4.4 跨域请求报 invalid CORS request出现这个报错绝大多数原因是浏览器发送了 OPTIONS 预检请求但服务端没有正确处理。nginx 层面做好两点配置 add_header 允许跨域。对 OPTIONS 请求直接返回 204。如果配置了但没生效注意一个细节add_header 指令只有在当前 location 返回 200/204/301/302/304 时才会追加响应头。如果后端返回了其他状态码比如 500add_header 可能不生效浏览器就会觉得没收到 CORS 头。另一个问题是 add_header 的生效范围是按 location 隔离的你在根 location 配了未必能覆盖到 /api/ 这个 location。4.5 平滑升级 nginx 的正确姿势热词里有“nginx 平滑升级”这里顺带说下。nginx 支持在不停机的情况下升级二进制版本。流程是# 1. 下载新版 nginx 编译 # 2. 拷贝新二进制到旧 nginx 目录但先备份旧的 cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old # 3. 给新二进制发送 USR2 信号nginx 会启动新的 worker kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid) # 4. 等待新 worker 接管给旧 master 发 WINCH 信号优雅关闭旧 worker kill -WINCH $(cat /usr/local/nginx/logs/nginx.pid.oldbin)升级完成后建议保存旧二进制文件并做好回滚预案。如果新版本有问题重新发 HUP 信号让旧版本接管即可。这里提醒一句编译新 nginx 时留意模块兼容性尤其是你用了第三方模块的情况下别只顾着升版本结果模块不兼容。4.6 排查流程总结先看日志再猜原因排查 nginx 问题的第一原则是日志比经验靠谱。默认的日志路径在 /var/log/nginx/ 下access.log 记录访问情况error.log 记录错误信息。我处理任何 nginx 问题的第一步都是tail -f /var/log/nginx/error.log然后去复现问题看日志打印了什么。error.log 里的 error 级别信息非常直白会说“directory index of /data/www/example is forbidden”这种一看就知道是 index 文件缺失。access.log 里能看到请求路径和返回状态码可以快速定位是哪个 location 没有匹配上。有些人一上来就去翻配置文件、怀疑 DNS、怀疑防火墙这个思路不对。日志区会把 90% 的问题直接告诉你在哪先看日志再用 curl 验证最后才动手改配置。5. 根据实际场景扩展的两类配置变体静态资源部署不是一套配置吃遍天下的不同场景有不同细节。这里补两类我在业务里经常遇见的配置变体。5.1 多项目多域名的 server 块隔离一台服务器上跑多个项目需要为每个域名配置独立的 server 块。根据 server_name 区分请求归属server { listen 80; server_name a.com; root /data/www/a; # ... a 项目的配置 } server { listen 80; server_name b.com; root /data/www/b; # ... b 项目的配置 }注意两点一是 server_name 不要重复二是如果服务器只有一个公网 IP多个域名都监听 80nginx 依靠 Host 头来区分虚拟主机。域名没解析到这台服务器或者 Host 头不对就会走默认 server 块这可能是你看到“怎么访问 A 域名出来的是 B 项目”的原因。5.2 图片服务器的单独优化如果你的场景是专门托管图片和普通静态资源站点有点区别。图片数量大、体积相对大且绝大部分访问来自网页引用这时候需要单独做缓存路径和防盗链location ~* \.(jpg|jpeg|png|gif|webp|ico)$ { root /data/www/img; expires 7d; access_log off; # 简单的防盗链校验 valid_referers none blocked *.example.com; if ($invalid_referer) { return 403; } }valid_referers 是 nginx 自带的防盗链模块允许空 Referer、禁止不同来源的请求。这个配置适合对资源保护有要求的场景比如你不想自己的图片被别的站点直接引用。但也要提醒一句Referer 是可以伪造的防盗链只能起到“君子协议”的作用真正严格的资源保护还是得靠鉴权和签名 URL这些就超出静态资源部署的范畴了需要后端配合。5.3 限流和访问控制热词里有“nginx 反向代理 tcp 最大连接数”这说明有人在高并发下遇到连接数瓶颈。nginx 的连接数受 worker_connections 和 worker_processes 共同限制理论上限是两者的乘积。如果你的 Nginx 单节点扛不住了常规手段之一是对某些接口或资源做限流# 按 IP 限制每秒请求数 limit_req_zone $binary_remote_addr zonestatic_limit:10m rate5r/s; location /download/ { limit_req zonestatic_limit burst20 nodelay; root /data/www/files; }这里的 rate5r/s 表示平均每秒 5 个请求burst20 表示允许 20 个突发请求nodelay 表示突发请求不排队。限流配置要在 http 块里定义 zone再在 location 里引用。写在最后几个服务器上验证过的操作习惯静态资源部署这件事技术深度不算高但影响面很大。一个配置错误可能导致整站 404、白屏、资源加载不全而这些问题往往在发布后才暴露。所以最后分享几个我在实操中反复验证过的习惯这些通常是用血泪换来的。第一每次改配置前先备份。我会习惯性地在 nginx 配置目录里建一个 backup 目录每次改动前把当前配置复制一份按日期命名。这样出了问题能快速回滚不用靠记忆去猜之前是什么样。第二改完配置必须 nginx -t。这个命令检查配置文件语法很多低级错误在 reload 前就会被拦截掉。养成习惯之后基本杜绝了“reload 失败导致 nginx 挂掉”的惨剧。第三发布静态资源时设置好文件属主和权限。我见过太多人排查半天配置最后发现是权限问题。统一执行 chown -R nginx:nginx 和 chmod -R 755能省掉大量无谓的排查时间。第四线上环境的日志一定要开 access_log 和 error_log。有人为了省磁盘把日志关了等出问题时一片漆黑想查什么数据都没有。日志是最基础的可观测性这个钱省不得。静态资源部署的核心并不在于会敲多少条命令而在于能够理解 nginx 如何将 URI 映射到磁盘文件理解 location 的匹配规则理解缓存和压缩对性能的影响。把这些底层逻辑吃透之后不管配置怎么变、场景怎么换你都能顺着思路推导出来。这套方法我自己用了将近十年从单机静态站到大规模的 CDN 源站、动静分离架构底层逻辑从未变过也希望你在实际部署中少走些弯路。
返回列表