HTTP/2 Server Push允许服务器在响应主请求时主动向客户端推送额外资源,理想情况下可以省去客户端解析HTML后再发起CSS、JS请求的等待时间。但Push是一把双刃剑:如果推送的资源并不是客户端需要的,或者客户端缓存中已经存在,就会浪费带宽和连接。因此,只有把Push行为记录到日志并持续观察,才能知道配置是否合理。Nginx从1.13.9版本开始原生支持HTTP/2 Server Push,但默认的访问日志并不会体现推送细节,我们需要手动定制日志格式,并配合简单的分析工具来监控推送效果。

Nginx中启用HTTP/2 Server Push的配置方法
要在Nginx中使用HTTP/2 Server Push,首先需要确保监听套接字启用了http2参数,并且站点使用HTTPS,因为浏览器只对安全连接启用HTTP/2。配置示例如下:
server {
listen 443 ssl http2;
server_name ipipp.com;
ssl_certificate /etc/nginx/ssl/ipipp.com.pem;
ssl_certificate_key /etc/nginx/ssl/ipipp.com.key;
location / {
http2_push /assets/css/main.css;
http2_push /assets/js/app.js;
# 同时发送预加载头,兼容不支持Push的客户端
add_header Link "</assets/css/main.css>; rel=preload; as=style";
add_header Link "</assets/js/app.js>; rel=preload; as=script";
}
}
上面的配置通过http2_push指令显式声明要推送的资源路径,这些路径必须与当前location的映射一致,例如对于根路径下的HTML请求,会同时推送CSS和JS。Nginx会在响应主文档时自动创建PUSH_PROMISE帧,并紧接着发送被推送资源的响应。如果客户端已经通过缓存协商表明不需要某个资源,比如发送了RST_STREAM,Nginx会取消对应的推送流,但这些细节在默认日志中完全看不到。
另一种更灵活的方式是利用http2_push_preload指令,让Nginx自动识别响应头中Link: </path>; rel=preload的内容并触发推送。这种方式的好处是把推送决策交给上游应用(如PHP、Node.js),由应用根据用户状态决定推送哪些资源。配置方法如下:
location / {
http2_push_preload on;
}
此时上游应用只需要在响应头里添加多个Link头即可,Nginx会解析并推送。无论采用哪种方式,推送行为最终都会体现在HTTP/2帧的交互上,而要观察这些交互,下一步就是定制日志。
定制Nginx日志记录Push关键信息
Nginx的默认访问日志格式记录了请求方法、URI、状态码、响应体大小等基本信息,但对于被推送的资源并不会单独产生一条普通日志——因为推送流是由服务器主动发起的,没有对应的客户端请求行。不过,我们可以通过一些间接手段来记录推送相关的上下文。第一个思路是记录响应头中的Link头,因为如果使用http2_push_preload,推送的资源列表就来源于Link头;如果使用显式http2_push指令,我们也可以手动添加同样的Link头用于日志记录。自定义日志格式如下:
log_format push_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'push_uris="$sent_http_link" '
'request_id=$request_id';
access_log /var/log/nginx/push_access.log push_log;
这里的关键变量是$sent_http_link,它表示发送给客户端的响应头中所有Link头的值,多个Link头会用逗号分隔。这样每一条主文档请求的日志行都会携带本次推送(或预加载声明)的资源URI。另外$request_id可以关联Nginx内部请求ID,方便与错误日志对照排查问题。如果使用了http2_push_preload,建议在应用层也统一输出Link头,确保日志信息完整。
第二个思路是记录HTTP/2会话级别的信息,例如使用$http2变量(值为h2表示HTTP/2连接),以及$ssl_protocol、$ssl_cipher等。这可以帮助我们判断哪些请求确实发生在HTTP/2连接上,避免把HTTP/1.1的日志混入统计。你还可以在map块中定义变量来标记是否触发了Push,但Nginx原生并没有直接暴露“本次响应推送了几个资源”这样的变量,所以基于Link头的分析是目前最实用的方法。
需要注意的是,如果响应头中带有多个Link头,Nginx在组合$sent_http_link时可能会因为重复字段而被覆盖,建议在应用层合并为一个Link头,每个资源用逗号分隔,或者使用多个不同rel类型的Link头并测试日志输出效果。另外,日志文件中会包含尖括号和分号等字符,后续解析时需要做相应处理。
用脚本解析Push日志并生成监控指标
有了上述日志格式,就可以通过简单的文本处理来统计推送情况。下面是一个Python脚本示例,它会从日志文件中提取push_uris字段,统计每个资源被推送的次数,并计算包含推送的请求占比。
import re
from collections import Counter
LOG_PATH = '/var/log/nginx/push_access.log'
def parse_push_uris(line):
match = re.search(r'push_uris="([^"]*)"', line)
if not match:
return []
raw = match.group(1)
# 按逗号分割,并去掉可能存在的空格和尖括号
uris = []
for item in raw.split(','):
item = item.strip()
if item.startswith('<') and item.endswith('>'):
item = item[1:-1]
# 去掉 ; rel=preload 等参数
uri = item.split(';')[0].strip()
if uri:
uris.append(uri)
return uris
def main():
push_counter = Counter()
total_requests = 0
push_requests = 0
with open(LOG_PATH) as f:
for line in f:
total_requests += 1
uris = parse_push_uris(line)
if uris:
push_requests += 1
push_counter.update(uris)
print(f'总请求数: {total_requests}')
print(f'触发Push的请求数: {push_requests}')
print(f'Push触发比例: {push_requests / total_requests:.2%}')
print('\n推送资源Top10:')
for uri, count in push_counter.most_common(10):
print(f'{count:5d} {uri}')
if __name__ == '__main__':
main()
这个脚本假设日志中每行最多只有一个push_uris字段,且该字段只出现在主文档请求的日志行中。实际环境中可能存在多个Link头,或者某些响应没有Link头,脚本中已经做了容错处理。运行脚本后可以得到推送触发比例和资源分布,如果某个资源推送次数很多但很少被后续请求引用(比如通过对比独立资源请求日志),就说明存在过度推送。
为了更准确地评估推送效果,还需要结合客户端的实际使用情况。一种简单的方法是在被推送的资源上增加查询参数或特殊标记,然后统计这些资源后来是否被作为独立请求再次请求,如果大量推送后没有出现对应资源的GET请求,说明客户端可能已经有缓存,或者资源推送并不必要。更进阶的做法是使用像ngx_http_slice_module这样的模块配合自定义响应头,或者通过浏览器端Performance API上报,但这就超出了纯日志分析的范围。
如果你需要实时监控,可以将Nginx日志输出到syslog,然后用Logstash或Fluentd等工具收集,并配置告警规则,比如当Push触发比例突然下降或者某个资源的推送错误率升高时发送通知。对于小规模站点,每天定时跑一次上面的Python脚本并发送邮件报告也能满足需求。
避免Push滥用与优化策略
HTTP/2 Server Push最大的风险就是推送了客户端已经缓存的资源。因为服务器在发送PUSH_PROMISE时,并不知道客户端本地缓存的状态(除非使用Cache Digest等尚未广泛部署的扩展)。为了减少浪费,可以采用以下策略:
- 按需推送:只推送首屏渲染必需的CSS和关键JS,不要把所有静态资源都推出去。
- 利用Cookie区分用户:在应用层根据Cookie判断用户是否曾经访问过,对于回头客不推送或只推送少量更新资源。
- 条件推送:通过
http2_push_preload配合上游应用动态生成Link头,只在没有缓存标记时输出。 - 监控缓存命中:结合日志中推送后的资源请求情况,定期调整推送列表。
Nginx自身也提供了一些控制手段。例如使用if块配合add_header来有条件地添加Link头,但需要注意if在Nginx配置中的使用限制。更推荐在应用层做决策,因为应用掌握更多用户状态和业务逻辑。此外,HTTP/2规范允许客户端通过SETTINGS_ENABLE_PUSH禁用Push,Nginx会尊重这个设置,并在客户端发出RST_STREAM时终止推送,因此不必担心强推。
最后,监控的目的不只是统计数字,而是通过数据反馈调整推送策略。建议每隔一段时间重新评估推送资源列表,删除长时间没被使用的内容,并关注移动网络下的推送开销。一个合理的Push监控体系能够让你的HTTP/2优化不再盲目,真正发挥协议优势。
NginxHTTP/2 Server Push日志监控修改时间:2026-10-04 13:58:10