When receiving a HTTP/1.x request, NGINX does not check that the
authority
coming from the request line (if present) is consistent with the
authority
from the Host header field. In some deployments (when $http_host is
used)
an attacker could exploit this ambiguity to bypass security restrictions
configured in NGINX (of have other security impacts).
Example:
GET http://foo/ HTTP/1.1 <- Used for virtual host routing
User-Agent: UA
Host: bar <- Used for $http_host
Debian's proxy_param file used to use $http_host:
proxy_set_header Host $http_host;
This has been fixed as part of Debian bug #1126960 [1].
In this case, "foo" is used for virtual host routing but the request is
forwarded as:
GET / HTTP/1.1
User-Agent: UA
Host: bar
X-Real-IP: ...
X-Forwarded-For: ...
X-Forwarded-Proto: ...
If the backend application actually makes use of the Host value, this
might have a security impact. For example,
* if the backend application uses the Host value for tenant dispatching;
* and NGINX is configured with per-tenant configuration (eg.
authorization,
logging, etc),
then this might have a security impact.
Example NGINX configuration:
server {
listen 443 ssl;
server_name tenant1;
ssl_certificate /etc/nginx/ssl/tenant1.crt;
ssl_certificate_key /etc/nginx/ssl/tenant1.key;
location / {
proxy_pass http://backend;
include proxy_params;
}
}
server {
listen 443 ssl;
server_name host2;
ssl_certificate /etc/nginx/ssl/tenant2.crt;
ssl_certificate_key /etc/nginx/ssl/tenant2.key;
location / {
allow 10.0.0.0/8;
allow 192.168.0.0/16;
allow 127.0.0.1/8;
deny all;
proxy_pass http://backend;
include proxy_params;
}
}
In this example, tenant2 is expected to be only available from
private IP addresses. However, an attacker could target tenant2
with:
GET http://tenant1/ HTTP/1.1 <- Used for virtual host routing
User-Agent: UA
Host: tenant2 <- Used for $http_host
Another potential application, would be to send access logs
of an attack to the log files of the wrong tenant.
You might be impacted if:
* NGINX is directly exposed;
* you use $http_host (eg. through Debian's proxy_params).
NGINX's position
----------------
NGINX's position is that the bug is to use $http host. NGINX's
documentation recommends using $host [2]:
proxy_set_header Host $host;
I would claim that NGINX could (should?):
1. either reject host ambiguous requests altogether
(because they are attacks right?);
2. or override the HTTP header with the value from the request line.
These behaviors are consistent with what other (open-soruce)
HTTP server do:
1. NGINX when using HTTP/2, HA proxy for solution 1;
2. Traefik, Caddy, Apache HTTPD for solution 2.
At the very least, the security impact of using $host vs
$http_host should be better documented.
Mitigations
-----------
Mitigation 1: always use `$host` instead of `$http_host`:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
Mitigation 1b: always use `$server_name` instead of `$http_host`:
proxy_set_header Host $server_name;
proxy_set_header X-Forwarded-Host $server_name;
Mitigation 1c: always the hardcoded expected hostname instead of
`$http_host`.
proxy_set_header Host "www.example.com";
proxy_set_header X-Forwarded-Host "www.example.com";
Mitigation 2: use NGINX directive to reject ambiguous (malicious)
requests.
# Does not work when using ports:
if ($host != $http_host) {
return 421 "Ambiguous host";
}
# Since nginx 1.29.3:
if ($http_host != "$host$is_request_port$request_port") {
return 421 "Ambiguous host";
}
Debian info
-----------
Snippet from Debian changelogs:
nginx (1.26.3-3+deb13u4) trixie; urgency=medium
* d/conf/*_params: use "$host" instead of "$http_host"
* "$http_host" forwards the Host header exactly as supplied by
the client
and may not match the effective request target (e.g.
absolute-form
requests with a conflicting Host header)
this can expose inconsistent or attacker-controlled host
values to
backend applications (uwsgi, fastcgi, scgi, proxy)
* switch to "$host" as a safer, normalized alternative
* note: this changes behaviour, as "$host" does not preserve the
client-supplied port; deployments relying on "$http_host"
including
a port number may be affected
* it is workaround for Debian bug #1126960 for stable/oldstable
release
-- Jan Mojžíš <[email protected]> Mon, 20 Apr 2026 17:52:06
+0000
New proxy_params file:
# !!! Security workaround !!!
# Do not set the `Host` header as "$http_host".
#
# "$http_host" is the Host header exactly as supplied by the client.
# This is unsafe when a client sends an absolute-form request target
together
# with a different Host header, for example:
#
# GET https://example.com/ HTTP/1.1
# Host: malformedhost
#
# In such a case, passing "$http_host" upstream exposes the raw
client-supplied
# Host value ("malformedhost") to the backend application, even
though it does
# not match the effective request target. Applications often use
HTTP_HOST for
# redirects, absolute URL generation, virtual host routing, or
security checks;
# forwarding the raw Host header can therefore lead to incorrect or
unsafe
# behaviour.
#
# Newer nginx versions (since 1.30.0) introduce variables
"$is_request_port" and
# "$request_port", allowing `Host` to be constructed as:
# $host$is_request_port$request_port
#
# In stable/oldstable packages we use "$host" as a security
workaround.
# It avoids forwarding an untrusted raw Host header to the backend.
#
# Note: this changes behaviour compared to previous versions,
because "$host"
# does not preserve the client-supplied port, while "$http_host"
typically
# does. Existing deployments that rely on "$http_host" containing a
port number
# may therefore break or behave differently after this change.
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
[1] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1126960
[2] https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
[3]
https://metadata.ftp-master.debian.org/changelogs//main/n/nginx/nginx_1.26.3-3+deb13u5_changelog
Regard,
--
Gabriel Corona