73

What is the difference between the three Nginx variables $host, $http_host, and $server_name?

I have a rewrite rule where I'm not sure which one I should be using:

location = /vb/showthread.php {
    # /vb/showthread.php?50271-What-s-happening&p=846039
    if ($arg_p) {
        return 301 $scheme://$host/forum/index.php?posts/$arg_p/;
        }

I'm looking for an answer that doesn't just say 'use ___ variable in your rewrite rule' but also explains the theoretical differences between them.

Jeff Widman
  • 2,285
  • 3
  • 22
  • 20
  • I realized later I didn't even need to specify `$scheme` and `$host`... `return 301 /forum/index.php?posts/$arg_p/;` works fine. – Jeff Widman Jul 17 '15 at 08:06
  • Most browser would work with relative URL in redirect, but the standard (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) requires absolute url in the `Location` header. – Cthulhu Jul 17 '15 at 09:16

4 Answers4

82

You should almost always use $host, as it's the only one guaranteed to have something sensible regardless of how the user-agent behaves, unless you specifically need the semantics of one of the other variables.

The difference is explained in the nginx documentation:

  • $host contains "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"
  • $http_host contains the content of the HTTP "Host" header field, if it was present in the request
  • $server_name contains the server_name of the virtual host which processed the request, as it was defined in the nginx configuration. If a server contains multiple server_names, only the first one will be present in this variable.

Since it is legal for user-agents to send the hostname in the request line rather than in a Host: header, though it is rarely done except when connecting to proxies, you have to account for this.

You also have to account for the case where the user-agent doesn't send a hostname at all, e.g. ancient HTTP/1.0 requests and modern badly-written software. You might do so by diverting them to a catch-all virtual host which doesn't serve anything, if you are serving multiple web sites, or if you only have a single web site on your server you might process everything through a single virtual host. In the latter case you have to account for this as well.

Only the $host variable accounts for all the possible things that a user-agent may do when forming an HTTP request.

Michael Hampton
  • 237,123
  • 42
  • 477
  • 940
5

I would like to add another important point not mentioned in the accepted answer.

$host do NOT have port number, while $http_host does include the port number.

edit: not always.

I set up a header "add_header Y-blog-http_host "$http_host" always;"

Then curl -I -L domain.com:80 (or 443) and the header doesn't show a port number at all. Verified with nginx-extra 1.10.3. Is is because it's common http(s) ports or nginx configuration? This comment just to say things do not always behave the way you think.

Mohammed Noureldin
  • 491
  • 1
  • 9
  • 24
  • _Is is because it's common http(s) ports or nginx configuration?_ Yes, `curl` won't add the common port number to the `Host` header even if you invoke it that way. However there is no warranty that some other program will not add that number if being invoked similar way. And of course you can manually overwrite the default `Host` header using `curl -I -H 'Host: domain.com:80' ...` – Ivan Shatsky Jun 10 '22 at 10:01
1

TL;DR: If you don't use regular expressions in server_name you can ignore this answer .

Since this aspect hasn't been mentioned at all in other answers, but it's something that I found somewhat annoying: I use $host in the creation of my log file names, like so:

access_log /var/log/nginx/access_$host.log

Alas, if you use a regular expression in your server_name stanza like:

server_name ~^(?<subdomain>foo|bar|baz)\.example.net;

Your $host will actually end up containing the regular expression like so (yep, including the backslash and everything):

access_~^(?<subdomain>foo|bar|baz)\.example.net.log

I for one found this very annoying and ended up changing my regular expression to:

server_name ~^(?<fulldomain>(?<subdomain>foo|bar|baz)\.example.net);

to capture the full name for reuse in the log file name. The respective stanza was then changed to:

access_log /var/log/nginx/access_$fulldomain.log
0xC0000022L
  • 1,456
  • 2
  • 20
  • 41
1

I also struggled with this for a while. It became clear when I understood that $http_XXXXX refer to all declared header variables.

So $http_user_agent, $http_referer are "USER AGENT", "REFERER" referenced in lower case and underslash. This explained me where the hell $http_upgrade was coming from in many NGINX configuration samples.

Read it from https://stackoverflow.com/questions/15414810/whats-the-difference-of-host-and-http-host-in-nginx

luison
  • 273
  • 1
  • 7
  • 21