Chapter 04

静态资源服务

发静态文件是 Nginx 的看家本领。掌握 index、autoindex、MIME、gzip 压缩、缓存头与 try_files,就能把一个前端站点服务得又快又稳。

4.1 核心概念

index
当请求指向一个目录时,Nginx 依次尝试的默认文件名,如 index index.html index.htm;。找到就返回它。
autoindex
目录下没有 index 文件时,是否自动生成一个文件列表页面。autoindex on; 打开,常用于下载站;生产站点通常关闭以免暴露文件结构。
MIME 类型(MIME Type)
告诉浏览器文件是什么类型的标识,如 text/htmlapplication/json。Nginx 靠 mime.types 按扩展名映射,写进响应的 Content-Type。
gzip 压缩
发送前用 gzip 算法压缩文本类响应(HTML/CSS/JS/JSON),显著减小传输体积。浏览器通过 Accept-Encoding 声明支持,Nginx 据此决定是否压缩。
expires / Cache-Control
缓存头。告诉浏览器"这个资源多久内可以直接用本地副本,不必再问服务器"。对带哈希指纹的静态资源可设很长。
try_files
按顺序尝试多个路径,用第一个存在的;全都不存在则走最后的兜底(内部跳转或返回状态码)。SPA 路由回退的关键指令。

4.2 最简静态站点

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

    # 目录请求时依次找这些文件
    index  index.html index.htm;

    location / {
        # 找不到文件返回 404,而不是目录列表
        try_files  $uri $uri/ =404;
    }
}

4.3 autoindex:目录列表(下载站)

location /files/ {
    alias  /data/public/;
    autoindex  on;                # 生成文件列表
    autoindex_exact_size  off;    # 显示人类可读大小(KB/MB)
    autoindex_localtime  on;      # 用本地时间显示修改时间
}
autoindex 是把双刃剑

它方便做简易文件服务器,但也会暴露目录下所有文件名。业务站点务必保持默认关闭;确需开启,只对特定下载目录开,并搭配访问控制(第 9 章)。

4.4 gzip 压缩

http {
    gzip  on;
    gzip_comp_level  5;           # 1-9,越高压得越狠也越费 CPU,5 是平衡点
    gzip_min_length  1024;        # 小于 1KB 不压缩(没意义)
    gzip_vary  on;                # 加 Vary: Accept-Encoding,利于缓存正确
    gzip_proxied  any;            # 代理请求也压缩
    gzip_types
        text/plain text/css application/json
        application/javascript text/xml
        application/xml image/svg+xml;
}
别压已经压过的东西

图片(JPG/PNG/WebP)、视频、字体 woff2 本身已是压缩格式,再 gzip 几乎不会更小,反而白费 CPU。所以 gzip_types 只列文本类。更进一步可用 ngx_brotli 模块,压缩率比 gzip 更高。

4.5 缓存头 expires

# 带哈希指纹的资源(app.3f2a.js)可长期缓存
location ~* \.(js|css|woff2|png|jpg|svg)$ {
    expires  1y;
    add_header  Cache-Control  "public, immutable";
}

# HTML 入口文件不能长缓存,否则更新不生效
location = /index.html {
    expires  -1;                  # 每次都校验
    add_header  Cache-Control  "no-cache";
}

策略要点:文件名带哈希的资源(内容变则文件名变)可以放心 expires 1y 甚至 immutable;而 index.html 这类入口文件绝不能长缓存,否则用户拿到旧 HTML、引用不到新资源。

4.6 try_files 与 SPA 路由回退

单页应用(React/Vue)的前端路由(如 /user/123)在服务器上并没有对应文件。刷新页面时若直接找文件会 404。try_files 解决这个问题:

location / {
    # 依次尝试:文件 → 目录 → 回退到 index.html 交给前端路由
    try_files  $uri $uri/ /index.html;
}

请求 /user/123 时:$uri(无此文件)→ $uri/(无此目录)→ 回退返回 /index.html,前端 JS 接管路由。这是部署 SPA 最关键的一行。

4.7 自定义错误页

server {
    # 把 404 指向自定义页面
    error_page  404  /404.html;
    location = /404.html {
        root  /var/www/errors;
        internal;                 # 只能内部跳转到达,不能被外部直接请求
    }

    # 5xx 统一到一个维护页
    error_page  500 502 503 504  /50x.html;
    location = /50x.html {
        root  /var/www/errors;
        internal;
    }
}

4.8 sendfile 与静态性能

http {
    sendfile  on;         # 零拷贝:内核直接把文件送到 socket,不经用户态
    tcp_nopush  on;       # 攒满一个包再发,配合 sendfile 提升吞吐
    tcp_nodelay  on;      # 长连接下小包立即发,降低延迟
    open_file_cache  max=10000 inactive=60s;  # 缓存文件描述符/元信息
}
sendfile 为什么快

传统发文件要经历"磁盘 → 内核缓冲 → 用户态缓冲 → 内核 socket 缓冲 → 网卡"多次拷贝。sendfile 让数据在内核态直接从文件描述符流向 socket,省去用户态来回拷贝,这就是 Nginx 发静态文件极快的底层原因。

4.9 小结与下一步