SSL Certificates with DNS Wild Wild Card
Priming
This configuration guide was tested on Raspberry Pi OS (64-bit), but should be compatible with any Debian-based Linux.
For serving multi-tenant self-hosted applications with a single SSL certificate one might use a DNS provider that supports the following features:
- Dynamic DNS
- Wildcard Subdomain Addresses
- DNS-01 challenge API supported by Lego
Dynamic DNS configuration process has been described in Dynamic DNS article.
Wildcard Subdomain Address and DNS-01 challenge configurations are closely related subjects. Therefore their configurations are both described in the current article.
However Google Domains is used here as an example, for the new deployments Google Domains may not be used as it will no longer offer new domain registrations.
NGINX is used as a reverse proxy and as SSL
termination.
LEGO is used for obtaining SSL certificates
and keys.
systemd timer
is used to automate SSL certificates and keys renewals before they expire.
For simplicity it is assumed that all software are running on the same host (Raspberry Pi 4 in my case).
Mock web-server and error page
For the purpose of this article, it is assumed that that the host is already running a web-server such as Next.js at the following address:
It is also assumed that there is a static HTML page on the host file system that holds page not found error. For example:
/home/rh/artizanya-host/packages/wui-errors/out/404.html
(I might write another article on setting-up Next.js 14 as a web server and as a static pages generator if there is a need for such guidelines.)
Configuring Router Port Forwarding
The configuration of port forwarding depends on the Internet provider and the
router in use. In this article, it is assumed that ports 80
and 443
are
forwarded to the corresponding ports at the internal IP address running the
NGINX server.
Installing and Configuring LEGO
Refer to your DNS provider documentation and to LEGO DNS Providers documentation to configure wildcard subdomain addresses and to obtain API key for DNS-01 challenge.
At the time of writing, LEGO is relatively new, and the versions included with most Linux distributions are not up-to-date with the current release. The best way to obtain the latest version is to compile it from the source. LEGO is written in Golang and requires a fairly recent version of Golang, which might not be available in a Linux distribution.
To compile LEGO from the source, Golang can be installed from its original binary distribution by following the official instructions.
With Golang installed, LEGO can be built and installed using the following command:
$ # go1.17+
$ # environment variable: GO111MODULE=on
$ go install github.com/go-acme/lego/v4/cmd/lego@latest
Getting SSL Keys and Certificates Using LEGO
The following command is an example of how LEGO may be used for DNS-01 challenge and wildcard subdomain:
$ # environment variable: GOOGLE_DOMAINS_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$ cd /home/rh/artizanya-host
$ lego --accept-tos \
--dns=googledomains \
--domains='artizanya.com' \
--domains='*.artizanya.com' \
--email='[email protected]' \
run
Getting NGINX files required for Let's Encrypt SSL
The following commands could be used to obtain NGINX files required for Let's Encrypt SSL:
$ cd /home/rh/artizanya-host
$ mkdir -pv nginx/letsencrypt && cd nginx/letsencrypt
$ wget --backups=1 https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf
$ wget --backups=1 https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem
Installing and Configuring NGINX
NGINX configuration file for artizanya.com
:
# /home/rh/artizanya-host/nginx/sites-available/artizanya.com
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name *.artizanya.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_certificate /home/rh/artizanya-host/.lego/certificates/artizanya.com.crt;
ssl_certificate_key /home/rh/artizanya-host/.lego/certificates/artizanya.com.key;
ssl_trusted_certificate /home/rh/artizanya-host/.lego/certificates/artizanya.com.issuer.crt;
include /home/rh/artizanya-host/nginx/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /home/rh/artizanya-host/nginx/letsencrypt/ssl-dhparams.pem;
server_name *.artizanya.com;
root /home/rh/artizanya-host/packages/wui-errors/out;
index 404.html;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /home/rh/artizanya-host/.lego/certificates/artizanya.com.crt;
ssl_certificate_key /home/rh/artizanya-host/.lego/certificates/artizanya.com.key;
ssl_trusted_certificate /home/rh/artizanya-host/.lego/certificates/artizanya.com.issuer.crt;
include /home/rh/artizanya-host/nginx/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /home/rh/artizanya-host/nginx/letsencrypt/ssl-dhparams.pem;
server_name artizanya.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
To enable above NGINX configuration:
$ ln -s /home/rh/artizanya-host/nginx/sites-available/artizanya.com /etc/nginx/sites-enabled
The following command can be used to verify NGINX configuration:
# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
To reload NGINX after configuration change:
# systemctl reload nginx
To view NGINX available output:
$ journalctl -u dns01_challenge.service
To follow NGINX current output:
$ journalctl --no-pager --follow -u nginx
systemd LEGO auto-update timer
The following bash, service and timer files are required to execute LEGO on systemd timer:
# /home/rh/artizanya-host/utils/dns01_challenge.sh
cd ..
ACTION=run
if [ -f .lego/certificates/artizanya.com.crt ]; then
ACTION=renew
fi
lego --accept-tos \
--dns=googledomains \
--domains='artizanya.com' \
--domains='*.artizanya.com' \
--email='[email protected]' \
"${ACTION}"
systemctl reload nginx
# /home/rh/artizanya-host/systemd/dns01_challenge.timer
[Unit]
Description=Run Artizanya dns-01_challenge.service every 2 months
[Timer]
Unit=dns01_challenge.service
# Run on the 7th day of every odd month
OnCalendar=*-01,03,05,07,09,11-07 00:00:00 UTC
# or run every 2 months
# OnCalendar=*-*-1/2 00:00:00 UTC
Persistent=true
[Install]
WantedBy=timers.target
# /home/rh/artizanya-host/systemd/dns01_challenge.service
[Unit]
Description=Execute dns01_challenge.sh script
[Service]
Type=oneshot
WorkingDirectory=/home/rh/artizanya-host
EnvironmentFile=/home/rh/artizanya-host/.env
Environment=PATH=/home/rh/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=/home/rh/artizanya-host/utils/dns01_challenge.sh
.env
file referred by service option EnvironmentFile
should contain
environment variable GOOGLE_DOMAINS_ACCESS_TOKEN
(see LEGO DNS Providers
documentation for the environment
variables required by LEGO for your DNS provider).
To enable above service and timer:
# systemctl enable /home/rh/artizanya-host/systemd/dns01_challenge.service
# systemctl enable --now /home/rh/artizanya-host/systemd/dns01_challenge.timer
To make timer targets to wait until real time is initialised on systems with no real time clock (such as Raspberry Pi 4):
# systemctl enable --now systemd-time-wait-sync.service
To reload systemd after changing enabled service and timer files:
# systemctl daemon-reload
DANE TLSA records
To print TLSA record for the current TSL certificate:
$ printf '_25._tcp.%s. IN TLSA 3 1 1 %s\n' artizanya.com \
(openssl x509 -in /home/rh/artizanya-host/.lego/certificates/artizanya.com.crt \
-noout -pubkey | \
openssl pkey -pubin -outform DER | \
openssl dgst -sha256 -binary | \
hexdump -ve '/1 "%02x"')
The output of the above command should be entered to DNS records (I do not know how to automate this step yet).
- lego-tlsa by Pauline Middelink
- Check a DANE TLS Service by Shumon Huque
- DANE TLSA record verifier by SIDN Labs
- DANE SMTP validator by sys4 etc.
References
- Using Nginx As a Reverse Proxy With SSL (2023-03-02) Mike Slinn
- Creating and Renewing Letsencrypt Wildcard SSL Certificates (2023-03-02) Mike Slinn
- Letsencrypt/ACME Wildcard SSL Certificates by Lego (2023-06-07) Mike Slinn
- Generating options-ssl-nginx.conf and ssl-dhparams in certonly mode (2020-10) Wilson29thID
- Chain of Trust (2021-10-02) Let's Encrypt
- Use the ACME DNS-Challenge to get a TLS certificate (2020-04-13) Marco Franssen