2

I've been running across an odd behavior in Nginx with requests that have a trailing dot in the hostname, i.e. domain.com. rather than domain.com alone. I set up a simple server config to test, like so:

server {
    listen 80;
    server_name example.com.;
    root /var/www/example;
    index server1.txt;
}

server {
    listen 80;
    server_name example.com;
    root /var/www/example;
    index server2.txt;
}

Initially, I expected that requests made to example.com. would be sent to the first block and requests made to example.com would be sent to the second. Requests whose Host header didn't match any block, e.g. www.example.com, I expected to be sent to the first block again as it was the implicit default.

However, upon testing I discovered that requests made to example.com. were sent instead to the second block. After messing around for a good while with various alternate names, regular expressions etc. I decided to write the $host variable to a custom header so I could look at it. Turns out that Nginx is actually dropping the trailing dot from the $host value. As far as I can tell a request received at example.com. is considered identical to one received at example.com, at least as far as server-selection goes. This seems undesirable, as trailing dots can result in various errors..

To make matters even more confusing, after some googling I found the Nginx changelog page, which has in the 0.1.29 changelog:

Bugfix: nginx did not take into account trailing dot in "Host" header line.

Then, years later, the 1.5.9 changelog says:

Bugfix: resolver did not understand domain names with a trailing dot. Thanks to Yichun Zhang.

Although I'm not sure whether the "resolver" refers to a component of Nginx that is in play when it receives a request. (From reading the docs, it sounds like maybe the resolver doesn't do anything unless a request is being forwaded to some other host, whose name must then be resolved.)

What's going on here? Should Nginx be dropping the trailing dot when it evaluates server names? If this is intended behavior, shouldn't the trailing dot also be dropped from the server_name parameter to yield a "conflicting server name" error?

I know that a domain name without a trailing dot is technically a "relative" domain name rather than "absolute," although most people these days seem to treat them as all relative to the . zone so it doesn't make a practical difference. But shouldn't Nginx at least be able to make this distinction if desired?

Finally, is there any better way to catch and redirect requests made to example.com. than adding if ($http_host = 'domain.com.')? I've been told that this is inefficient, as it requires the Host header to be evaluated twice.

1
  • The 1.5.9 bugfix you mention is discussed on the mailing list here. I too, do not know whether the "resolver" is used to pick the server/vhost block, but I can confirm that server_name example.com. matches neither example.com nor example.com. in my tests. Oct 4 '17 at 10:19
3

At the DNS level, example.com and example.com. are the same name. That does not just mean that they are supposed to be treated the same, it means that both of them will encode to the exact same sequence of octets in a DNS packet. Trying to treat them as different in protocols that use DNS (like, for example, HTTP) is guaranteed to cause confusion and problems.

If a piece of software uses names from DNS and treats the same name with and without a trailing dot as different, that piece of software has a bug.

2
  • "At the DNS level, example.com and example.com. are the same name." I was under the impression that the first is a partially-qualified domain name, while the second is fully-qualified. Cf. RFC 1535: An absolute "rooted" FQDN is of the format {name}{.} A non "rooted" domain name is of the format {name} Apr 7 '17 at 18:18
  • A partially-qualified name only exists on the user-interface level. It's technically impossible to encode one into DNS wire format. Each label is encoded as a <length><data> pair, and the list of labels terminates with an empty label (that is, a single zero octet representing no length). That final empty label is the terminal period character, when writing the name out fully for humans. It must be there, or the packet is malformed and can't be parsed. Apr 7 '17 at 20:31
2

TLDR;

This seems like a Nginx bug, probably because few people know that fully qualified domain names end in a dot.

Background

I had to read up on your statement "fully qualified domain names end with a dot". I found this useful resource that explains it quite well. There's also a SF question and answer here. Is seems that only a very small minority (including myself until now) knew that a domain could even have a trailing dot.

Note

Note that a "host header" and "Nginx server_name" are quite different. The server_name defines the requests the Nginx server will reply to. The "host header" is part of the http header sent by the client to the server.

Example

I tried your example servers, with example.com mapped to my server, with the same results as you on nginx 1.9.11. I also tried with just the server "example.com." defined but not "example.com". When I curled that configuration I got a response from my default server, rather than the "example.com." server.

Documentation

The Nginx server_name documentation does not mention a trailing dot. The documentation does allow a wildcard after the dot (example.com.*). I wonder if whoever wrote the documentation knew about fully qualified domain names.

Theory

I have to wonder though, if you define "example.com" and "example.com." in Nginx what is the difference? "example.com" would have to be assumed to be the full URL from the DNS root, so there's an assumed "." after it anyway.

Conclusion

I think you're probably technically correct that the Nginx server_name should cater for the trailing dot. I'm not sure that it should treat it any differently or distinct from a server_name without the trailing dot, for the reasons outlined above.

This seems like it could be a bug in Nginx as a result of oversight. It could also be deliberate, based on common practices rather than the RFC.

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.