nginx map directive config multiple certificates
Published: 2023-04-02 10:27:19

https://jjj.blog/2019/05/variable-ssl-certificate-directives-in-nginx-part-2/

https://www.nixops.me/articles/ngnix-mutiple-https-sites.html


Variable SSL certificate directives in nginx (part 2)
May 6, 2019
Feeling encouraged by my friend Jeremy Felt’s blog post on the subject, I thought I may finally be able to achieve the panacea of WordPress Multi-Network SSL configurations:

Multisite (subdirectory install type, with subdomains anyways)
Multi-Network (using the WP Multi Network plugin)
LetsEncrypt Wildcard Certificates for every domain
Behind an nginx proxy server, using a single set of SSL directives
In the past few years (really since LetsEncrypt saved the web) a bunch of really smart folks have invested a bunch of time into trying to address that last bullet point.

LetsEncrypt traditionally stores its SSL certificates in the following location:

/etc/letsencrypt/live/example.org/fullchain.pem;
What we need to do, is take example.org and make it a variable.

To this day, if you search the web for anything similar to “nginx SSL variables” you’ll find dozens of (now out of date) articles, where frustrated folks are attempting to do the impossible – use nginx variables inside the ssl_certificate and ssl_certificate_key configuration settings.

Since nginx 1.15.9, this is now possible. It isn’t incredibly straightforward, but it is possible. Here’s what you need:

Latest version of WordPress (5.1 or higher)
Latest version of WP Multi Network (2.2.0 or higher)
Latest version of nginx (1.15.9 or higher)
Latest version of openSSL (1.1.0j or higher)
As I write this, nginx 1.15.9 isn’t the latest package available, so to use this you’ll need to compile it from source. This isn’t for everyone, and there are repercussions for doing it wrong (trust me on this) but once it’s working, it’s really-really sweet, so at the very least you may want to spin up a test server and give it a go.

I searched the web for quite a few hours looking for anyone that got this working, and frankly, no one really has other than Jeremy. I even dug into the nginx Trac and found issue 1744 where Maxim Dounin was kind enough to gently nudge me in the right direction.

He said:

Variables set with the set directive of the rewrite module are only available after rewrite instructions were evaluated when processing a request, see ​rewrite module documentation. 

When loading certificates you have to use builtin connection-related variables, or custom variables which are always available – such as provided with ​map, ​geo, ​perl_set, or ​js_set.

Loosely translated, that means you can’t just use any variable you’ll find in the nginx variables list; and you can’t just set your own the way probably expected to, you need to “map” them from a variable that is guaranteed to always exist.

Here’s what I came up with:

map $ssl_server_name $ssl_domain_name {
     volatile;
     hostnames;
     default 'example.org'; # Your "root" WordPress site
 ~^((?<subdomain>.*)\.)(?<domain>[^.]+)\.(?<tld>[^.]+)$ $domain.$tld;
 }
In the above block, we

map from $ssl_server_name (which is an nginx variable that is always available)
to $ssl_domain_name (a new nginx variable of our own creation)
with a small regular expression that splits $ssl_server_name up into 3 separate variables, but only returns 2 of them: domain and tld
The volatile and hostnames directives are optional, and only exist as speed-ups to hint to nginx what the value might be
I’m able to do this because I know that every single website I’m serving will be in that format, with $subdomain being optional.

Then, when it comes time to configure nginx to look for the SSL certificates:

ssl_certificate           /etc/letsencrypt/live/$ssl_domain_name/fullchain.pem;
ssl_certificate_key       /etc/letsencrypt/live/$ssl_domain_name/privkey.pem;
A sample nginx.conf might look something like this:

user                root; # or www-data if you can figure out perms
error_log           /var/log/nginx/error.log crit;
pid                 /var/run/nginx.pid;
worker_processes    auto;

events {
    worker_connections 1024;
    multi_accept on;
    use epoll;
}

http {

    # Get your SSL domain.tld
    map $ssl_server_name $ssl_domain_name {
        volatile;
        hostnames;
        default 'example.org';
       ~^((<subdomain>?.*).)(?<domain>[^.]+).(?<tld>[^.]+)$ $domain.$tld;
    }

    server {
        listen                   80;
        listen                   [::]:80;
        limit_req                zone=http burst=5;
        server_name              _;
        server_name_in_redirect  off;
        return                   301 https://$host$request_uri;
    }

    server {
        listen                   443 ssl http2;
        listen                   [::]:443 ssl http2;

        # Cert locations
        ssl_certificate      /etc/letsencrypt/live/$ssl_domain_name/fullchain.pem;
        ssl_certificate_key  /etc/letsencrypt/live/$ssl_domain_name/privkey.pem;

        # Tons of other things will go here
    }

    # Site specific things
    include /etc/nginx/sites-enabled/*;

    # Tons of other things (location blocks, etc...) will go here
}
I’m working on publishing by advanced WordPress/LetsEncrypt/nginx configuration to a Git repository, and will link to that here as soon as it’s cleaned up and ready to go.
--------------------

nginx多https证书配置精简
7月 27日, 2020年
对于nginx多https站点通常的做法是这样：

server 443 ssl;
server_name nixops1.me;
ssl_certificate     /usr/local/nginx/keys/nixops1.me.crt;
ssl_certificate_key /usr/local/nginx/keys/nixops1.me.key;
......
如果有多个https站点，就是每个站点复制一份这个配置。如nixops2.me、nixops3.me等等，在只有几十个https站点的时候还好，如果某个站点的域名特别多，例如有几千个域名甚至上万个域名的时候，这个配置就很恐怖了，需要精简并合并配置。

下面举例说明，假设应用场景为：

同一站点有大量域名需要配置https证书，其它配置一样
每个之证书都是泛域名证书，如let's encrypt的wildcard证书
所有证书命名均为公钥domain.com.crt、私钥为domain.com.key 这样的格式
所有证书保存在相同目录中，如/usr/local/nginx/conf/keys/
具体做法可以用nginx的map和正则表达式，根据ssl_server_name（即https的访问域名），获取公钥和私钥的完整路径做为变量。配置证书时使用该变量即可。先来看nginx配置文件中HTTP段的map配置：

map $ssl_server_name $NixopsCert {
    default /usr/local/nginx/conf/keys/nixops.me.crt; #指定一个默认的证书
    ~*^(.+\.)*([^\.]+\.[^\.]+)$ /usr/local/nginx/conf/keys/$2.crt;
}

map $ssl_server_name $NixopsKey {
    default /usr/local/nginx/conf/keys/nixops.me.key;
    ~*^(.+\.)*([^\.]+\.[^\.]+)$ /usr/local/nginx/conf/keys/$2.key;
}
这样配置好后，如果通过https访问nixops1.me或*.nixops1.me，获取到的路径分别为：

$NixopsCert  --> /usr/local/nginx/conf/keys/nixops1.me.crt;
$NixopsKey   --> /usr/local/nginx/conf/keys/nixops1.me.key;
然后配置server段证书公钥和私钥时就不需要按传统作法复制配置，只需指定变量即可：

server {
    listen  80;
    listen  443 ssl http2;
    server_name
        nixops.me
        nixops1.me
        nixops2.me
        #......
    ;

    ssl_certificate     $NixopsCert;
    ssl_certificate_key $NixopsKey;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
   #其它server段配置
}
这样同一站点有多个证书时，就可以只在server_name里添加，不需要指定https配置了，和http站点添加域名绑定非常类似。