首页  /  Nginx 进阶  /  正文

浏览器缓存与 Nginx 缓存头配置

Nginx 进阶 2026-10-02📖 16 分钟👁 —
🚀 Nginx 水平扩展与优化第 7 / 8 页12345678📖 完整导航 →

浏览器缓存

多级缓存简介

用户请求服务器资源,不需要每次都向服务器请求,通过浏览器本地存储响应给用户,通过 nginx 可以控制浏览器是否要做缓存(一些主流的站点都会使用,直接发缓存给用户甚至不做服务端校验)。

CDN 缓存:用户和 CDN 越近越快。

Nginx 缓存:基于代理的缓存,把相同的请求从上游服务器拉去缓存,从而减少后端的并发量(nginx 本身对静态资源的性能是非常高的),比如对 url 请求的 html 缓存。当然对于时效性的数据会有方法更新的(数据分层:热点内容缓存到内存,一般数据缓存到硬盘)。

上游服务器缓存:不在 nginx 讲解范围之内,是成体系、庞大的业务结构。

用户浏览器缓存 → CDN 缓存 → Nginx 缓存 → 上游服务器缓存

京东 Web 端浏览器缓存的使用情况

京东资源列表的 Size 列,有的标着 memory cache,有的标着 disk cache

其中分别显示了内存缓存和磁盘缓存。

浏览器内存管理做的都不是很好,因为上面的缓存很难清理的,想要释放内存就得关闭页面,清除内存也不及时,所以打开多标签页浏览器会占用很多内存。

其中可以看到内存中的基本是 0ms,磁盘的缓存是 1ms,有的是 100 多 ms。

同一批请求的 Time 列(红框框出),最下面那条 document 请求用了 202 ms

js 类的延迟的很小,图片延迟高。

可能说的有点乱,下面具体配置会清晰很多。

浏览器的强制缓存和协商缓存

几乎所有的网站都会用浏览器缓存。

强制缓存:用户访问站点,站点根据日期把数据缓存到浏览器(不过期就能继续使用)。

协商缓存:查一下是否发生变化,不单纯认为本机的时间是正确的。或以文件的修改日期为标准。

上述只做简单了解,会很模糊,待会直接看具体配置即可。

协商缓存使用场景:(下图)客户端访问站点的 index.html,同时告知 nginx 的最后修改时间 last_modify:2022 5.28:12:12,浏览器拿到文件,下次访问还是会请求 index.html,但是会带上修改日期(可以写入请求头)。nginx 收到去找上游服务器的磁盘文件的修改日期(只看修改日期,不读数据),如果已经是最新的了,nginx 就返回一个状态码 304,浏览器直接从本地磁盘发给用户。

协商缓存一问一答的流程,请求带上文件修改日期,Nginx 回 304

强制缓存使用场景:请求到 nginx,nginx 返回文件并且加入过期时间,假设 3 天,浏览器拉到本地,如果 3 天之内就直接返回缓存给用户。

两种缓存都可以配合使用,目的是减少用户的重复请求。

协商缓存优点:有效避免强制缓存的过期、缓存一直不失效等问题。

ssi、缓存配置可能会和 gzip 冲突。

状态码是 200 OK,响应头里的 Etag 和 Last-Modified 被框出
普通刷新后状态码变成 304 Not Modified,响应头里的 Etag 和 Last-Modified 被框出

在静态资源页强制刷新(C+T+R),状态码是 200 代表没有使用缓存。

使用普通刷新看到了状态码 304,说明使用了缓存。

如上图 Etag 和 Last-Modified 是 Nginx 处理静态文件时自动添加 的响应头。

这两个是协商缓存的(分别是缓存文件的精确哈希和缓存的最后修改日期)。

实际上只需要 nginx 的默认的协商缓存就可以应对大部分的情况了,但还是有一些特殊的需求需要强制缓存。

Nginx Etag、Last-Modified 具体配置(协商缓存配置)

如果想要关闭 etag,比如关闭对应站点的 etag,可以直接在 location 的配置添加 etag off。

# /etc/nginx/sites-available/test
server {
    listen 80;
    server_name test.168kaguyachi.site;

    root /var/www/test;
    index index.html;

    location / {
        etag off; # 此处是新加入的配置
        rewrite ^/([0-9]+).html$        /index.html?page=$1     break;
        try_files $uri $uri/ =404;
    }

    location ~*^/(js|css|images)/ {
    }

}

重载入 nginx 看控制台,该站点已经失去了 etag 和 lastmodify 标签。

关掉 etag 之后响应头里只剩 Last-Modified,没有 Etag 了

是否使用缓存是浏览器决定的,nginx 只能给建议,所以返回了 304:

请求头里带着 Cache-Control: max-age=0 和 If-Modified-Since,响应头里的 Last-Modified 也还在

文件修改日期还在,304 是 nginx 主动返回的(上面说过没有携带文件只是一个状态码)。

想要浏览器不用缓存:直接取消 last-modified 的发送即可,也可以让 nginx 不返回 304 的状态码。

清除 header 的方法:由于 nginx 是自动发送的 lastmodify,没有关闭的选项,但是可以清除这个请求头字段 Last-Modified。

方法1:自身不返回。在对应 location 或者 server 配置 add_header Last-Modified "";

# /etc/nginx/sites-available/test
server {
    listen 80;
    server_name test.168kaguyachi.site;

    root /var/www/test;
    index index.html;

    location / {
        etag off;
        add_header Last-Modified "";
        rewrite ^/([0-9]+).html$        /index.html?page=$1     break;
        try_files $uri $uri/ =404;
    }

    location ~*^/(js|css|images)/ {
    }

}

重载完需要等待一会,然后测试就有结果了。

状态码回到 200 OK,响应头里的 Etag 和 Last-Modified 都没了
请求头里也不再带 If-Modified-Since 了

可以看到状态码一直是 200,请求和响应都没有文件修改日期了(因为浏览器无法判断文件是否修改,无法返回缓存文件)。

方法2:不让浏览器判断。写 if_modified_since off;

# /etc/nginx/sites-available/test
server {
    listen 80;
    server_name test.168kaguyachi.site;

    root /var/www/test;
    index index.html;

    location / {
        etag off;
        if_modified_since off;
        #add_header Last-Modified "";
        rewrite ^/([0-9]+).html$        /index.html?page=$1     break;
        try_files $uri $uri/ =404;
    }

    location ~*^/(js|css|images)/ {
    }

}

插入:在 vim 写中文空格不行,在外部文本编辑器可以,因为全角空格会自动转换为半角空格,如果找不到中文空格的位置了,可以使用 cat -A [文件] 查看文件的所有不可见字符,换行是 $,Tab 是 ^I,全角空格或特殊空白字符为 M-BM- 之类的乱码。

语法不高亮很可能是写错了。

配置完测试:

请求头里还是带着 If-Modified-Since,响应头里的 Last-Modified 也还在

可以看到浏览器还是携带 If-Modified-Since 请求头,但是响应码还是 200,因为 nginx 忽略了这个标头。

etag、lastmodify、cache-control、expires 具体配置(强制缓存配置)

强制缓存向服务器发送请求主要是 cache-control 和 expires。

使用强制缓存需要关掉 etag 和浏览器的 if modified since 吗?不需要的,可以都开启,二者可以共存的。但是为了单独测试强制缓存需要关掉。

expires

直接配置 expires [时间] 即可比如 expires 300s;

直接配置在 server 或者 location 即可。

需要补充的是,expires 是比较老的 header,HTTP/1.1 更多的是 cache-control,不同的浏览器识别/处理 header 的时候是不一样的,chrome 首次访问(到新标签访问)会识别到 expires。

为什么提到上面的补充呢?因为在 chrome 测试 expires 配置时,控制台一直没有显示使用到了缓存,开新标签才会使用缓存,刷新之后就一直不用缓存了。

总之,是否使用缓存都是浏览器决定的,nginx 只能“提建议”。

这里确实说的有点麻烦了,expires 算是一个老东西了,但是为什么留到现在还在用呢,因为他有很多其他的功能,比如:

expires 1h;

等价于

add_header Cache-Control "max-age=3600";

add_header Expires "Thu, 16 Jul 2026 10:00:00 GMT";

当然这是深层次的理解了,现在涉及不到的。

cache-control

作为 header 标头配置对应的字段 add_header cache-control "";

其中常见的只有三个:

cache-control 各个标记的类型和功能,框出的是 no-cache、no-store、max-age

no-cache 是不直接使用。

no-store 是不缓存。

max-age 和 expires 很像,代表浏览器缓存文件的最大缓存时间。

# /etc/nginx/sites-available/test
server {
    listen 80;
    server_name test.168kaguyachi.site;

    root /var/www/test;
    index index.html;

    location / {
        # 协商缓存配置
        #etag off;
        #if_modified_since off;
        #add_header Last-Modified "";
        rewrite ^/([0-9]+).html$        /index.html?page=$1     break;
        try_files $uri $uri/ =404;

        # 强制缓存配置
        expires 300s;
        add_header cache-control "max-age: 300";
        # 这里加不加空格都可以
        # 习惯了HTTP数据包都加,所以这里加了空格(vim写英文空格哦)
    }

    location ~*^/(js|css|images)/ {
    }

}

刷新可以看到有 Cache-Control 字段了。

刷新后常规面板里的状态码是 304 Not Modified
响应标头里同时出现 Cache-Control: max-age=300 和 Cache-Control: max-age: 300,被红框框出

浏览器默认会请求 cache-control:

请求标头里浏览器自己带的 Cache-Control: max-age=0,另外还有 If-Modified-Since 和 If-None-Match

返回了两个,可能是上游又给加了一个。

这个情况是关闭了协商缓存只开了强制缓存。

假设用户只是想刷新页面,但是浏览器还是拉取缓存数据,这是不合理的,所以强制缓存的主要场景用在不必要刷新的页面,那种实时反馈的就不行。

cache-control 优先级比 expires 高。

缓存配置一般都是在 location,达到精确配置的效果。

总结:两个缓存方式是用来组合使用的。用户首次打开链接,进入强制缓存逻辑,浏览器直接从缓存拉数据。配合 lastmodify 和 etag,用户强制刷新时,进入 etag 的逻辑,文件不变化时,nginx 返回 304,浏览器拉缓存(首次打开和页面刷新都使用了缓存~)。

两种缓存的分工总结:cache-control 和 expires 负责强制缓存(首次打开直接读缓存,刷新会向服务器发起请求),etag 和 lastmodify 负责协商缓存(没变化就返回 304,不发数据)

至此客户端的缓存算是比较完整了。

浏览器缓存额外需要注意的事项和应用场景

这里的东西很难理解,因为涉及到了具体的业务场景,所以可以混个耳熟,有助于理解业务流程和业务逻辑。

如图:

浏览器缓存实践里的各种注意事项清单

要保持多台机器的文件修改日期相同。

想要完全禁用浏览器(客户端)缓存是不可能的,但可以让 url 随着请求变化加上特殊信息:版本号、特定哈希值等:/v1.01/account/adduser?tx=hash 请求不一样自然不适用缓存了。

3XX 的跳转地址不需要缓存。

js/css 可以通过追加版本号更新内容(可以观察到大网站的处理方式)。

可以针对并发大的接口进行缓存(一般会配置时效)。

Etag 是资源生成之后才能加的。

客户端不一定都是浏览器,APP 作为客户端的情况也很多,这个就要和前端对接了(开发)。

不联网也可以展示一些数据。

浏览器缓存可以减少流量消耗。

提前下发数据可以减少流量洪峰压力服务器的可能。

兜底数据可以在服务器压力大的时候用(比如只展示一个框架,点不进去内容,而非直接来一个大报错)。

临时缓存,退出就删。

固定缓存,长时间不更新,和兜底数据很像。