Upstream: special handling of the "Host" header in proxy and gRPC

HTTP/2 and gRPC requests are now always sent with the ":authority"
pseudo-header, while the "Host" header is no longer passed.
Where a host value is set explicitly with "proxy_set_header Host"
or "grpc_set_header Host", that value is conveyed in ":authority".
As per RFC 9113, Section 8.3.1, clients and intermediaries MUST
generate an ":authority" pseudo-header to convey authority information.

For HTTP/1.1, HTTP/2 and gRPC, if the explicit value is empty, then
the value of $proxy_host is used instead.  As per RFC 9110, Sections
4.2.1 and 4.2.2, the "http" and "https" URI schemes MUST contain a
non-empty host identifier.  The change enforces this rule by
introducing a default.

Closes: https://github.com/nginx/nginx/issues/1426
This commit is contained in:
Roman Arutyunyan 2026-07-21 18:14:39 +04:00 • committed by Roman Arutyunyan
parent e419e28c98
commit dea68dbf12
4 changed files with 140 additions and 80 deletions

View file

@ -25,7 +25,7 @@ typedef struct {
ngx_array_t *headers_source;
ngx_str_t host;
ngx_uint_t host_set;
ngx_http_complex_value_t *host_value;
ngx_array_t *grpc_lengths;
ngx_array_t *grpc_values;
@ -739,6 +739,7 @@ ngx_http_grpc_create_request(ngx_http_request_t *r)
key_len, val_len, uri_len;
uintptr_t escape;
ngx_buf_t *b;
ngx_str_t host;
ngx_uint_t i, next;
ngx_chain_t *cl, *body;
ngx_list_part_t *part;
@ -809,18 +810,28 @@ ngx_http_grpc_create_request(ngx_http_request_t *r)
/* :authority header */
if (!glcf->host_set) {
if (ctx->host.len > NGX_HTTP_V2_MAX_FIELD) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"too long http2 host: \"%V\"", &ctx->host);
return NGX_ERROR;
}
host.len = 0;
len += 1 + NGX_HTTP_V2_INT_OCTETS + ctx->host.len;
if (glcf->host_value
&& ngx_http_complex_value(r, glcf->host_value, &host) != NGX_OK)
{
return NGX_ERROR;
}
if (tmp_len < ctx->host.len) {
tmp_len = ctx->host.len;
}
if (host.len == 0) {
host = ctx->host;
}
if (host.len > NGX_HTTP_V2_MAX_FIELD) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"too long http2 host: \"%V\"", &host);
return NGX_ERROR;
}
len += 1 + NGX_HTTP_V2_INT_OCTETS + host.len;
if (tmp_len < host.len) {
tmp_len = host.len;
}
/* other headers */
@ -1051,14 +1062,11 @@ ngx_http_grpc_create_request(ngx_http_request_t *r)
"grpc header: \":path: %V\"", &r->uri);
}
if (!glcf->host_set) {
*b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX);
b->last = ngx_http_v2_write_value(b->last, ctx->host.data,
ctx->host.len, tmp);
*b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX);
b->last = ngx_http_v2_write_value(b->last, host.data, host.len, tmp);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"grpc header: \":authority: %V\"", &ctx->host);
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"grpc header: \":authority: %V\"", &host);
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
@ -4517,7 +4525,7 @@ 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->host_value = NULL;
* conf->ssl = 0;
* conf->ssl_protocols = 0;
* conf->ssl_ciphers = { 0, NULL };
@ -4725,7 +4733,7 @@ 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;
conf->host_value = prev->host_value;
}
rc = ngx_http_grpc_init_headers(cf, conf, &conf->headers,
@ -4743,7 +4751,7 @@ 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->host_value = conf->host_value;
}
return NGX_CONF_OK;
@ -4754,16 +4762,17 @@ static ngx_int_t
ngx_http_grpc_init_headers(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf,
ngx_http_grpc_headers_t *headers, ngx_keyval_t *default_headers)
{
u_char *p;
size_t size;
uintptr_t *code;
ngx_uint_t i;
ngx_array_t headers_names, headers_merged;
ngx_keyval_t *src, *s, *h;
ngx_hash_key_t *hk;
ngx_hash_init_t hash;
ngx_http_script_compile_t sc;
ngx_http_script_copy_code_t *copy;
u_char *p;
size_t size;
uintptr_t *code;
ngx_uint_t i;
ngx_array_t headers_names, headers_merged;
ngx_keyval_t *src, *s, *h;
ngx_hash_key_t *hk;
ngx_hash_init_t hash;
ngx_http_script_compile_t sc;
ngx_http_script_copy_code_t *copy;
ngx_http_compile_complex_value_t ccv;
if (headers->hash.buckets) {
return NGX_OK;
@ -4799,7 +4808,23 @@ ngx_http_grpc_init_headers(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf,
if (src[i].key.len == 4
&& ngx_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0)
{
conf->host_set = 1;
conf->host_value = ngx_pcalloc(cf->pool,
sizeof(ngx_http_complex_value_t));
if (conf->host_value == 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->host_value;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_ERROR;
}
continue;
}
s = ngx_array_push(&headers_merged);

View file

@ -759,7 +759,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") },
@ -788,7 +788,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") },
@ -825,10 +825,6 @@ static ngx_http_variable_t ngx_http_proxy_vars[] = {
{ ngx_string("proxy_add_via"), NULL, NULL, 0, NGX_HTTP_VAR_NOHASH, 0 },
#endif
{ ngx_string("proxy_internal_host"), NULL,
ngx_http_proxy_host_variable, 1,
NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
{ ngx_string("proxy_internal_body_length"), NULL,
ngx_http_proxy_internal_body_length_variable, 0,
NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
@ -904,8 +900,6 @@ ngx_http_proxy_handler(ngx_http_request_t *r)
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ctx->legacy = 1;
ngx_http_set_ctx(r, ctx, ngx_http_proxy_module);
u = r->upstream;
@ -1193,7 +1187,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;
ngx_uint_t i, unparsed_uri;
ngx_chain_t *cl, *body;
ngx_list_part_t *part;
@ -1231,6 +1225,20 @@ ngx_http_proxy_create_request(ngx_http_request_t *r)
ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
host.len = 0;
if (plcf->host_value
&& ngx_http_complex_value(r, plcf->host_value, &host) != NGX_OK)
{
return NGX_ERROR;
}
if (plcf->host_value == NULL
|| (host.len == 0 && plcf->http_version == NGX_HTTP_VERSION_11))
{
host = ctx->vars.host_header;
}
if (method.len == 4
&& ngx_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0)
{
@ -1300,6 +1308,10 @@ ngx_http_proxy_create_request(ngx_http_request_t *r)
ctx->internal_body_length = r->headers_in.content_length_n;
}
if (host.len) {
len += sizeof("Host: ") - 1 + host.len + sizeof(CRLF) - 1;
}
le.ip = headers->lengths->elts;
le.request = r;
le.flushed = 1;
@ -1410,6 +1422,12 @@ ngx_http_proxy_create_request(ngx_http_request_t *r)
sizeof(ngx_http_proxy_version) - 1);
}
if (host.len) {
b->last = ngx_cpymem(b->last, "Host: ", sizeof("Host: ") - 1);
b->last = ngx_cpymem(b->last, host.data, host.len);
*b->last++ = CR; *b->last++ = LF;
}
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
e.ip = headers->values->elts;
@ -2727,11 +2745,6 @@ ngx_http_proxy_host_variable(ngx_http_request_t *r,
return NGX_OK;
}
if (data == 1 && !ctx->legacy) {
v->not_found = 1;
return NGX_OK;
}
v->len = ctx->vars.host_header.len;
v->valid = 1;
v->no_cacheable = 0;
@ -3542,7 +3555,7 @@ 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->host_value = NULL;
* conf->headers_cache.values = NULL;
* conf->headers_cache.hash = { NULL, 0 };
* conf->body_lengths = NULL;
@ -4126,7 +4139,7 @@ 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;
conf->host_value = prev->host_value;
}
rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers,
@ -4159,7 +4172,7 @@ 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->host_value = conf->host_value;
}
return NGX_CONF_OK;
@ -4170,16 +4183,17 @@ static ngx_int_t
ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf,
ngx_http_proxy_headers_t *headers, ngx_keyval_t *default_headers)
{
u_char *p;
size_t size;
uintptr_t *code;
ngx_uint_t i;
ngx_array_t headers_names, headers_merged;
ngx_keyval_t *src, *s, *h;
ngx_hash_key_t *hk;
ngx_hash_init_t hash;
ngx_http_script_compile_t sc;
ngx_http_script_copy_code_t *copy;
u_char *p;
size_t size;
uintptr_t *code;
ngx_uint_t i;
ngx_array_t headers_names, headers_merged;
ngx_keyval_t *src, *s, *h;
ngx_hash_key_t *hk;
ngx_hash_init_t hash;
ngx_http_script_compile_t sc;
ngx_http_script_copy_code_t *copy;
ngx_http_compile_complex_value_t ccv;
if (headers->hash.buckets) {
return NGX_OK;
@ -4215,7 +4229,23 @@ ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf,
if (src[i].key.len == 4
&& ngx_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0)
{
conf->host_set = 1;
conf->host_value = ngx_pcalloc(cf->pool,
sizeof(ngx_http_complex_value_t));
if (conf->host_value == 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->host_value;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_ERROR;
}
continue;
}
s = ngx_array_push(&headers_merged);

View file

@ -49,7 +49,7 @@ 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 *host_value;
ngx_array_t *proxy_lengths;
ngx_array_t *proxy_values;
@ -102,7 +102,6 @@ typedef struct {
unsigned head:1;
unsigned internal_chunked:1;
unsigned header_sent:1;
unsigned legacy:1;
} ngx_http_proxy_ctx_t;

View file

@ -326,7 +326,7 @@ ngx_http_proxy_v2_create_request(ngx_http_request_t *r)
loc_len, body_len;
uintptr_t escape;
ngx_buf_t *b;
ngx_str_t method, *host;
ngx_str_t method, host;
ngx_uint_t i, next, unparsed_uri;
ngx_chain_t *cl, *body;
ngx_list_part_t *part;
@ -445,20 +445,28 @@ ngx_http_proxy_v2_create_request(ngx_http_request_t *r)
/* :authority header */
host = &ctx->ctx.vars.host_header;
host.len = 0;
if (!plcf->host_set) {
if (host->len > NGX_HTTP_V2_MAX_FIELD) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"too long http2 host: \"%V\"", host);
return NGX_ERROR;
}
if (plcf->host_value
&& ngx_http_complex_value(r, plcf->host_value, &host) != NGX_OK)
{
return NGX_ERROR;
}
len += 1 + NGX_HTTP_V2_INT_OCTETS + host->len;
if (host.len == 0) {
host = ctx->ctx.vars.host_header;
}
if (tmp_len < host->len) {
tmp_len = host->len;
}
if (host.len > NGX_HTTP_V2_MAX_FIELD) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"too long http2 host: \"%V\"", &host);
return NGX_ERROR;
}
len += 1 + NGX_HTTP_V2_INT_OCTETS + host.len;
if (tmp_len < host.len) {
tmp_len = host.len;
}
/* other headers */
@ -719,13 +727,11 @@ ngx_http_proxy_v2_create_request(ngx_http_request_t *r)
val_tmp);
}
if (!plcf->host_set) {
*b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX);
b->last = ngx_http_v2_write_value(b->last, host->data, host->len, tmp);
*b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX);
b->last = ngx_http_v2_write_value(b->last, host.data, host.len, tmp);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http proxy header: \":authority: %V\"", host);
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http proxy header: \":authority: %V\"", &host);
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));