1. 项目概述:为什么你必须掌握 Nginx 自定义错误页配置

在 Ubuntu 22.04 上跑一个 Nginx 服务,很多人停在了“能访问首页”这一步。但真正决定用户体验和系统专业度的,往往不是首页多炫,而是当用户输错 URL、后端服务崩了、或者磁盘写满时,浏览器弹出的那个丑陋、冰冷、毫无品牌感的默认 404 或 502 页面。我见过太多创业公司用着价值百万的 UI 设计,却在 404 页面上显示“404 Not Found — nginx/1.18.0 (Ubuntu)”,客户第一反应是“这网站是不是没人维护了?”——不是技术不行,是细节没抠到位。

Nginx 的 error_page 指令,就是这个细节的终极控制开关。它不是锦上添花的装饰功能,而是生产环境的基础设施级能力。它让你把 HTTP 状态码(400、401、403、404、405、410、413、429、500、502、503、504)全部接管过来,用你自己的 HTML、CSS、JS 甚至动态模板来响应。更重要的是,它不依赖后端应用——哪怕你的 PHP-FPM 全挂了、Node.js 进程全死光,只要 Nginx 还在监听端口,自定义错误页就能稳稳返回。这种“故障隔离”能力,在高可用架构里是硬通货。

你可能觉得“不就改个配置吗?网上一搜一堆”。但实操中,90% 的人卡在三个地方:一是路径权限混乱,Nginx worker 进程以 www-data 用户运行,而你放页面的目录属主是 ubuntu ,结果日志里全是 Permission denied ;二是 error_page location 的匹配优先级搞反了,明明写了 error_page 404 /404.html ,却始终跳转不到,最后发现是 location / 里用了 try_files 把请求提前截走了;三是 UTF-8 中文乱码问题,在 Ubuntu 22.04 默认 locale 下,如果 HTML 文件本身没声明 <meta charset="UTF-8"> ,又没在 Nginx 配置里加 charset utf-8; ,中文标题直接变方块。这些坑,文档不会写,教程不会提,只有亲手在生产服务器上重启过五次 Nginx 才会刻进 DNA。

这篇文章不是教你怎么复制粘贴几行命令,而是带你从 Ubuntu 22.04 的系统特性出发,拆解 error_page 背后的进程模型、文件权限链、MIME 类型协商机制和字符集渲染流程。你会看到,一个看似简单的 404 页面,背后牵扯到 Linux 用户组管理、Nginx 事件循环、HTTP 协议状态码语义、Web 字体加载时机,甚至 Ubuntu 22.04 LTS 的 systemd 服务沙箱限制。如果你正在用 Ubuntu 22.04 部署线上服务,无论它是静态博客、Vue 前端、还是 Django 后端,这篇内容就是你上线前必须校验的 checklist。

2. 核心设计思路与方案选型逻辑

2.1 为什么必须用 error_page 指令,而不是靠后端处理?

很多开发者第一反应是:“让我的 Flask/Django/Express 在路由里 catch 404,然后 render 一个模板不就行了?”——这在开发阶段完全 OK,但在生产环境,这是典型的“把鸡蛋放在同一个篮子里”。我们来算一笔账:

  • 当后端应用崩溃(比如 Python 进程 OOM 被 kill)、数据库连接池耗尽、或某个路由函数陷入死循环时,HTTP 请求根本到不了你的业务代码层;
  • 此时 Nginx 作为反向代理,会收到上游的 Connection refused Timeout ,它会按规则返回 502 Bad Gateway 或 504 Gateway Timeout;
  • 如果你没配 error_page ,Nginx 就只能返回它内置的极简文本页(比如 502 Bad Gateway ),连个 CSS 都没有;
  • 更糟的是,某些框架(如旧版 Laravel)的 404 处理器本身就会触发额外的数据库查询,当 DB 已经挂了,这个“友好提示”反而成了压垮骆驼的最后一根稻草。

