资讯详情

资讯详情

Nginx下SPA刷新404?try_files配置与HTTPS本地调试指南

1. 先从现象说起刷新怎么就404了1.1 两种最常见的场景SPA路由刷新和纯静态路径如果你访问https://localhost/index第一次正常按 F5 刷新后直接变成 404不用太焦虑这个问题在本地开发里属于“每天都有几百个人遇到”的典型坑。我最初看到这个标题时第一反应就是 Nginx 的try_files没写对或者干脆忘了写。标题里的httpslocalhostindex虽然挤成了一串但拆开来就是“https、localhost、index”大概率是你的入口页面叫 index或者项目部署在/index这个路径下然后用 HTTPS 访问时刷新报错。这个现象最常见的场景有两种。第一种是单页应用SPA用了前端路由比如 Vue 的 history 模式、React 的 BrowserRouter页面在浏览器里跳转时 URL 变了但并没有真正向服务器请求新的 HTML一旦你在这个深层链接上手动刷新浏览器就会直接向 Nginx 请求服务器上不存在的那个路径Nginx 没有回退规则时当然返回 404。第二种是纯静态站点也会踩到比如你在 HTML 目录里只放了index.html但手动访问https://localhost/index/foo磁盘上没有foo这个文件或目录Nginx 按请求 URI 找文件找不到就 404。这两种情况的本质是一样的Nginx 默认只负责按路径找真实文件不关心你前端路由怎么跳。把问题定位清楚才能少走弯路。1.2 为什么第一次能进、刷新就失败很多人困惑的点在于既然首页能进说明 Nginx 配置里 root 和 index 应该没问题那为什么刷新就挂其实这里有一个容易被忽略的事实首次访问https://localhost/时Nginx 会按index指令自动找index.html所以能正常显示。这个行为是 Nginx 自带的“目录默认首页”逻辑。但刷新时浏览器会把当前地址栏里的完整路径原样发过去比如https://localhost/index/foo。Nginx 收到请求后第一步是把这个 URI 拼接到 root 目录后面去磁盘上找对应的文件。磁盘上只有index.html没有index/foo文件或目录很多配置又会忽略“找不到就返回到入口页”的兜底逻辑于是直接返回 404。你可以把 Nginx 理解成一个很听话的图书管理员他只会按书名找实体书你让他找一本不存在的书他不会给你推荐别的只会告诉你“没这本书”。如果你还用了 HTTPS这里会多一层干扰。有时候http://localhost下刷新正常切到https://localhost就 404多半是因为你写了两个 server 块HTTP 的 server 块里加了try_filesHTTPS 的 server 块里漏了。证书本身不会导致 404但配置不全会导致 HTTPS 下的行为跟 HTTP 不一致。1.3 另外两个隐藏元凶路径拼接和证书协议除了try_files还有两个容易被忽略的点。一个是 root 和 alias 的路径拼接问题。假设你前端项目构建后的产物放在/data/www/index目录下你想通过https://localhost/index访问这个项目如果你用了alias那么请求/index/foo会映射到/data/www/index/foo看起来没问题但try_files里的路径是相对 root 来解析的不是相对 alias。这个“解析基准不一致”很容易让你配出来的规则在刷新时失效甚至让人越调越乱。另一个是证书和浏览器缓存。自签名证书在本地 HTTPS 场景下很常见如果你用https://localhost/index访问时浏览器提示证书不可信或者证书只签了127.0.0.1没签localhost刷新时浏览器可能直接拦截请求表现成页面打不开。虽然这不是 404但很多人会把这种“打不开”和“404”混在一起排查白白浪费时间。2. 核心原理拆解Nginx是怎么找文件的2.1 location、root 与 index 的协作关系要彻底搞定刷新 404必须理解 Nginx 处理一个静态文件请求的完整链条。以最简单的配置为例server { listen 443 ssl; server_name localhost; root /data/www; index index.html; location / { try_files $uri $uri/ /index.html; } }用户访问https://localhost/index时Nginx 会先看请求 URI 匹配哪个 location。这里是location /匹配所有请求。然后它拿着 URI 里的/index去和 root 拼得到/data/www/index。接下来Nginx 会到磁盘上看这个路径是什么如果它是一个文件就直接返回如果它是一个目录就去这个目录里找index指令配置的默认文件比如index.html如果它什么都不存在就看 location 里有没有try_files没有的话直接 404。很多人以为配置了root /data/www; index index.html;之后所有路径都能自动映射到对应的 HTML 文件其实不是。index指令只在“请求的 URI 以/结尾”或者“URI 对应的是一个目录”时才会生效。像/index这种不带尾部斜杠的路径如果磁盘上确实有一个叫index的文件那没问题如果磁盘上只有index.html但你把入口设计成/index那 Nginx 找的是/data/www/index而不是/data/www/index.html自然就 404 了。这也是为什么标题里的/index特别容易让人困惑它到底是“一个叫 index 的文件”还是“一个叫 index 的目录”还是“一个叫 index 的前端路由”配置方式完全不同。我的建议是先在服务器上执行ls -l /data/www看一眼实际目录结构搞清楚入口文件到底叫什么再写配置别凭感觉猜。2.2 try_files 的工作机制与 fallbacktry_files是解决刷新 404 的核心指令它的语法是try_files file1 [file2 ...] fallback;它按顺序检查每个参数对应的文件在磁盘上是否存在。如果存在就对该文件做内部重定向并返回如果不存在就继续查下一个如果全部不存在就用最后一个参数作为兜底。最后一个参数可以是一个 URI也可以是命名 location写法非常灵活。最常见的 SPA 配置是location / { try_files $uri $uri/ /index.html; }$uri是当前请求的 URI比如/index/foo。第一个检查项是请求路径本身对应的文件第二个是请求路径作为目录时找默认首页第三个是兜底所有找不到的都交给/index.html。这里的关键是最后一个参数必须带上根路径/。如果你写成index.htmlNginx 会把它当作相对路径去拼接结果可能指向错误位置。我之前就见过有人把 fallback 写成try_files $uri $uri/ index.html;结果请求/index/foo时内部跳转到了/index/foo/index.html照样 404。如果你想用命名 location 做更精细的控制也可以这样location / { try_files $uri $uri/ fallback; } location fallback { rewrite ^ /index.html last; }命名 location 的好处是可以额外加一些头信息或者重写规则但对于绝大多数本地开发和静态部署场景直接写/index.html就已经够了。2.3 为什么刷新会请求到磁盘文件Nginx 本身是一个高性能静态服务器它默认不“认识”你前端路由的规则。对于它来说https://localhost/index/login就是一个静态文件请求它只关心磁盘上有没有login这个文件或目录。如果你的前端项目用了 history 路由浏览器地址栏里的/index/login只是前端框架模拟出来的路径真实文件并不存在。首次通过点击链接跳转时前端框架拦截了浏览器默认行为不发出真实请求一旦刷新浏览器可不会再配合你它会老老实实把 URL 发给服务器。这种情况其实不只在本地开发会遇到生产环境部署 SPA 时同样会踩。区别只是生产环境一般有团队沉淀好的 Nginx 模板而本地开发经常是从零开始写配置漏掉try_files的概率就高得多。用 curl 模拟一下就能看到全过程curl -k -i https://localhost/index/login如果返回的是404 Not Found而且响应体是 Nginx 默认的html...Not Found/html基本可以断定是没有兜底规则如果返回的是 200 且内容里有你前端项目里index.html的标记说明try_files已经生效了。2.4 用生活化的类比理解整个链路我用一个图书馆管理员的例子帮你建立直观印象。想象你去图书馆第一次你说“我要去大厅”管理员把你领到了大厅大厅里摆着一本《总目录》一切正常。然后你在里面逛了一圈记下了一个位置叫“三楼靠窗A区书柜”下次你直接告诉管理员“带我去这个位置”管理员去查楼层目录发现这个位置根本没有登记他就会说“没这个地方”也就是 404。try_files相当于你提前告诉管理员“如果以后有读者问一个我这边没有登记的位置直接把他带回大厅让他自己用《总目录》去找。”也就是说所有不存在的路径都回到index.html由前端路由自己去解析 URL 并渲染正确页面。这样既保持了地址栏里 URL 的“假象”又能让刷新之后页面依然正常显示。3. 实操修复一套可以“抄作业”的配置3.1 最简 SPA 配置模板含 HTTPS先给出一套我本地经常用的最小配置适合入口文件就是index.html、项目直接部署在根目录的场景server { listen 443 ssl; server_name localhost; ssl_certificate /usr/local/etc/nginx/ssl/localhost.crt; ssl_certificate_key /usr/local/etc/nginx/ssl/localhost.key; root /data/www; index index.html; location / { try_files $uri $uri/ /index.html; } }解释几个关键点listen 443 ssl;表示监听 HTTPS 端口并启用 SSL。ssl_certificate和ssl_certificate_key指向本地自签名证书。root /data/www;是站点根目录你的构建产物要放在这个目录下面。location / { try_files $uri $uri/ /index.html; }是所有静态路径的兜底规则。如果你访问的入口地址是https://localhost/index而不是https://localhost/那么你要保证请求/index时也能落到这个 location。上面这套配置中location /会匹配所有请求所以/index也会走到try_files最终回退到/index.html。也就是说只要磁盘上有/data/www/index.html访问https://localhost/index就会返回它。这里还有一个容易踩的细节如果你希望访问https://localhost/和https://localhost/index显示同一个页面但后者在刷新https://localhost/index/foo时正常那么try_files的 fallback 一定要写/index.html不要写/index/index.html除非你的入口文件真的在index子目录里。这个要看实际目录结构别照搬模板。3.2 带子路径的配置项目在 /index 下如果你的前端项目不是部署在根目录而是希望访问https://localhost/index时进入一个叫 index 的项目并且所有前端路由都在/index/...下那配置要稍微调整。假设你的构建产物放在/data/www/index目录下最稳妥的方案是用 root 而不是 aliasserver { listen 443 ssl; server_name localhost; ssl_certificate /usr/local/etc/nginx/ssl/localhost.crt; ssl_certificate_key /usr/local/etc/nginx/ssl/localhost.key; root /data/www; index index.html; location /index { try_files $uri $uri/ /index/index.html; } }这里root /data/www;意味着请求/index会映射到/data/www/index请求/index/foo会映射到/data/www/index/foo。由于磁盘上只有/data/www/index/index.html没有/data/www/index/foo所以try_files会把请求回退到/index/index.html。注意 fallback 里的路径也是基于 root 的因此写/index/index.html才对应磁盘上的/data/www/index/index.html。很多人在这个场景下会尝试用一个location /index加alias /data/www/index;的写法但try_files和 alias 组合时有一个非常隐蔽的坑Nginx 在解析try_files内部路径时使用的是 root 上下文而不是 alias 上下文。假设你写了location /index { alias /data/www/index; try_files $uri $uri/ /index/index.html; }这里$uri是/index/foo在 alias 下它确实映射到/data/www/index/foo看起来没问题但 fallback 的/index/index.html是按 root 解析的如果 root 不是/data/www就会解析到一个奇怪的位置导致 404。这个坑我踩过两次排查的时候看日志发现 Nginx 去/data/www/index/index.html找文件但明明文件就在那里后来才意识到是 root 和 alias 的解析基准不一致。建议子路径场景一律用 root 物理目录结构简单直接不容易出错。3.3 生成本地 HTTPS 证书并在 Nginx 中启用标题里带了 https所以本地证书这块还是要讲一下。如果只是为了本地开发调试可以用 openssl 命令生成自签名证书。我一般会同时把localhost和127.0.0.1都加进 SAN 扩展里不然浏览器可能会提示证书名称不匹配。生成证书的命令如下openssl req -x509 -newkey rsa:2048 \ -nodes \ -keyout localhost.key \ -out localhost.crt \ -days 365 \ -subj /CNlocalhost \ -addext subjectAltNameDNS:localhost,IP:127.0.0.1参数说明-x509表示直接生成自签名证书而不是证书签名请求。-newkey rsa:2048生成一个 2048 位的 RSA 私钥。-nodes表示私钥不加密这样 Nginx 启动时不需要输入密码。-keyout和-out分别是私钥和证书的输出路径。-subj /CNlocalhost设置证书的通用名称。-addext是 OpenSSL 1.1.1 以上版本支持的扩展字段把localhost和127.0.0.1都写进 SAN。生成后把两个文件放到 Nginx 能读取的目录比如/usr/local/etc/nginx/ssl/然后在 server 块里引用。配置改完后记得执行nginx -t检查语法再nginx -s reload重载。有一点要特别说明自签名证书导致的浏览器“不是安全连接”提示和刷新 404 是两回事。如果你用curl -k https://localhost/index能拿到 200但浏览器里提示证书错误那是证书信任问题不是 Nginx 路径问题。很多人在这两者之间反复横跳浪费时间。你可以在浏览器里点“高级”继续访问或者把证书导入系统钥匙串后重新打开。3.4 修改后如何验证配置和重启配置写完不要急着刷新浏览器先在终端里验证。第一步是检查配置语法nginx -t如果输出类似syntax is ok和test is successful说明配置语法没问题。然后重载nginx -s reload重载后再用 curl 模拟实际请求curl -k -i https://localhost/index curl -k -i https://localhost/index/foo第一条命令用来确认入口页面能被正常访问第二条命令用来验证深层路由是否也能返回入口页面的内容而不再是 404。-k参数是告诉 curl 跳过证书校验因为本地自签名证书本来就不受信任。如果第二条返回了 HTTP/200 且响应体里包含你的前端项目内容就可以放心去浏览器里按 F5 了。如果 curl 仍然返回 404再去翻 Nginx 的错误日志。默认日志路径一般是在/usr/local/var/log/nginx/error.log或者/var/log/nginx/error.log具体看你编译安装时配置的路径。日志里如果出现/index/foo对应的open() failed ... No such file or directory那就说明 Nginx 确实在尝试找磁盘文件但没找到属于典型的try_files缺失或 fallback 配置错误。4. 常见问题排查实录404到底怎么“查案”4.1 先分清楚是 Nginx 返回的 404 还是应用返回的 404排查任何 404 问题第一步永远是分清楚这个 404 是谁产生的。Nginx 返回的 404 通常是它自带的错误页响应体很短大概长这样html headtitle404 Not Found/title/head body centerh1404 Not Found/h1/center hrcenternginx/1.25.3/center /body /html而应用自己返回的 404 会包含你前端项目里的内容比如 Vue Router 或 React Router 渲染的空白页、错误提示等。区分方法很简单用 curl 看响应体的 HTML 里有没有你的项目特征。如果返回的是 Nginx 默认错误页说明请求压根没有进入前端应用问题出在 Nginx 的路由兜底如果返回的是你的应用页面但状态码是 404那通常是前端路由配置的问题比如 history 模式下的服务端 fallback 没配好或者路由路径写错了。我在本地排查时习惯先看 HTTP 状态码加响应体前 200 个字符。你可以用curl -k -s https://localhost/index/foo | head -c 200如果看到centernginx/center基本就是 Nginx 层的问题直接往下查 location 和 try_files如果看到你自己的div idapp之类的结构那就去检查前端路由配置。4.2 curl 和日志的配合使用curl 是排查这类问题最好用的工具因为它不受浏览器缓存、证书弹窗、前端跳转逻辑的影响能看到服务器真正返回了什么。我常用的几个命令# 查看响应头重点看状态码和 Server 字段 curl -k -I https://localhost/index/foo # 查看完整响应头与响应体 curl -k -i https://localhost/index/foo # 指定 Host 请求绕过 DNS 和 server_name 匹配干扰 curl -k -H Host: localhost -i https://127.0.0.1/index/foo第三条命令在排查多个 server 块的场景特别有用。如果你本机除了 Nginx 还有别的服务占用了 443 端口或者 Nginx 里配置了多个 server 块直接访问https://localhost可能会落到默认 server而不是你预期的那一个。用curl -k -H Host: localhost https://127.0.0.1/index/foo可以绕开 localhost 的 DNS 解析直接请求 Nginx 监听的 IP再通过 Host 头控制 server_name 匹配这样就能确认是不是server_name localhost这个 server 块在生效。日志方面重点看两个文件。access log 记录所有请求的状态码error log 会记录 Nginx 尝试打开文件失败的详细路径。比如 error log 里写着[error] 12345#0: *123 open() /data/www/index/index/foo failed (2: No such file or directory)这个路径非常关键能看到 Nginx 实际是去哪个目录找文件了。如果你以为它在/data/www目录下找/index/foo但日志显示它去/data/www/index/index/foo找那就说明 root 或 fallback 里的路径写错了多了或者少了一层index根据实际目录结构修正即可。4.3 权限、软链接、证书过期等非典型原因有些刷新 404 问题不是路由兜底没写而是文件和权限层面的问题。我遇到过几次这样的情况Nginx 配置完全没问题try_files也写了但 Linux 上目录的权限不对导致 Nginx worker 进程无法读取入口文件。常见现象是index.html能访问但深层目录下的静态资源 403 或 404。这种情况不要在 Nginx 配置里反复折腾先看目录权限ls -l /data/www/index/index.html ps aux | grep nginx第一行看文件权限第二行看 Nginx 的 worker 进程以什么用户运行。如果文件权限是600或者目录没有x权限而 worker 进程是www-data用户那就需要调整权限。我用的是chmod -R 755 /data/www当然具体权限策略要看你的目录结构但本地开发环境直接放开读权限是最省心的方案。另外还有两个非典型原因软链接和证书过期。如果你的/data/www是指向其他位置的软链接Nginx 在读取时可能会因为路径解析不一致而出现问题。排查时可以用realpath或readlink -f查看实际路径确认配置里的 root 没有指错。证书过期则不会表现为 404它会在浏览器里直接拦截请求显示NET::ERR_CERT_DATE_INVALID之类的错误。这种问题要重新生成证书而不是在 Nginx 路径规则里找原因。4.4 一套排查速查表我把常见现象、可能原因、检查手段和解决方向整理成一个表你在排查时可以直接对着看。现象可能原因检查手段解决方向刷新深层路由返回 Nginx 默认 404try_files缺失或 fallback 写错curl error.log添加/修正try_files $uri $uri/ /index.html;只有 HTTPS 下 404HTTP 正常HTTPS server 块漏配try_filesnginx -T对比两个 server 块在 HTTPS 的 server 块里补上兜底规则子路径/index下刷新 404root/alias 解析基准不一致看 error.log 中的实际查找路径改用 root 目录结构避免 alias404 但 error.log 提示 Permission denied目录权限不足ls -lps aux调整文件权限或 Nginx worker 用户curl 返回 200 但浏览器显示证书错误自签名证书未信任或 SAN 缺失浏览器查看证书信息重新生成包含 SAN 的证书并导入信任404 页面是你的应用自定义页面前端路由配置问题检查路由 base 和 history 模式在应用内配置服务端 fallback 支持这个表基本覆盖了我在本地和服务器环境里遇到过的绝大多数刷新 404 场景。你可以把它保存下来下次遇到问题先对号入座比自己一行行翻配置高效得多。最后再分享一个小经验改完 Nginx 配置后别急着在浏览器里验证。浏览器有缓存可能在你按下 F5 之前就已经缓存了旧的 404 响应。我习惯先用 curl 确认服务器层面已经返回 200 了再回浏览器里按 CtrlShiftR 强制刷新这样能避免“明明配置改了浏览器还是 404”的假象。还有排查时要养成看 error.log 的习惯那条open() failed的日志会直接告诉你 Nginx 到底在找哪个文件很多所谓“诡异”的 404其实都只是路径拼接里多了一层或少了一层目录。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →