如何基于MIME类型为Nginx代理的内容设置Expires响应头?
嘿,我来帮你捋捋这个问题——你遇到的核心问题其实是Nginx在处理代理内容时,获取上游服务返回的MIME类型的正确姿势,以及配置细节上的小坑。
首先得说清楚:你最开始用$sent_http_content_type是行不通的,因为这个变量代表的是Nginx准备发送给客户端的Content-Type头,而在代理场景下,当Nginx执行expires指令的时候,这个值还没被完全确定下来,所以用它来匹配肯定得不到想要的结果。
你后来换成$upstream_http_content_type的思路是完全对的,这个变量才是上游服务(也就是你的Django应用)返回的Content-Type头的值。至于为什么还是没生效,大概率是下面几个原因之一,咱们一个个解决:
1. 先确认上游是否真的返回了Content-Type头
先用curl测一下你的代理接口,看看上游有没有返回这个关键头:
curl -I https://example.com/你的代理路径
如果输出里没有Content-Type这一行,那$upstream_http_content_type就是空的,map会匹配default规则(也就是off),自然不会添加Expires头。这种情况你得先去Django那边配置,确保接口返回正确的Content-Type。
2. 调整Map的匹配规则,兼容大小写和额外参数
很多时候上游返回的Content-Type可能带有大小写差异(比如Text/HTML)或者附加参数(比如text/html; charset=utf-8),你原来的map匹配是严格的字符串匹配,很容易漏匹配。建议改成忽略大小写的正则匹配:
map $upstream_http_content_type $expires2 { default off; # 用~*开启忽略大小写的正则匹配 ~*text/html 2d; ~*text/plain 20d; ~*text/css 180d; ~*application/font-woff2 1y; ~*application/pdf 1y; ~*image/ 1y; }
这样不管上游返回的是text/html还是Text/HTML; charset=utf-8,都能正确匹配到对应的过期时间。
3. 给expires指令加上always参数
如果上游服务自己返回了Cache-Control头,Nginx默认不会用expires指令的结果去覆盖它。这时候你需要加上always参数,强制让Nginx设置Expires头:
location / { uwsgi_pass django; # 加上always,确保不管上游有没有返回缓存头,都按我们的规则设置Expires expires $expires2 always; }
完整的配置示例
把这些调整整合起来,最终的配置大概是这样:
# 针对上游返回的Content-Type设置过期规则 map $upstream_http_content_type $expires2 { default off; ~*text/html 2d; ~*text/plain 20d; ~*text/css 180d; ~*application/font-woff2 1y; ~*application/pdf 1y; ~*image/ 1y; } server { listen 443 ssl http2; server_name example.com; location / { uwsgi_pass django; expires $expires2 always; } # 静态文件的配置保留你原来的即可 location ~ ^/(css|fonts|images|pdf|html)/ { root /var/www/django/static/; expires $expires; } }
最后别忘了测试和重载
修改完配置后,先检查配置是否合法:
nginx -t
没问题的话就重载Nginx:
nginx -s reload
测试的时候记得清空浏览器缓存,或者用curl加上-H "Cache-Control: no-cache"参数,避免旧缓存干扰结果。
备注:内容来源于stack exchange,提问作者janeden