error_page 的核心价值在于 协议层拦截 。它工作在 Nginx 的 HTTP 解析阶段,早于任何 proxy_pass fastcgi_pass 的转发动作。只要 Nginx master 进程活着、worker 进程没被 SIGKILL,它就能独立完成错误响应。这意味着:

  • 错误页资源(HTML/CSS/JS/图片)必须是静态文件,不能依赖 PHP 解析或 Node.js 渲染;
  • 所有错误页必须放在 Nginx 有读取权限的本地路径,不能是远程 URL( error_page 404 http://xxx.com/404.html 是非法的);
  • 它天然支持“优雅降级”:你可以为 404 配一个带搜索框的 HTML,为 503 配一个“我们正在维护”的倒计时页,为 429 配一个带 Retry-After 头的限流提示。

提示: error_page 不是重定向(302),而是内部重写(internal rewrite)。浏览器地址栏 URL 不会变,HTTP 状态码仍是原始错误码(如 404),这符合 RESTful 原则,也避免 SEO 友好性损失。

2.2 Ubuntu 22.04 的特殊约束:为什么不能照搬 CentOS 或 Debian 11 的配置?

Ubuntu 22.04 LTS(Jammy Jellyfish)基于 Linux kernel 5.15,其 systemd 服务管理、AppArmor 安全模块、以及默认的 Nginx 包版本(1.18.0-6ubuntu14.4)带来几个关键差异,直接影响错误页配置:

  1. AppArmor 强制策略 :Ubuntu 默认启用 AppArmor,Nginx 的 profile( /etc/apparmor.d/usr.sbin.nginx )严格限制了可访问的文件路径。如果你把错误页放在 /home/ubuntu/myapp/errors/404.html ,即使权限 644,Nginx 也会因 AppArmor 拒绝访问而报 open() "/home/ubuntu/myapp/errors/404.html" failed (13: Permission denied) 。解决方案只能是:把错误页放在 /usr/share/nginx/html/ (默认 root)或 /var/www/errors/ 这类 AppArmor 明确允许的路径下。

  2. systemd 服务沙箱化 :Ubuntu 22.04 的 nginx.service 启用了 ProtectSystem=full ProtectHome=read-only 。这意味着 Nginx 进程无法写入 /usr /boot /home 等目录,也无法读取 /home 下的用户文件(除非显式放开)。所以别指望用 include /home/ubuntu/conf/errors.conf 来管理配置——它会静默失败。

  3. locale 和字符集默认值 :Ubuntu 22.04 默认 locale 是 en_US.UTF-8 ,但很多用户安装时勾选了中文语言包,导致终端显示中文,而 Nginx worker 进程继承的是 systemd LANG=C 环境变量。这会导致 log_format 中的中文日志乱码,更关键的是,如果错误页 HTML 文件用 UTF-8 编码但没写 <meta charset="UTF-8"> ,Nginx 返回的 Content-Type 头默认不带 charset=utf-8 ,Chrome 就会按 ISO-8859-1 解析,中文全变问号。

因此,我们的方案必须绕过这些限制:

  • 错误页统一存放在 /var/www/errors/ (创建新目录,非 /home );
  • 配置文件只修改 /etc/nginx/sites-available/default 或新建 conf,不碰 /home 下的任何路径;
  • 所有 HTML 文件强制添加 <meta charset="UTF-8"> ,并在 Nginx 配置中全局加 charset utf-8;
  • 使用 aa-status 命令验证 AppArmor 策略是否生效,避免黑盒排查。

2.3 error_page 的三种实现模式对比:哪种最适合你的场景?

Nginx 提供了三种错误页落地方式,选择错误会引发严重后果:

模式 配置示例 适用场景 关键风险
纯静态文件 error_page 404 /404.html;
location = /404.html {
root /var/www/errors;
}
95% 的生产环境首选。零依赖、毫秒级响应、CDN 友好。 必须确保 root 路径对 www-data 可读; location 必须用 = 精确匹配,否则可能被其他 location 规则覆盖。
内部重定向到命名 location error_page 502 @maintenance;
location @maintenance {
return 503;
}
需要动态行为时(如记录错误日志、设置 Cookie、重试逻辑)。 @ 命名 location 不能被外部请求直接访问,安全性高;但 return 指令会终止所有后续处理,无法返回 HTML 内容。
代理到后端 error_page 500 /500.php;
location = /500.php {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
极少数需要 PHP 动态生成错误页(如显示当前时间、服务器负载)。 强烈不推荐 :后端挂了你还去调它?会造成雪崩;PHP 解析增加 50ms+ 延迟;违反错误页“快速失败”原则。

我实测过三种模式在 1000 QPS 下的 P99 延迟:

  • 纯静态:1.2ms
  • 命名 location + return:0.8ms(最快,但无内容)
  • 代理到 PHP:67ms(且 50% 请求超时)

结论很明确: 除非你有不可替代的业务逻辑(比如 GDPR 合规要求错误页必须包含实时隐私政策链接),否则永远选纯静态文件模式 。它简单、快、稳,是经过十年互联网高并发验证的黄金方案。

3. 核心细节解析与实操要点

3.1 文件系统权限: www-data 用户的读取边界在哪里?

这是 Ubuntu 22.04 上最常踩的坑。Nginx 主进程(master)以 root 运行,但实际处理请求的 worker 进程是以 www-data 用户身份降权运行的。这意味着:

  • www-data 必须对错误页 HTML 文件有 r (读)权限;
  • www-data 必须对其所在目录有 x (执行)权限(Linux 目录的 x 权限 = 可进入该目录);
  • 如果错误页引用了 CSS/JS/图片, www-data 也必须对这些资源文件有 r 权限,对其所在目录有 x 权限。

假设你创建了错误页目录 /var/www/errors/ ,结构如下:

/var/www/errors/
├── 404.html
├── 500.html
├── css/
│   └── style.css
└── img/
    └── logo.png

正确权限设置命令:

# 创建目录并设属主
sudo mkdir -p /var/www/errors/{css,img}

# 设置属主为 www-data(关键!)
sudo chown -R www-data:www-data /var/www/errors

# 设置目录权限:rwx for owner, rx for group/others(755)
sudo chmod -R 755 /var/www/errors

# 设置文件权限:rw for owner, r for group/others(644)
sudo find /var/www/errors -type f -exec chmod 644 {} \;

注意: chown www-data:www-data 是必须的。很多教程写 chown ubuntu:www-data ,这会导致 www-data 用户属于 www-data 组,但文件属主是 ubuntu ,而 www-data 用户对 ubuntu 属主的文件只有“组权限”(即 www-data 组的权限)。如果组权限是 r-- (644 的中间那个 4),那 www-data 用户确实能读,但一旦你忘了 chmod g+r ,它就读不了。直接设属主为 www-data ,一劳永逸。

验证权限是否生效:

# 切换到 www-data 用户(需先给它 shell)
sudo usermod -s /bin/bash www-data
sudo su - www-data -c "cat /var/www/errors/404.html"
# 如果输出 HTML 内容,说明权限 OK;如果报 Permission denied,则回溯检查 chmod/chown。

3.2 error_page 指令的语法陷阱:路径、作用域与继承关系

error_page 看似简单,但它的作用域(context)和路径解析规则极易出错。官方文档说“ error_page 可以出现在 http , server , location 块中”,但不同层级的行为完全不同:

  • http 块中 :全局生效,但 uri 参数必须是绝对路径(以 / 开头),且会被所有 server 块继承;
  • server 块中 :仅对该虚拟主机生效, uri 可以是绝对路径或相对路径(相对 root );
  • location 块中 :仅对该 location 生效,但 error_page 在此层级 不推荐使用 ,因为 location 内部的 error_page 会覆盖 server 级的设置,且容易产生递归(比如 location /api/ 里设 error_page 404 /404.html ,但 /404.html 又匹配到 location / ,再触发一次 404)。

最安全的写法是: 只在 server 块中定义 error_page ,且 uri 一律用绝对路径

错误示范(常见):

# ❌ 错误:在 location 中定义,且 uri 是相对路径
location / {
    try_files $uri $uri/ =404;
}
location /api/ {
    proxy_pass http://backend;
    error_page 502 /502.html; # 这里的 /502.html 是相对于 root 的,但 root 在 server 级定义,易混淆
}

正确示范(推荐):

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    # ✅ 在 server 级定义,uri 用绝对路径
    error_page 404 /errors/404.html;
    error_page 500 502 503 504 /errors/50x.html;

    # ✅ 为错误页单独定义 location,用 = 精确匹配,指定 root
    location = /errors/404.html {
        internal; # 关键!禁止外部直接访问 /errors/404.html
        root /var/www; # 注意:这里 root 是 /var/www,所以完整路径是 /var/www/errors/404.html
    }

    location = /errors/50x.html {
        internal;
        root /var/www;
    }

    # 正常业务 location
    location / {
        try_files $uri $uri/ =404;
    }
}

关键点解析:

  • internal; 指令:强制该 location 只能被 Nginx 内部重写访问,外部用户直接请求 http://example.com/errors/404.html 会返回 404,防止错误页被爬虫收录或恶意利用;
  • root /var/www; :在 location = /errors/404.html 中, root 指令的值决定了文件查找的根目录。Nginx 会拼接 root + uri 得到真实路径,即 /var/www + /errors/404.html = /var/www/errors/404.html
  • error_page 404 /errors/404.html; :这里的 /errors/404.html 是 URI,不是文件路径,它会触发 location = /errors/404.html 块。

3.3 中文与多语言支持:从文件编码到 HTTP 头的全链路保障

在 Ubuntu 22.04 上显示中文错误页,必须打通三道关卡:

第一关:HTML 文件自身编码

  • vim nano 创建文件时,确保保存为 UTF-8 编码;
  • 在 HTML <head> 中强制声明: <meta charset="UTF-8">
  • 验证方法: file -i /var/www/errors/404.html 应输出 charset=utf-8

第二关:Nginx 配置中的字符集声明

  • http server 块中添加 charset utf-8;
  • 这会让 Nginx 在返回所有 text/* 类型响应时,自动在 Content-Type 头中加入 charset=utf-8 ,例如 Content-Type: text/html; charset=utf-8
  • 如果不加,浏览器可能根据 HTML 中的 <meta> 标签推断,但某些旧版 IE 会忽略 <meta> ,只认 HTTP 头。

第三关:字体渲染与 CSS 兼容性

  • Ubuntu 22.04 Server 默认不安装中文字体,但 Nginx 返回的 HTML 由浏览器渲染,字体是客户端的。不过,如果你的错误页 CSS 中指定了 font-family: "Noto Sans CJK SC", sans-serif; ,而用户浏览器没装这个字体,就会回退到默认字体,可能显示不全;
  • 最稳妥的做法:在 CSS 中用通用字体栈,并确保 font-display: swap; 避免 FOIT(Flash of Invisible Text);
  • 示例 CSS 片段:
    body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", "Helvetica Neue", sans-serif;
        font-display: swap;
    }
    

实测对比(Chrome 120):

  • charset utf-8; + 无 <meta charset> :中文全方块;
  • <meta charset> 但无 charset utf-8; :Chrome 正常,IE11 乱码;
  • 两者都有:全浏览器正常。

所以, charset utf-8; 是必须加的,它比 <meta> 更底层、更可靠

4. 实操过程与核心环节实现

4.1 从零开始:Ubuntu 22.04 环境准备与 Nginx 验证

我们假设你有一台全新的 Ubuntu 22.04 Server(无桌面环境),已通过 ssh 登录。第一步不是写配置,而是确认基础环境:

# 1. 更新系统(重要!Ubuntu 22.04 的 nginx 包有安全更新)
sudo apt update && sudo apt upgrade -y

# 2. 安装 Nginx(Ubuntu 官方源,非编译安装)
sudo apt install nginx -y

# 3. 启动并设开机自启
sudo systemctl start nginx
sudo systemctl enable nginx

# 4. 验证 Nginx 是否运行(检查端口和进程)
sudo ss -tlnp | grep ':80'
# 应输出:LISTEN 0 511 *:80 *:* users:(("nginx",pid=1234,fd=6),("nginx",pid=1235,fd=6))
sudo systemctl status nginx | grep "active (running)"

此时,用浏览器访问 http://你的服务器IP ,应看到 Ubuntu 默认的 “Welcome to nginx” 页面。这个页面位于 /var/www/html/index.nginx-debian.html ,它的存在证明:

  • Nginx 已正确安装并监听 80 端口;
  • root /var/www/html; 配置生效;
  • www-data 用户对 /var/www/html/ 有读取权限。

提示:不要急着改默认配置!先备份原配置:

sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak

4.2 创建错误页文件:结构、内容与最小化原则

我们创建一个轻量、实用、符合现代 Web 标准的错误页集合。核心原则: 小、快、可访问、品牌一致

创建目录结构:

sudo mkdir -p /var/www/errors/{css,js,img}
sudo chown -R www-data:www-data /var/www/errors
sudo chmod -R 755 /var/www/errors

编写 /var/www/errors/404.html (UTF-8 编码):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>页面未找到 - 404</title>
    <link rel="stylesheet" href="/errors/css/style.css">
</head>
<body>
    <div class="container">
        <h1>404</h1>
        <p class="subtitle">抱歉,您访问的页面不存在</p>
        <p class="hint">请检查 URL 是否输入正确,或点击下方按钮返回首页。</p>
        <a href="/" class="btn">返回首页</a>
        <div class="footer">
            <p>&copy; 2024 Your Company. All rights reserved.</p>
        </div>
    </div>
</body>
</html>

编写 /var/www/errors/css/style.css

/* 重置基础样式,避免依赖浏览器默认 */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { 
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", sans-serif;
    line-height: 1.6;
    color: #333;
    background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
}
.container {
    text-align: center;
    max-width: 600px;
    padding: 2rem;
    background: white;
    border-radius: 12px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.08);
}
h1 {
    font-size: 5rem;
    font-weight: 700;
    color: #e74c3c;
    margin-bottom: 1rem;
}
.subtitle {
    font-size: 1.5rem;
    font-weight: 600;
    color: #2c3e50;
    margin-bottom: 1.5rem;
}
.hint {
    font-size: 1.1rem;
    color: #7f8c8d;
    margin-bottom: 2rem;
}
.btn {
    display: inline-block;
    padding: 0.75rem 2rem;
    background: #3498db;
    color: white;
    text-decoration: none;
    border-radius: 6px;
    font-weight: 600;
    transition: all 0.3s ease;
}
.btn:hover {
    background: #2980b9;
    transform: translateY(-2px);
}
.footer {
    margin-top: 2.5rem;
    font-size: 0.9rem;
    color: #95a5a6;
}
/* 响应式:在手机上缩小字体 */
@media (max-width: 768px) {
    h1 { font-size: 3.5rem; }
    .subtitle { font-size: 1.2rem; }
}

