Multiple Let's Encrypt domains in a single Nginx server block

Nginx is a fantastic web server and reverse proxy to use with Let’s Encrypt, but when dealing with multiple domains it can be a bit tedious to configure. I have been moving services into more FreeBSD jails as I alluded to in my previous post, among them the general Nginx proxy jail which I have serving my HTTP-based services. Using Let’s Encrypt for TLS, I found myself declaring multiple server blocks inside my virtual host configurations to handle the apex domain (e.g. dotdotvote.com), the www subdomain, and vanity domains (e.g. dotdot.vote). With the help Membear and MTecknology in the #nginx channel on Freenode, I was able to refactor multiple largely redundant server blocks into one.

With Nginx 1.15.9 a feature I have seen referred to as “dynamic certificates” was released. Originally the ssl_certificate and ssl_certificate_key were loaded when Nginx started. This meant that you could not refer to any of the Nginx variables when creating the setting. With dynamic certificates, the resolution of the ssl_certificate directive is done later by the worker(s) process(es). It’s a very handy feature!

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name dotdotvote.com www.dotdotvote.com dotdot.vote;
  ##
  # Certificates
  ssl_certificate     /usr/local/etc/letsencrypt/live/$ssl_server_name/fullchain.pem;
  ssl_certificate_key /usr/local/etc/letsencrypt/live/$ssl_server_name/privkey.pem;

  # ... snip ...
  location / {
    # ... snip ...
    proxy_pass http://dotdotvote;
  }
}

In the example above, I’m using the $ssl_server_name variable which will correspond to the server name requested in the SNI part of the TLS payload. This ensures that the right hostname’s certificate is utilized.

My first attempt was with $server_name, which I recommend you avoid using $server_name since that will not be computed per request. For example, in the block below the $server_name variable will always be dotdotvote.com and requests served on www.dotdotvote.com will use the incorrect certificate.:

server {
    server_name dotdotvote.com www.dotdotvote.com;

    ssl_certificate '/some/path/$server_name/fullchain.pem';
}

When I was originally setting this up, I also stumbled into some “Permission denied” errors from Nginx. With the static certificate declaration, the main Nginx process would load the file. That process would run as root before dropping privileges to the www user for the Nginx worker(s). To address this I needed to go change filesystem ownership in order for the www user to properly read the certificate files.


In retrospect, this feature seems relatively simple to use if you have a good understanding of the Nginx process and permissions model. Suffice it to say, I wouldn’t have figured this out without a bit of help from the folks in #nginx. With the change in place my Nginx configurations are now much more succinct and readable!