HTTP/2: add proxy_set_authority and grpc_set_authority directives

Previously, setting proxy_set_header Host caused :authority to be
omitted in HTTP/2 requests. This caused many upstream servers to reject
requests that only contained a Host header without :authority.

Now users can explicitly set the :authority pseudo-header field.

Closes: https://github.com/nginx/nginx/issues/1462
This commit is contained in:
Li Zining 2026-06-27 04:12:49 +08:00
parent 1c7687c98d
commit a86cc836d6
4 changed files with 163 additions and 83 deletions

View file

@ -25,7 +25,6 @@ typedef struct {
ngx_array_t *headers_source;
ngx_str_t host;
ngx_uint_t host_set;
ngx_http_complex_value_t *authority;
ngx_array_t *grpc_lengths;
@ -204,6 +203,8 @@ static ngx_int_t ngx_http_grpc_init_headers(ngx_conf_t *cf,
static char *ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_http_grpc_set_header(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
#if (NGX_HTTP_SSL)
static char *ngx_http_grpc_ssl_certificate_cache(ngx_conf_t *cf,
@ -335,7 +336,7 @@ static ngx_command_t ngx_http_grpc_commands[] = {
{ ngx_string("grpc_set_header"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
ngx_conf_set_keyval_slot,
ngx_http_grpc_set_header,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_grpc_loc_conf_t, headers_source),
NULL },
@ -798,15 +799,16 @@ ngx_http_grpc_create_request(ngx_http_request_t *r)
}
/* :authority header */
host = &ctx->host;
if (glcf->authority != NULL) {
if (ngx_http_complex_value(r, glcf->authority, &host_val) != NGX_OK) {
return NGX_ERROR;
}
host = &host_val;
} else {
host = &ctx->host;
if (host_val.len) {
host = &host_val;
}
}
if (host->len > NGX_HTTP_V2_MAX_FIELD) {
@ -4443,7 +4445,6 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf)
* conf->headers.values = NULL;
* conf->headers.hash = { NULL, 0 };
* conf->host = { 0, NULL };
* conf->host_set = 0;
* conf->ssl = 0;
* conf->ssl_protocols = 0;
* conf->ssl_ciphers = { 0, NULL };
@ -4646,7 +4647,6 @@ ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
if (conf->headers_source == prev->headers_source) {
conf->headers = prev->headers;
conf->host_set = prev->host_set;
}
rc = ngx_http_grpc_init_headers(cf, conf, &conf->headers,
@ -4664,8 +4664,6 @@ ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
&& conf->headers_source == prev->headers_source)
{
prev->headers = conf->headers;
prev->host_set = conf->host_set;
prev->authority = conf->authority;
}
return NGX_CONF_OK;
@ -4718,37 +4716,6 @@ ngx_http_grpc_init_headers(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf,
src = conf->headers_source->elts;
for (i = 0; i < conf->headers_source->nelts; i++) {
if (src[i].key.len == 4
&& ngx_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0)
{
conf->host_set = 1;
if (conf->authority != NULL) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"grpc_set_header Host\" cannot be "
"used with \"grpc_set_authority\"");
return NGX_ERROR;
} else {
ngx_http_compile_complex_value_t ccv;
conf->authority = ngx_palloc(cf->pool,
sizeof(ngx_http_complex_value_t));
if (conf->authority == NULL) {
return NGX_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &src[i].value;
ccv.complex_value = conf->authority;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_ERROR;
}
}
}
s = ngx_array_push(&headers_merged);
if (s == NULL) {
return NGX_ERROR;
@ -4850,6 +4817,28 @@ ngx_http_grpc_init_headers(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf,
*code = (uintptr_t) NULL;
}
if (conf->authority == NULL) {
ngx_str_t v;
ngx_http_compile_complex_value_t ccv;
ngx_str_set(&v, "$proxy_internal_host");
conf->authority = ngx_palloc(cf->pool,
sizeof(ngx_http_complex_value_t));
if (conf->authority == NULL) {
return NGX_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &v;
ccv.complex_value = conf->authority;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_ERROR;
}
}
code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t));
if (code == NULL) {
return NGX_ERROR;
@ -4971,6 +4960,48 @@ ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
}
static char *
ngx_http_grpc_set_header(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_grpc_loc_conf_t *glcf = conf;
ngx_str_t *value = cf->args->elts;
if (value[1].len == 4
&& ngx_strncasecmp(value[1].data, (u_char *) "Host", 4) == 0)
{
ngx_http_compile_complex_value_t ccv;
if (glcf->authority != NGX_CONF_UNSET_PTR
&& glcf->authority != NULL)
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"grpc_set_header Host\" cannot be "
"used with \"grpc_set_authority\"");
return NGX_CONF_ERROR;
}
glcf->authority = ngx_palloc(cf->pool,
sizeof(ngx_http_complex_value_t));
if (glcf->authority == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &value[2];
ccv.complex_value = glcf->authority;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
return ngx_conf_set_keyval_slot(cf, cmd, conf);
}
#if (NGX_HTTP_SSL)
static char *

View file

@ -113,6 +113,8 @@ static char *ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_http_proxy_set_header(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_http_proxy_cookie_domain(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_http_proxy_cookie_path(ngx_conf_t *cf, ngx_command_t *cmd,
@ -321,7 +323,7 @@ static ngx_command_t ngx_http_proxy_commands[] = {
{ ngx_string("proxy_set_header"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
ngx_conf_set_keyval_slot,
ngx_http_proxy_set_header,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_proxy_loc_conf_t, headers_source),
NULL },
@ -752,7 +754,7 @@ static char ngx_http_proxy_version_11[] = " HTTP/1.1" CRLF;
static ngx_keyval_t ngx_http_proxy_headers[] = {
{ ngx_string("Host"), ngx_string("$proxy_internal_host") },
{ ngx_string("Host"), ngx_string("") },
{ ngx_string("Connection"), ngx_string("") },
{ ngx_string("Proxy-Connection"), ngx_string("") },
{ ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") },
@ -781,7 +783,7 @@ static ngx_str_t ngx_http_proxy_hide_headers[] = {
#if (NGX_HTTP_CACHE)
static ngx_keyval_t ngx_http_proxy_cache_headers[] = {
{ ngx_string("Host"), ngx_string("$proxy_internal_host") },
{ ngx_string("Host"), ngx_string("") },
{ ngx_string("Connection"), ngx_string("") },
{ ngx_string("Proxy-Connection"), ngx_string("") },
{ ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") },
@ -1186,7 +1188,7 @@ ngx_http_proxy_create_request(ngx_http_request_t *r)
key_len, val_len;
uintptr_t escape;
ngx_buf_t *b;
ngx_str_t method;
ngx_str_t method, host_val;
ngx_uint_t i, unparsed_uri;
ngx_chain_t *cl, *body;
ngx_list_part_t *part;
@ -1313,6 +1315,15 @@ ngx_http_proxy_create_request(ngx_http_request_t *r)
len += key_len + sizeof(": ") - 1 + val_len + sizeof(CRLF) - 1;
}
/* Host header */
if (ngx_http_complex_value(r, plcf->authority, &host_val) != NGX_OK) {
return NGX_ERROR;
}
if (host_val.len) {
len += sizeof("Host: ") - 1 + host_val.len + sizeof(CRLF) - 1;
}
if (plcf->upstream.pass_request_headers) {
part = &r->headers_in.headers.part;
@ -1400,6 +1411,14 @@ ngx_http_proxy_create_request(ngx_http_request_t *r)
sizeof(ngx_http_proxy_version) - 1);
}
/* Host header */
if (host_val.len) {
b->last = ngx_cpymem(b->last, "Host: ", sizeof("Host: ") - 1);
b->last = ngx_copy(b->last, host_val.data, host_val.len);
*b->last++ = CR; *b->last++ = LF;
}
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
e.ip = headers->values->elts;
@ -3510,7 +3529,6 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf)
* conf->headers.values = NULL;
* conf->headers.hash = { NULL, 0 };
* conf->headers_cache.lengths = NULL;
* conf->host_set = 0;
* conf->headers_cache.values = NULL;
* conf->headers_cache.hash = { NULL, 0 };
* conf->body_lengths = NULL;
@ -4090,7 +4108,6 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
#if (NGX_HTTP_CACHE)
conf->headers_cache = prev->headers_cache;
#endif
conf->host_set = prev->host_set;
}
rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers,
@ -4123,8 +4140,6 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
#if (NGX_HTTP_CACHE)
prev->headers_cache = conf->headers_cache;
#endif
prev->host_set = conf->host_set;
prev->authority = conf->authority;
}
return NGX_CONF_OK;
@ -4177,37 +4192,6 @@ ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf,
src = conf->headers_source->elts;
for (i = 0; i < conf->headers_source->nelts; i++) {
if (src[i].key.len == 4
&& ngx_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0)
{
conf->host_set = 1;
if (conf->authority != NULL) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"proxy_set_header Host\" cannot be "
"used with \"proxy_set_authority\"");
return NGX_ERROR;
} else {
ngx_http_compile_complex_value_t ccv;
conf->authority = ngx_palloc(cf->pool,
sizeof(ngx_http_complex_value_t));
if (conf->authority == NULL) {
return NGX_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &src[i].value;
ccv.complex_value = conf->authority;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_ERROR;
}
}
}
s = ngx_array_push(&headers_merged);
if (s == NULL) {
return NGX_ERROR;
@ -4309,6 +4293,28 @@ ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf,
*code = (uintptr_t) NULL;
}
if (conf->authority == NULL) {
ngx_str_t v;
ngx_http_compile_complex_value_t ccv;
ngx_str_set(&v, "$proxy_internal_host");
conf->authority = ngx_palloc(cf->pool,
sizeof(ngx_http_complex_value_t));
if (conf->authority == NULL) {
return NGX_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &v;
ccv.complex_value = conf->authority;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_ERROR;
}
}
code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t));
if (code == NULL) {
return NGX_ERROR;
@ -4595,6 +4601,48 @@ ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
}
static char *
ngx_http_proxy_set_header(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_proxy_loc_conf_t *plcf = conf;
ngx_str_t *value = cf->args->elts;
if (value[1].len == 4
&& ngx_strncasecmp(value[1].data, (u_char *) "Host", 4) == 0)
{
ngx_http_compile_complex_value_t ccv;
if (plcf->authority != NGX_CONF_UNSET_PTR
&& plcf->authority != NULL)
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"proxy_set_header Host\" cannot be "
"used with \"proxy_set_authority\"");
return NGX_CONF_ERROR;
}
plcf->authority = ngx_palloc(cf->pool,
sizeof(ngx_http_complex_value_t));
if (plcf->authority == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &value[2];
ccv.complex_value = plcf->authority;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
return ngx_conf_set_keyval_slot(cf, cmd, conf);
}
static char *
ngx_http_proxy_cookie_domain(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{

View file

@ -49,7 +49,6 @@ typedef struct {
ngx_http_proxy_headers_t headers_cache;
#endif
ngx_array_t *headers_source;
ngx_uint_t host_set;
ngx_http_complex_value_t *authority;
ngx_array_t *proxy_lengths;

View file

@ -439,14 +439,16 @@ ngx_http_proxy_v2_create_request(ngx_http_request_t *r)
/* :authority header */
host = &ctx->ctx.vars.host_header;
if (plcf->authority != NULL) {
if (ngx_http_complex_value(r, plcf->authority, &host_val) != NGX_OK) {
return NGX_ERROR;
}
host = &host_val;
} else {
host = &ctx->ctx.vars.host_header;
if (host_val.len) {
host = &host_val;
}
}
if (host->len > NGX_HTTP_V2_MAX_FIELD) {