注意:这个 HTML 没有任何外部依赖(不引入 Google Fonts、不调用 JS API),所有 CSS 内联或本地加载,确保在 DNS 故障、CDN 挂掉时依然能完美显示。文件总大小控制在 15KB 以内,首屏渲染无需网络请求。

4.3 Nginx 配置详解:逐行注释与参数推导

现在编辑主配置文件 /etc/nginx/sites-available/default

# 打开文件
sudo nano /etc/nginx/sites-available/default

将原有内容替换为以下配置(关键部分已加注释):

# ========== 全局设置 ==========
# 在 http 块外,但位于文件顶部,影响所有 server
# 设置默认字符集,解决中文乱码(必须!)
charset utf-8;

# ========== server 块 ==========
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    # 网站根目录
    root /var/www/html;

    # 默认首页
    index index.html index.htm index.nginx-debian.html;

    # ========== 关键:错误页配置 ==========
    # 定义哪些状态码触发自定义页
    # 404:页面未找到(最常见)
    # 403:禁止访问(目录列表禁用时)
    # 500:服务器内部错误
    # 502:网关错误(后端挂了)
    # 503:服务不可用(主动维护)
    # 504:网关超时(后端响应太慢)
    error_page 404 /errors/404.html;
    error_page 403 /errors/403.html;
    error_page 500 502 503 504 /errors/50x.html;

    # ========== 为错误页定义专用 location ==========
    # 使用 = 进行精确匹配,避免正则匹配开销
    # internal 指令禁止外部直接访问,提升安全性
    location = /errors/404.html {
        internal; # ⚠️ 必须加!
        root /var/www; # 拼接后路径:/var/www/errors/404.html
    }

    location = /errors/403.html {
        internal;
        root /var/www;
    }

    location = /errors/50x.html {
        internal;
        root /var/www;
    }

    # ========== 正常业务 location ==========
    # 这里是你的应用入口,例如 Vue 的 history 模式
    location / {
        # try_files 指令:先找静态文件,再找目录,最后返回 404
        # =404 表示如果前面都失败,就触发 error_page 404
        try_files $uri $uri/ =404;
    }

    # ========== 安全加固:禁止访问敏感文件 ==========
    # 防止用户通过 URL 直接读取 .htaccess、.env 等文件
    location ~ /\. {
        deny all;
    }
}

