3

I have multiple domains on a single host, and nginx manages all of them. Each domain has it's own SSL certificate (which I get from certbot, using the "webroot" plugin).

I have a server block at the end of each config file, as a "catch-all" (from here and here), to return 404 for invalid subdomains.

Default nginx config file default.conf:

# ...other config...
include /path/to/domain1.conf;
include /path/to/domain2.conf;
# ...other config...

domain1.conf:

# redirect http to https
server {
  listen      80;
  listen      [::]:80;
  server_name domain1.com www.domain1.com
  return      301 https://$host$request_uri;
}

# redirect naked to www
server {
  listen      443 ssl http2;
  listen      [::]:443 ssl http2;
  server_name domain1.com
  include     path/to/ssl_config.conf
  return      301 https://www.$host$request_uri;
}

# serve subdomain www
server {
  listen      443 ssl http2;
  listen      [::]443 ssl http2;
  server_name www.domain1.com
  include     path/to/ssl_config.conf
  location    / { proxy_pass http://$app; }
}

# catch-all for invalid subdomains (e.g. foo.domain1.com)
server {
  listen       443 ssl http2 default_server;
  listen       [::]:443 ssl http2 default_server;
  server_name: _.domain1.com
  include      path/to/ssl_config.conf
  return       404;
}

domain2.conf:

# same as above, but uses "domain2.com" instead of "domain1.com"

But that causes an error:

[emerg] a duplicate default server for xxx.xxx.xxx.xxx:443

If I remove those default_server directives, then it doesn't route properly: a request to foo.example1.com redirects to www.foo.example1.com, then to www.www.foo.example1.com, etc.

Everything works, except for the invalid subdomain logic. How can I fix it?

lonix
  • 14,255
  • 23
  • 85
  • 176
  • You should try `server_name *.example1.com;` to match specific domains. See [this document](http://nginx.org/en/docs/http/server_names.html). – Richard Smith Jun 13 '22 at 10:17
  • @RichardSmith Thanks. I just tried that, but unfortunately I get the same error. – lonix Jun 13 '22 at 10:31

1 Answers1

5

You need only the single default server block to catch everything else that is undefined in other server blocks. You don't need to expose any of your real certificates in that block; use the dummy self-signed certificate/key instead for the security purposes. You don't need to use any server_name at all in that block; moreover, that _ doesn't act as a wildcard at all. You can see that

server_name _;

in some nginx configurations from time to time due to the historical reasons because before nginx 0.7.12 you was required to specify something as server_name, which isn't required nowadays anymore. More information provided by the official documentation:

In catch-all server examples the strange name "_" can be seen:

server {
    listen       80  default_server;
    server_name  _;
    return       444;
}

There is nothing special about this name, it is just one of a myriad of invalid domain names which never intersect with any real name. Other invalid names like "--" and "!@#" may equally be used.

An example of a catch-all block can be the following one:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 443 default_server ssl;
    listen [::]:443 default_server ssl;
    ssl_certificate /some/path/any.crt;
    ssl_certificate_key /some/path/any.key;
    return 444; # silently drop the connection
    # or you can define some landing page here
}

For generating a pair of self-signed key/cert in one line you can use the following command:

openssl req -nodes -new -x509 -subj "/CN=localhost" -keyout /some/path/any.key -out /some/path/any.crt

A good practice is to put this stub server block as the /etc/nginx/conf.d/default.conf or /etc/nginx/sites-enabled/default file contents, depending on the vhosts serving style you are actually using (a long read discussion on this subject can be found here at the ServerFault).

If you are using certbot, don't allow it to auto-generate redirect server blocks for you in case of using such a stub server block. Since everything with a non valid request hostname will be handled by the stub block, instead of something like

server {
    if ($host = example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
    if ($host = www.example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;
    return 404; # managed by Certbot
}

use the following one:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

To redirect non-www to www and HTTP to HTTPS, use three server blocks for each hosted vhost:

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com;
    # ssl certificate/key for 'example.com' domain here
    return 301 https://www.example.com$request_uri;
}
server {
    listen 80;
    listen [::]:80;
    server_name www.example.com;
    return 301 https://www.example.com$request_uri;
}
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name www.example.com;
    # ssl certificate/key for 'www.example.com' domain here
    # the main configuration part
    ...
}
Ivan Shatsky
  • 13,267
  • 2
  • 21
  • 37
  • Thanks Ivan! Are you saying I must use a self-gen cert in production? It's only for invalid subdomains, I understand, but can that cause any problems (e.g. browsers)? – lonix Jun 13 '22 at 11:18
  • Do you really want to expose valid certificate to some kind of IP range scanning bot to allow it to see what actual site(s) hosted here? I don't think so, and you are not going to serve those requests anyway. – Ivan Shatsky Jun 13 '22 at 11:21
  • I just tried this - when I use a self cert, the subdomain shows an invalid cert error (I tested in firefox). This is a nice solution because it solves one problem (nginx error goes away), but it has another (it fails in the browser). I'm not sure which way I'll go but thank you anyway for this option. I suspect I'm going to use your way because at least it works... need to think about it some more. Thanks. – lonix Jun 13 '22 at 11:26
  • Not sure what you call a "subdomain"; nevertheless check the answer update. If you somewhat clarify what exactly is working and what isn't, maybe I'll be able to help more. – Ivan Shatsky Jun 13 '22 at 11:37
  • I added all the config to my question - it's almost the same as yours. For subdomain I mean "foo.example.com" (which should give 404). Because there are two server blocks with `default_server` nginx gives an error. If I remove them then it works but performs endless redirect: foo.example.com -> www.foo.example.com -> www.www.foo.example.com -> etc. That is why I said your idea of the self-gen cert is a good one, but I'm moving the error from nginx to the browser (maybe that's ok). – lonix Jun 13 '22 at 12:03
  • The first thing is that you need only one, single catch-all block with the `default_server` flag being set upon the `listen` directive(s), which will work for all those `domain1.com`, `domain2.com`, etc. You can't have multiple `default_server` server blocks listening on the same port unless they are listening on different network interfaces / IP addresses. Next, do you keep in mind than the 301 permanent redirect, being once received by the browser, will be cached and no additional requests with the same hostname and request URI will be made to your server unless you clear your browser cache? – Ivan Shatsky Jun 13 '22 at 13:17
  • The three blocks used in your configuration looks similar to my variant, except mine uses less variables for interpolation (not uses the `$host` one), which should be slightly faster and prevent those loop errors in case of incorrectly written configuration. I don't see anything that is able to produce that kind of loop in the configuration you added to your question (if it isn't the browser cache itself, see the previous comment; you can check that for sure from the incognito browser window). – Ivan Shatsky Jun 13 '22 at 13:22
  • @IvanShatsky I copied your answer into an answer in the thread [ssl - Properly setting up a "default" nginx server for https - Server Fault](https://serverfault.com/questions/578648/properly-setting-up-a-default-nginx-server-for-https) as I thought it to be way better than the others. If you would like to claim it, please let me know, and I will delete mine. Thanks! – toraritte Aug 10 '23 at 18:02