关键是在 header_filter_by_lua 阶段修改响应头、在 rewrite_by_lua 或 access_by_lua 阶段预处理请求头后配合 proxy_set_header 透传;前者专用于动态控制 ngx.header.,后者需通过变量中转实现请求头注入,二者阶段不可互换且行为严格分离。

在 Nginx 中用 Lua 动态注入自定义 Header,关键不是“什么时候加”,而是“在哪一阶段加”——不同阶段能读取/修改的头类型不同,且行为不可互换。最常用、最稳妥的方式是:用 header_filter_by_lua* 修改响应头,用 rewrite_by_lua* 或 access_by_lua* 预处理请求头再透传给后端。
改响应头:用 header_filter_by_lua*(推荐)
这个阶段发生在后端返回响应之后、Nginx 发送给客户端之前,专门用于读取和修改响应头(ngx.header.*),适合做脱敏、动态缓存策略、环境标识注入等。
- 支持直接赋值:
ngx.header["X-Env"] = "prod",或删除:ngx.header["Server"] = nil - 可结合变量判断,比如按请求参数控制缓存:
if ngx.var.arg_v == "1" then ngx.header["Cache-Control"] = "public, max-age=31536000" end - 注意:它不修改请求头,也不影响 proxy_pass 流程;若需向后端传参,得往前置阶段走
改请求头:用 rewrite_by_lua* 或 access_by_lua*
这两个阶段都在 proxy_pass 之前执行,可用于构造并注入自定义请求头,再由 proxy_set_header 透传——但 Lua 本身不能直接“设置请求头供 proxy_pass 使用”,必须配合 Nginx 原生命令。
- 典型做法是:在
rewrite_by_lua*中解析参数/cookie/Origin,生成变量(如ngx.var.my_tenant_id),再在 location 中写:proxy_set_header X-Tenant-ID $my_tenant_id; - 或者用
access_by_lua*做权限校验+头准备,失败时可直接ngx.exit(403),避免无效转发 - 不建议在
content_by_lua中尝试 set header 后再 proxy_pass,该阶段已跳过 upstream 流程,无法影响后端请求
安全与兼容性要点
动态注入不是写完就生效,几个容易踩坑的细节必须对齐:
- 确保
underscores_in_headers on;已启用(如果用了下划线命名如X_User_ID),否则会被静默丢弃 - 敏感字段(如用户 ID、手机号)务必脱敏后再注入,例如用
ngx.md5("uid_123")替代明文 - HTTPS 场景下,Lua 只能操作 Nginx 终止 TLS 后的明文头;若 CDN 做了 SSL 卸载,要确认它没过滤或覆盖你的自定义头
- 所有动态头都应被后端主动读取并校验——Nginx 不负责信任,只负责传递
别混淆 add_header 和 Lua 注入
add_header 是静态指令,不支持条件逻辑;header_filter_by_lua* 才是真正的动态响应头控制手段。两者可共存,但不要试图在 Lua 里调用 add_header(语法错误)或指望 add_header X-Foo $some_lua_var 自动展开(Nginx 不支持变量展开)。


