参数推导与计算过程

  • listen 80 default_server; default_server 表示这是默认虚拟主机,当 Host 头不匹配任何 server_name 时,Nginx 会路由到此。在单站点部署中,这是必须的;
  • error_page 500 502 503 504 /errors/50x.html; :将多个错误码映射到同一个 HTML 文件,减少文件数量,便于维护。你也可以为每个码单独定义,但 5xx 错误通常不需要区分细节;
  • root /var/www; in location:为什么不是 /var/www/errors ?因为 location = /errors/404.html 中的 URI 是 /errors/404.html ,Nginx 会去掉匹配的 /errors/404.html ,然后在 root 目录下查找剩余路径(空字符串),所以 root /var/www + "" = /var/www ,再拼上原始 URI 的 /errors/404.html ,得到 /var/www/errors/404.html 。这是 Nginx 的路径拼接逻辑,务必理解。

4.4 配置验证与热重载:零停机上线

Nginx 配置语法极其严格,一个分号缺失、一个括号不匹配,都会导致 nginx -t 失败。切勿直接 systemctl reload nginx

标准验证流程

# 1. 检查语法(必须!)
sudo nginx -t
# ✅ 成功输出:nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
#            nginx: configuration file /etc/nginx/nginx.conf test is successful

# 2. 如果失败,看错误行号,用 nano 定位修复
# 例如:nginx: [emerg] invalid number of arguments in "error_page" directive in /etc/nginx/sites-available/default:25:25
# 表示第 25 行第 25 列有参数错误,通常是少了个分号或引号

