Nginx的变量体系是其配置灵活性的核心来源之一。除了常见的$remote_addr、$request_uri这类请求侧变量外,Nginx还提供了一组专门用于读取响应头信息的内置变量,即$sent_http_*系列。这类变量的作用是:当Nginx向客户端发送HTTP响应时,自动把已经发送出去的响应头内容映射成变量,供日志输出、条件判断和调试使用。很多初学者容易把它和$upstream_http_*变量混淆,导致在反向代理场景下取不到预期的值,本文将围绕这些内容展开详细说明。

$sent_http_*变量的基本原理和命名规则
$sent_http_*并不是一个单独的变量,而是一个变量家族。星号部分需要替换成实际的响应头名称,Nginx会按照固定规则进行匹配。它的语法形式是$sent_http_加上响应头名称,其中响应头名称中的连字符(-)会被转换为下划线(_),并且整个名称不区分大小写。例如响应头Content-Type对应变量$sent_http_content_type,Cache-Control对应$sent_http_cache_control,X-Request-Id对应$sent_http_x_request_id。
需要特别理解的一点是,这个变量的取值时机。$sent_http_*反映的是Nginx已经发送出去的响应头,也就是说只有在响应头真正发送给客户端之后,变量才有值。如果在header过滤器阶段之前就去读取它,很可能会得到空值。这也是为什么在某些早期执行阶段(比如server块或location匹配阶段)使用它做判断时经常取不到值的原因。
下面通过一个简单的日志配置来观察这些变量的输出:
# 在http或server块中定义日志格式
log_format resp_debug '$remote_addr - [$time_local] "$request" '
'status=$status type=$sent_http_content_type '
'cache=$sent_http_cache_control len=$sent_http_content_length';
server {
listen 80;
server_name test.ipipp.com;
access_log /var/log/nginx/resp_debug.log resp_debug;
location / {
root /data/www;
}
}请求一个静态文件后,日志中就能看到类似type=text/html、len=1024这样的记录。对于排查“客户端实际收到了什么响应头”这类问题,这种方式比抓包更轻量,特别适合在生产环境中做抽样观测。
$sent_http_*与$upstream_http_*的区别及典型误区
这是实际使用中最容易踩坑的地方。$upstream_http_*读取的是上游服务器(被代理的后端)返回给Nginx的响应头,而$sent_http_*读取的是Nginx最终发送给客户端的响应头。两者在大多数情况下值相同,但存在明显差异的场景并不少见。
第一种典型场景是Nginx对响应头做了修改。比如在Nginx层通过add_header追加了额外的头信息,或者通过proxy_hide_header隐藏了某些上游头,此时$sent_http_*看到的才是客户端真正接收到的内容。第二种场景是错误页面的处理:当后端不可用、Nginx返回自带的502或504错误页时,$upstream_http_*可能为空,而$sent_http_*依然可以正常记录Nginx自己生成的响应头。
可以通过下面的配置对比观察两者的差异:
log_format compare '$uri upstream_type=$upstream_http_content_type '
'sent_type=$sent_http_content_type';
server {
listen 80;
location /api/ {
proxy_pass http://127.0.0.1:8080;
# Nginx隐藏上游返回的Server头
proxy_hide_header Server;
# 追加一个自定义响应头
add_header X-Gateway nginx;
access_log /var/log/nginx/compare.log compare;
}
}在这个例子中,如果后端返回了Server头,$upstream_http_server能读到值,但由于被proxy_hide_header隐藏,$sent_http_server将为空;反过来,X-Gateway是Nginx自己加的,只有$sent_http_x_gateway有值,$upstream_http_x_gateway则为空。理解这个差异后,排查“响应头丢失”问题时就能快速定位到底是后端没返回,还是Nginx层把它过滤掉了。
实战应用:条件判断、调试与注意事项
除了日志记录,$sent_http_*还可以配合if指令做一些条件逻辑。例如根据实际发送的Content-Type决定是否启用gzip相关的处理,或者在返回特定头时触发额外动作。下面是一个利用map做判断的示例:
# 根据是否携带特定响应头映射一个标记值
map $sent_http_x_request_id $has_trace {
"" 0;
default 1;
}
server {
listen 80;
# 在响应结束后记录是否带了追踪头
log_format trace '$request_uri traced=$has_trace';
access_log /var/log/nginx/trace.log trace;
location / {
proxy_pass http://127.0.0.1:8080;
}
}使用时还有几个注意点值得强调。首先是取值为空的问题:如果变量在读取时响应头尚未发送,或者该响应头根本不存在,变量都会返回空字符串,并不会报错,因此不能依赖报错来判断配置是否有误。其次是命名转换规则,Header中的连字符必须写成下划线,写成$sent_http_content-type是不合法的,Nginx启动时会直接报配置错误。
另外要注意add_header指令的继承特性:add_header只在当前级别没有定义任何add_header时才会继承上级配置,父子级别同时存在时上级的配置会被覆盖,这经常导致“明明配置了头却读不到”的假象,实际是继承规则问题而非变量失效。最后,对于开启了缓冲的代理场景,响应头的发送时机可能晚于预期,如果在大流量环境下用$sent_http_*做实时判断,建议先在测试环境充分验证时机是否符合预期,避免出现逻辑与预期不符的情况。
总结来说,$sent_http_*是观察Nginx真实输出的一扇窗口,与$upstream_http_*配合使用,可以完整还原从后端到客户端的响应头流转链路。掌握它们之后,无论是排查缓存策略不生效、安全头丢失,还是做链路追踪埋点,都会事半功倍。
Nginx变量sent_http响应头内置变量修改时间:2026-09-08 21:01:13