230

In Nginx, what's the difference between variables $host and $http_host.

glarrain
  • 8,031
  • 7
  • 31
  • 44

4 Answers4

278

$host is a variable of the Core module.

$host

This variable is equal to line Host in the header of request or name of the server processing the request if the Host header is not available.

This variable may have a different value from $http_host in such cases: 1) when the Host input header is absent or has an empty value, $host equals to the value of server_name directive; 2)when the value of Host contains port number, $host doesn't include that port number. $host's value is always lowercase since 0.8.17.

$http_host is also a variable of the same module but you won't find it with that name because it is defined generically as $http_HEADER (ref).

$http_HEADER

The value of the HTTP request header HEADER when converted to lowercase and with 'dashes' converted to 'underscores', e.g. $http_user_agent, $http_referer...;


Summarizing:

  • $http_host equals always the HTTP_HOST request header.
  • $host equals $http_host, lowercase and without the port number (if present), except when HTTP_HOST is absent or is an empty value. In that case, $host equals the value of the server_name directive of the server which processed the request.
Daniel
  • 111
  • 7
glarrain
  • 8,031
  • 7
  • 31
  • 44
  • 77
    $host is specifically **the first `server_name`** that is defined in the current server block. if you have multiple `server_name`s, only the first one will appear. – Jonathan Vanasco Mar 14 '13 at 16:36
  • 5
    True. In fact, it is quite typical to define: server_name example.com www.example.com; – glarrain Mar 14 '13 at 16:41
  • 1
    you can have multiple `server_name` directives too. if you happen to have a regex in the first one, that becomes the `$host` , and all sorts of ugly stuff can happen during rewrite rules. – Jonathan Vanasco Mar 14 '13 at 17:09
  • 7
    Does the `$server_name` variable equal the `server_name` directive's value or the actual server name that was selected if there were multiple `server_name` directives? – CMCDragonkai Mar 13 '14 at 20:03
  • 2
    @CMCDragonkai `$server_name` is always equal to the first value specified with the server_name directive. For example, with `server_name example.com one.example.com two.example.com;`, `$server_name` will always be "example.com", **regardless of which host the user has specified**. In fact, if you don't have a `default_server`, the host might be something completely different (like example.org). – ATLief Apr 02 '19 at 14:23
  • 2
    Links are kinda 404 now. – luckydonald May 20 '20 at 15:35
18

The accepted answer and its comments don't seem to be correct (anymore). The docs (http://nginx.org/en/docs/http/ngx_http_core_module.html#var_host) say that $host is

in this order of precedence: host name from the request line, or host name from the “Host” request header field, or the server name matching a request

So $http_host is always the value of the Host header field. They might differ if the host in the request line (if specified) differs from the Host header field. Or if the Host header is not set.

server_name matches only the Host header field (http://nginx.org/en/docs/http/request_processing.html), so that $host may differ from the matched server_name.

Christian
  • 4,042
  • 4
  • 26
  • 28
  • yes, though it is important to note that `$http_host` has still the port included, while `$host` doesn't seem so. Some peculiar settings may need the port to behave well, otherwise they fall on standard settings (port 80/443). – Pier A Nov 28 '22 at 14:03
11

$http_host

$http_host always equals Host request header field

Host: example.org

$host

$host is in this order of precedence (from high to low):

  • Host name from the request line
    GET http://example.org/test/ HTTP/1.1
    
  • Host request header field
  • The server_name (in Nginx config) matching a request, even if server_name is wildcard (Ex: server_name *.example.org;)

Host name from the request line

When open URL http://example.org/test/ ...

Most browser send the request like this

GET /test/ HTTP/1.1
Host: example.org

Most browser doesn't send the request like this (but this is valid request)

GET http://example.org/test/ HTTP/1.1

Validation

Nginx testing config

server {
    listen       80;
    server_name  *.example.org;

    location / {
        default_type "text/plain";
        return 200 "[host] = $host";
    }
}

When all exist ...

$host = host name from the request line

curl http://127.0.0.1 -v \
  --request-target http://request.example.org/test/ \
  --path-as-is \
  -H "Host: host.example.org"

This command will

  • Connect to 127.0.0.1
  • Send request path as GET http://request.example.org/test/ HTTP/1.1
  • Set Host header to Host: host.example.org
*   Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET http://request.example.org/test/ HTTP/1.1
> Host: host.example.org
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.23.1
< Date: Fri, 21 Oct 2022 02:00:56 GMT
< Content-Type: text/plain
< Content-Length: 28
< Connection: keep-alive
<
* Connection #0 to host 127.0.0.1 left intact
[host] = request.example.org

When only Host header exist ...

$host = Host header

curl http://127.0.0.1/test/ -v \
  -H "Host: host.example.org"
*   Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET /test/ HTTP/1.1
> Host: host.example.org
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.23.1
< Date: Fri, 21 Oct 2022 02:01:37 GMT
< Content-Type: text/plain
< Content-Length: 25
< Connection: keep-alive
<
* Connection #0 to host 127.0.0.1 left intact
[host] = host.example.org

When none exist ...

$host = server_name (in Nginx config)

# HTTP 1.1 must have Host header, so use HTTP 1.0
curl http://127.0.0.1/test/ -v -H "Host:" -0
*   Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET /test/ HTTP/1.0
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.23.1
< Date: Fri, 21 Oct 2022 02:02:20 GMT
< Content-Type: text/plain
< Content-Length: 22
< Connection: close
<
* Closing connection 0
[host] = *.example.org

Ref: ngx_http_core_module, Nginx $host validation

Steely Wing
  • 16,239
  • 8
  • 58
  • 54
  • 2
    Excellent answer. The problem is, that "Host name from the request line" has the highest priority and will route the request to a `server` (vhost) even if the request has a bogus `Host` header and does not match `server_name`. This is an issue because applications behind nginx tend to use the passed on `Host` header. It's a pity that `server_name` does not check the `Host` header (if exists in the request) if there was a hostname in "the request line" -- a rare and mostly malicious case. – minusf Oct 18 '22 at 08:59
0

Both $http_host and $host are variables used in Nginx configuration files, but they have slight differences in their usage and behavior.

  1. $http_host: This variable represents the value of the "Host" header in the HTTP request. It includes the port if specified by the client. For example, if the client sends a request to "example.com:8080", then $http_host will be set to "example.com:8080". This variable is useful when you need to capture the exact host value from the client request.

  2. $host: This variable represents the name of the server requested by the client, without the port number. It only contains the hostname or IP address. For example, if the client sends a request to "example.com:8080", then $host will be set to "example.com". This variable is commonly used for server configuration, especially when defining server names or redirects.

To summarize, the main difference between $http_host and $host is that $http_host includes the port number specified by the client, while $host does not. In most cases, you would use $host when you need to capture the hostname for server-related configuration, and $http_host when you need to capture the complete "Host" header value with the port included.

Wollens
  • 81
  • 5