作弊条:在 Nginx 中正确配置自定义 404 页面

• 本文约 1234 字,阅读大致需要 3 分钟 Cheatsheets #Nginx #Hugo #Web Server #HTTP
目录

最近给blog增加了一个自定义的404错误页面。像Hugo这样的静态网站生成器在构建时,默认会把渲染好的 404 页面直接输出在网站根目录下,即 /404.html

针对这类页面,我们通常希望:

  1. 当访客访问不存在的网址时,服务器呈现该页面的内容,并且HTTP状态码必须是 404
  2. 当有人(或爬虫)直接在浏览器中访问 /404.html 时,同样呈现其内容,但返回的HTTP状态码依然必须是 404,而不是 200 OK

第2点的意义在于, /404.html 本身是一个错误提示页面,而不是正常的文章内容。如果直接访问时返回 200 OK,从HTTP语义上看,它就成了一个「普通的」正常页面;搜索引擎则可能将这种情况判定为soft 404,甚至在无法正确识别时造成无意义的内容收录。

查阅Nginx官方文档中关于 error_page 的说明,最基础的配置方式如下:

error_page 404 /404.html;

当客户端请求一个不存在的路径(例如 /non-existent)时,Nginx在常规流程中找不到对应资源,产生404错误,进而触发 error_page 进行内部重定向到 /404.html,并将404状态码和页面内容一并返回给客户端。

但这无法解决直接访问 /404.html 的情况。因为对于Nginx而言,根目录下确实存在一个名为 404.html 的物理静态文件。如果客户端直接请求 /404.html,Nginx就会将其当成普通静态资源处理,返回 200 OK

想要解决这个问题,可以在配置中增加一个匹配 /404.htmllocation,并使用 internal 指令:

1
2
3
4
5
error_page 404 /404.html;

location = /404.html {
    internal;
}

工作原理

这其中比较重要的是 location 语句块。此处使用了 = 修饰符进行精确匹配。当请求的URI恰好是 /404.html 时,Nginx便会直接选中这个 location 块,不再继续寻找其他匹配。

接下来的 internal 则告诉Nginx这种请求只能由内部发起,如果是来自客户端的外部请求直接尝试访问该 location,Nginx会直接拒绝并返回 404 Not Found。而由 error_pagetry_files 等产生的内部重定向,以及经过 rewrite 改写的请求,则属于Nginx所定义的「内部请求」,可以进入这个 location

这样一来,普通的不存在页面仍按常规流程处理:Nginx产生 404 后,通过 error_page 内部重定向到 /404.html。由于这是内部请求,internal 不会阻止它,因此最终返回 404.html 的内容,同时保持404状态码。

比较复杂的是直接访问 /404.html 的情况。这个请求会首先命中 location = /404.html,但因为它来自客户端,会被 internal 拒绝而产生 404。随后 error_page 又把这个 404 内部重定向到 /404.html。第二次进入该 location 时已经是内部请求,因此可以正常读取文件,最后客户端看到的仍然是自定义页面和 404 状态码。

验证

配置生效后,可以分别通过浏览器和 curl 进行验证:

  1. 浏览器访问:直接在浏览器中访问一个不存在的网址(如 /non-existent/),以及直接访问 /404.html,确认两种情况下都能看到自定义的 404 页面。(那边说开Developer Tools也能看到的同学请坐,如果页面比较复杂的话会产生大量结果,虽然还是能过滤但是不如 curl 来的直接)
  2. 检查HTTP状态码:浏览器界面通常不会直接显示响应状态码。可以通过 curl 检查直接访问两种情况时返回的HTTP状态码,确认返回的是 404 而非 200
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.net/non-existent
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.net/404.html
# 输出均应为:404