Home > Enterprise >  Catch-all nginx server blocks for invalid subdomains
Catch-all nginx server blocks for invalid subdomains

Time:06-14

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?

CodePudding user response:

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
    ...
}
  • Related