Nginx – Q: NGINX – Dynamically mapping multiple different domain names through ‘map’ to use as “server_name”

mappingnginxvirtualhost

Looking for some advice and peer input – I'm currently trying to configure NGINX in a way so that I can configure multiple websites at the same time without having to set up individual VHost configs or declaring multiple 'server {}' entries with duplicate config parameters in my NGINX configuration. I have partial success thus far but I'm worried that in doing this it might create security concerns or inadvertently have too broadly defined range. I'm also unsure how to properly/correctly use the "default" value in 'map' directive.

All in all, currently I have a typical NGINX configuration with 2 VHost configs for one domain name (and a 'www.*' subdomain for it) in 'nginx_root/sites-available' – one for HTTP (port 80) and one for HTTPS (port 443) which are soft-symlinked to 'nginx_root/sites-enabled'. But as previously mentioned – I'd like to add additional domain names but keep the amount and size of these configs as they currently are by using the NGINX 'map' features to set-up new domain names dynamically.

The way I've mapped it at the moment is by creating a config file: 'nginx_root/mapped_fqdn.conf' which I have then included early in my NGINX config (so that it gets mapped before it's called/referenced further in the config) and then using the map as 'server_name' parameter. If it's possible to expand this to other config parameters and safe to do so, I'd like to add the new domain names in this same way.

mapped_fqdn.conf:

### Domain name mapping
    map $host $fqdn_map {
        hostnames;
        default 0;

        domain-1.com 0;
        www.domain-1.com 0;
    }

00-http.conf:

server {
    listen 80;
    listen [::]:80;
    server_name $fqdn_map;
    root /var/www/public_html;
    index index.html;
    access_log /var/log/nginx/nginx_access.log nginx_access;;

    ### HTTP to HTTPS Redirect
    return 301 https://$server_name$request_uri;
}

For the sake of not repeating I'll skip adding the HTTPS config, which looks about the same, except it has some few minor additional parameters, along with the SSL-specific config parameters added and doesn't have the 301 redirect, which is present in the HTTP config.

The way I see it, I can add the additional domain names in the same way…

example of updated mapped_fqdn.conf:
(the way I see is that perhaps adding specific subdomains would be safer than adding them like wildcards, i.e.: "*.domain.com")

### Domain name mapping
    map $host $fqdn_map {
        hostnames;
        default 0;

        domain-1.com 10;
        www.domain-1.com 11;

        domain-2.co.uk 20;
        www.domain-2.co.uk 21;
        subdomain1.domain-2.co.uk 22;
        subdomain2.domain-2.co.uk 23;

        domain-3.com 30;
        www.domain-3.com 31;
        subdomain1.domain-3.com 32;
    }

example of updated 00-http.conf:

server {
    listen 80;
    listen [::]:80;
    server_name $fqdn_map;
    root /var/www/$fqdn_map/public_html;
    index index.html;
    access_log /var/log/nginx/$fqdn_map-nginx_access.log nginx_access;;

    ### HTTP to HTTPS Redirect
    return 301 https://$server_name$request_uri;
}

However, before I proceed, I'm concerned about how would this work with directories, i.e.: Let's Encrypt certificates for HTTPS config – is it possible to specifically select which mapped items to use? For example: $fqdn_map{10|20|30} (this way selecting only the domain names without subdomain names).

    ssl_client_certificate /etc/ssl/certs/$fqdn_map{10|20|30}/cloudflare_origin.pem;
    ssl_certificate /etc/letsencrypt/live/$fqdn_map{10|20|30}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/$fqdn_map{10|20|30}/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/$fqdn_map{10|20|30}/chain.pem;

Or perhaps I'm going about this the wrong way and I should map FQDN and subdomains separately … or maybe just create symlinks and junctions from mapped subdomain directories to fqdn directories (which, while resulting in less/cleaner configs, leads to more symlinking kinda defeating the purpose of "doing less but achieving more")?

Best Answer

If you simply want to map hostnames to certificate directories, then you can use this approach:

map $http_host $certdir {
    hostnames;

    dom1.example.com dom1;
    dom2.example.com dom2;
    .example.com dom;
}

server {
    listen 443 ssl;

    server_name something.example.com;

    ssl_certificate /path/to/certificates/$certdir/dom.crt;
    ssl_certificate_key /path/to/certificates/$certdir/dom.key;
    ...
}

Here $certdir value will be dom, since something.example.com matches .example.com in the map definition.

--- First answer which answered a question which was not originally asked ---

Maybe you are looking for this:

server {
    listen 80 default_server;

    server_name _;

    root /var/www/$host/public_html;

    ....
}

We specify a default_server block here, which processes all requests that do not have a matching virtual host elsewhere.

Then we use the $host variable, which contains the domain name.

If you want to use more specific matching for only some domains, use regular expression:

server {
     listen 80;

     server_name ~ ^(domain-1.com|domain-2.com)$;

     root /var/www/$host/public_html;

     ....
}

You can refine the regular expression.

However, as I stated in my comment, this will place restrictions how you can customise each server block. You could use include /etc/nginx/conf.d/$host.conf to add domain specific configuration in another file.

Still, in the long run a configuration management system is the best option.