TLDR: In this post I'm going to detail an optimal NGINX webserver configuration for multiple subdomains and wildcard subdomains using a single server block and a single Let's Encrypt SSL certificate.
Background
NGINX is the leading web server application powering more than 50% of the busiest sites on the internet. It's highly configurable and capable of handling thousands of concurrent requests.
Let's Encrypt is a free, automated SSL certificate service and certificate authority.
This post is technical and aimed at the DevOps community. I'm assuming that you're familiar with configuring NGINX, installing SSL certificates, and the command line.
Why this Post
Recently, I wanted to serve a new web application at https://app.mezosphere.com. I also wanted a related (but separate) micro-site at https://www.mezosphere.com, and it took some research to arrive at an optimal solution. The challenge was documentation outlining outdated solutions (including on Stack Overflow).
What I'm documenting here is:
- A single Let's Encrypt SSL certificate for mezosphere.com, www.mezosphere.com, & app.mezosphere.com.
- a single NGINX server block for mezosphere.com, www.mezosphere.com, & app.mezosphere.com referencing that single SSL certificate.
I consider this an optimal solution because it's 'DRY.' The domain & its subdomains share a single NGINX configuration block for a single SSL certificate making maintenance and automated renewal easier.
Things you should know beforehand:
- The "www" in "www.mezosphere.com" is a subdomain (like app.mezosphere.com). With a few caveats, mezosphere.com can be considered a separate domain - the root domain.
- NGINX has a master configuration file named "nginx.conf". This master file typically references other configuration files (typically one per virtual domain).
- Let's Encrypt SSL certificates are typically installed and renewed automatically using their 'certbot' (a certificate management agent running on the web server). This agent supports and uses NGINX to install SSL certificates.
- For various reasons, it's generally (but loosely) considered good practice to use the www subdomain for the main site (not the root domain). So the root domain will usually redirect to the www subdomain.
NGINX Configuration
I will assume that NGINX is already installed and that the main NGINX configuration file "nginx.conf" is already configured (probably to include separate configuration files for each virtual domain).
In this post, I'm focusing on configuring only the mezosphere.com domain and its subdomains.
Since we're strictly enforcing secure HTTPS, I'm going to start by redirecting all HTTP (insecure traffic on port 80) to HTTPS (secure):
# HTTP server block (redirect HTTP-->HTTPS)
server{
server_name .mezosphere.com; # special syntax to match mezosphere.com AND it's subdomains
listen 80; # IPv4
listen [::]:80; # IPv6
return 301 $https://$host$request_uri; # permanent redirect to HTTPS (whilst maintaining the original url)
}
Here's the NGINX config that I want to highlight:
# HTTPS server block for mezosphere.com, www.mezosphere.com & app.mezosphere.com
server {
server_name ~^(?[^.]*)\.?mezosphere.com$;
root /var/www/mezosphere.com/web/public/$subdomain;
...
It uses a regular expression to match the domain and its subdomains and captures the subdomain name into a variable: $subdomain. $subdomain is then used to specify separate "root" locations for each subdomain (for the location of the root index.html, among other files).
It's useful for configuring multiple subdomains requiring a similar configuration but a separate public folder. The alternative is to set up individual server blocks for each subdomain.
In our case we don't use the root domain "mezosphere.com," and so we want to redirect it to www.mezosphere.com:
# redirect mezosphere.com --> www.mezosphere.com
if ($subdomain = '') {
return 301 https://www.mezosphere.com$request_uri;
}
Some people say that if blocks are evil. But I'm using an if block to do this redirection inside the HTTPS server block (so that we don't have to repeat the SSL configuration in a separate server block).
Finally, after you're done configuring NGINX and SSL you can test what you've done using:
> sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
SSL certificate for multiple subdomains
If you want certbot to automatically update your NGINX configuration, make sure that your NGINX configuration is working for the subdomains. You may need to temporarily disable SSL and listening on port 443 in your NGINX configuration file.
Command certbot to create a single certificate for the root domain and 2 specific subdomains. Note: remove the --dry-run option after testing.
> sudo certbot certonly -d mezosphere.com -d www.mezosphere.com -d app.mezosphere.com --dry-run
This command is capable of updating your NGINX configuration and will prompt you if you don't specify all the options upfront.
Wildcard SSL certificate
Prerequisites
1. Create the subdomain wildcard entries in your DNS control panel using A/AAA or a CNAME:
2. You'll need to install Let's Encrypt SSL agent (certbot) for your specific webserver and OS before tackling this section. Follow certbot's instructions for wildcard certificates.
3. Install certbot's DNS plugin for your DNS provider (certbot now requires DNS verification for wildcard SSL certs).
4. Set up DNS access per the certbot instructions for your DNS provider (e.g. Cloudflare, Google, AWS Route53 etc.). This typically involves setting up a security policy and acquiring and installing access keys (your DNS provider will not let anybody add and remove DNS records remotely).
Create the wildcard certificate
Wildcard SSL certificates follow a slightly different method involving DNS verification. This is how we request a single certificate for the wildcard subdomain and the root domain (e.g. for the AWS Route53 DNS service):
> sudo certbot certonly -i nginx --dns-route53 -d *.backoffice.zone -d backoffice.zone --dry-run
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for *.backoffice.zone
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/backoffice.zone/fullchain.pem
Key is saved at: /etc/letsencrypt/live/backoffice.zone/privkey.pem
This certificate expires on 2022-06-17.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Use the --dry-run option to test it first (highly recommended because letsencrypt is rate limited). Watch the output and copy the paths to the new certificates (as Certbot will not amend the NGINX configuration in this case).
Edit your NGINX configuration to enable SSL and listen on port 443. Copy/paste the above certificate paths into the configuration file:
...
listen 443 ssl http2; # IPv4
listen [::]:443 ssl http2; # IPv6
ssl_certificate /etc/letsencrypt/live/mezosphere.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mezosphere.com/privkey.pem;
...
Finally, test your updated NGINX configuration and restart NGINX (e.g. for Ubuntu):
> sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
> sudo systemctl restart nginx
Watch (or 'tail') your NGINX log file for logs that could help you to debug specific issues while testing your subdomains requests/redirection in your browser address bar.
Click here to comment...