Let's Encrypt Wildcard Cert

I'm writing this more as documentation for myself as I have several domains which I often use as playgrounds for certain things. This could be a kubernetes environment or a docker cluster where I have several applications deployed, but don't want to set up an nginx site for each one of the apps that I deploy.

Recently, I found out about Portainer for managing a docker environment. Think of this as a kubernetes-lite. This isn't good for any kind of production use case, but does have some benefits for at home management of a local docker instance.

So in this illustration, we have a request coming in on 443. This request could be using the domain name c1.mydomain.com and there could be another request coming in from c2.mydomain.com.

Previously, you would have a certificate for both subdomains and would need to set up the appropriate port forwarding for each subdomain to the appropriate server. This is a huge pain, so, instead, this is where a wildcard certificate can come in handy.

In this particular scenario, I have an nginx proxy which will be used to route all of the traffic coming in to my network. This is handy because I can use this reverse proxy for SSL termination and also routing the traffic to various servers based on the domain name. This doesn't have to be a powerful server. You could even use a Raspberry Pi to route this traffic. Again, this is for a home server and not really production grade.

You will need some prerequisites. And I also use CloudFlare's DNS for handling these wildcard domains. This is nice because certbot and cloudflare play pretty nicely to automatically verify the challenges via their API.

apt install certbot letsencrypt python3-certbot-dns-cloudflare

If you already have certbot and the necessary extensions installed, you can simply run this script to get a wildcard certificate.

sudo certbot certonly \
     --cert-name mydomain.com \
     --dns-cloudflare \
     --dns-cloudflare-credentials /etc/letsencrypt/cloudflareapi.cfg \
     --server https://acme-v02.api.letsencrypt.org/directory \
     -d "*.mydomain.com" \
     -d mydomain.com

So, now we can configure our nginx proxy to take any request from this domain and forward it over to my docker host.

server {
  server_name *.mydomain.com;
  server_name ~^(?<subdomain>.+)\.mydomain\.com$;
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  # proxy_next_upstream error timeout http_502;
  location / {
    proxy_pass http://DOCKER_HOST_IP_ADDRESS;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_buffering off;
  listen 443 ssl;
  ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

server {
  return 301 https://$host$request_uri;
  listen 80 ;
  listen [::]:80 ;
  server_name *.mydomain.com;
  server_name ~^(?<subdomain>.+)\.mydomain\.com$;

In another blog article, I'll describe how I set up the Docker host to take in these requests from the nginx server.