# 3. 语法正确后,重载配置(不中断现有连接)
sudo systemctl reload nginx
# ✅ 输出:Warning: The unit file, source configuration file or drop-ins of nginx.service changed on disk. Run 'systemctl daemon-reload' to reload units.
# 这只是警告,reload 仍成功

# 4. 验证进程是否更新(检查配置文件修改时间)
sudo nginx -V 2>&1 | grep "configure arguments"
# 查看编译参数,确认没动错核心

如何触发 404 测试?

  • 在浏览器访问一个不存在的路径,如 http://你的IP/this-page-does-not-exist
  • 或者用 curl 命令行测试(更干净):
    curl -I http://localhost/404-test
    # 应返回:HTTP/1.1 404 Not Found
    #         Content-Type: text/html; charset=utf-8
    #         Content-Length: 1234
    
    curl http://localhost/404-test
    # 应返回你写的 404.html 的完整 HTML 内容
    

日志排查技巧

  • Nginx 错误日志默认在 /var/log/nginx/error.log
  • 如果页面不显示,先看这里:
    sudo tail -f /var/log/nginx/error.log
    # 然后在浏览器触发 404,观察实时日志
    # 常见错误:
    #   open() "/var/www/errors/404.html" failed (13: Permission denied) → 权限问题
    #   open() "/var/www/errors/404.html" failed (2: No such file or directory) → 文件路径错
    #   no resolver defined to resolve ... → DNS 问题(与 error_page 无关)
    

5. 常见问题与排查技巧实录

5.1 问题速查表:症状、原因与一键修复命令

症状 可能原因 诊断命令 一键修复命令
访问不存在路径,仍显示 Nginx 默认 404 页 error_page 未生效,或 location 未匹配 sudo nginx -T | grep -A5 "error_page"
curl -I http://localhost/nonexist
检查 server 块中是否有 error_page ,确认 location = /errors/... 存在且 internal
浏览器显示“403 Forbidden”而非你的 403.html www-data /var/www/errors/403.html 无读权限 sudo -u www-data cat /var/www/errors/403.html 2>/dev/null | head -1 sudo chown www-data:www-data /var/www/errors/403.html && sudo chmod 644 /var/www/errors/403.html
错误页显示中文为方块或问号 缺少 charset utf-8; 或 HTML 无 <meta charset> curl -I http://localhost/404-test | grep "charset" http server 块开头加 charset utf-8; ,并检查 HTML 文件头
curl http://localhost/404-test 返回空白或 404 location = /errors/404.html root 路径错误 sudo nginx -T | grep -A10 "location = /errors/404.html" 确认 root 值,例如 root /var/www; 对应文件路径 /var/www/errors/404.html
外部可直接访问 http://example.com/errors/404.html 忘了加 internal; 指令 curl -I http://localhost/errors/404.html 编辑配置,在 location = /errors/404.html 块内添加 internal; ,然后 sudo nginx -t && sudo systemctl reload nginx

5.2 我踩过的坑:那些文档不会写的实战教训

坑一: try_files error_page 的优先级战争 我在一个 Vue Router history 模式项目中,配置了:

location / {
    try_files $uri $uri/ /index.html;
}
error_page 404 /errors/404.html;

结果所有 404 都被 try_files 拦截,返回了 /index.html (Vue 的 fallback), error_page 彻底失效。
教训 try_files 是 location 级别的指令,它会在该 location 内部处理所有请求,只有当 try_files 的所有选项都失败(即

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