SSL
Notes on TLS certificates: local development, the system trust store, Let's Encrypt with Certbot, and a hands-on OpenSSL CA walk-through. For the Linux Certbot workflow see Certbot on Linux; for a real signing service see Security.
Development certificates
mkcert makes locally-trusted development certificates with no configuration.
mkcert example.com "*.example.com" example.test localhost 127.0.0.1 ::1
VS Code Live Server:
"liveServer.settings.donotVerifyTags": true,
"liveServer.settings.donotShowInfoMsg": true,
"liveServer.settings.https": {
"enable": true, // enable the feature
"cert": "/Users/gonsakon/localhost.pem", // full path
"key": "/Users/gonsakon/localhost-key.pem" // full path
}
System trust store
certmgr.msc
# List root CAs
Get-ChildItem Cert:\LocalMachine\Root
# List personal (my) certificates
certutil -store my
Certbot
Install
# Ubuntu 22.04
sudo apt-get install certbot
# Ubuntu 20.04
sudo apt-get install letsencrypt
sudo apt-get install python3-certbot-nginx
# Remove certbot-auto and any Certbot OS packages
sudo apt-get remove certbot
Via snap:
# Install snapd
sudo apt install snapd
sudo snap install core; sudo snap refresh core
# Install Certbot
sudo snap install --classic certbot
# Prepare the certbot command
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Configure
# Default path
/etc/letsencrypt/
# List certificates
sudo certbot certificates
# Certificate only; -d supports multiple domains
certbot \
certonly \
--webroot \
--webroot-path /var/www/letsencrypt \
--agree-tos \
--email [email protected] \
-d domain.com -d www.domain.com -d sub.domain.com
# Response:
# Successfully received certificate.
# Certificate is saved at: /etc/letsencrypt/live/domain.com/fullchain.pem
# Key is saved at: /etc/letsencrypt/live/domain.com/privkey.pem
# Auto-configure nginx
sudo certbot --nginx -d domain.com
# Check the certificate name
ls /etc/letsencrypt/renewal
# If there are -0001, -0002 ... delete them all and run certbot again.
sudo certbot delete --cert-name $CERTIFICATE_NAME
Convert the keys to PKCS#12 (for a Java keystore, for example):
openssl pkcs12 -export -in fullchain.pem \
-inkey privkey.pem \
-out keystore.p12 \
-name tomcat \
-CAfile chain.pem \
-caname root
If a Spring Boot application already loaded the previous keystore.p12 into
memory, restart it to pick up the new one.
Auto-renew
# Check that certbot.timer exists
systemctl list-timers
# Timer config
vi /etc/systemd/system/timers.target.wants/certbot.timer
# From snap
vi /etc/systemd/system/timers.target.wants/snap.certbot.renew.timer
Reload nginx after renewal with a post hook:
vi /etc/letsencrypt/renewal-hooks/post/01-restart-nginx
chmod +x /etc/letsencrypt/renewal-hooks/post/01-restart-nginx
#! /bin/sh
set -e # exit the script if an error happens
sudo service nginx reload
Maintain
If Let's Encrypt auto-renew is not working on a systemd-based Debian/Ubuntu:
# Dry run
certbot renew --dry-run
# Renew timers
sudo systemctl status snap.certbot.renew.timer # snap
sudo systemctl status certbot.timer
# List all systemd timers
systemctl list-timers --all
systemctl list-units | grep certbot.renew
find / -name certbot
# Verify these files exist
ls /lib/systemd/system/certbot.service
ls /lib/systemd/system/certbot.timer
ls /etc/cron.d/certbot
# Log
/var/log/letsencrypt/letsencrypt.log
Troubleshooting
Challenge failed for domain
Occurs when the domain's DNS records were removed. Remove the old certificate and create a new one, remembering to omit the removed domains. After a successful creation, the browser may still show "Not secure" — clear the cache or try another browser.
Chrome "Active content with certificate errors"
To clear this without restarting:
- Open developer tools.
- Go to the Application tab.
- Clear storage.
- Close and reopen the tab.
Also noted by others:
- Close all tabs in all windows open to the same domain (including subdomains).
- Uncheck "cookies" before clearing storage to preserve login information.
Too many failed authorizations recently
Delete everything under /etc/letsencrypt/accounts.
Snakeoil
ssl-snakeoil.key is created by the ssl-cert package's post-install scripts
for the snakeoil user and should not be deleted.
sudo apt-get install ssl-cert
sudo make-ssl-cert generate-default-snakeoil
sudo usermod --append --groups ssl-cert yyuu
ls -l /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/ssl/private/ssl-cert-snakeoil.key
OpenSSL walk-through
A training exercise that builds a root CA, an intermediate CA, and an end-entity certificate. In production, use certificates from a trusted third-party CA.
Generate a root certificate
# 2048-bit RSA private key for the root CA
openssl genrsa -out rootCA.key 2048
# Self-signed root CA certificate, valid for 1024 days.
# Prompts for country code, organization name, etc.
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem
Create an intermediate CA
openssl genrsa -out intermediateCA.key 2048
# CSR for the intermediate CA. Make some fields (such as the organization name)
# different from the root CA.
openssl req -new -key intermediateCA.key -out intermediateCA.csr
# Sign the intermediate CSR with the root CA
openssl x509 -req -in intermediateCA.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out intermediateCA.crt -days 500 -sha256
Create an end-entity certificate
For example, a website's TLS certificate:
openssl genrsa -out server.key 2048
# CSR — set the Common Name to your website address
openssl req -new -key server.key -out server.csr
# Sign the CSR
openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 500 -sha256
Verify:
# server.crt: OK
openssl verify -CAfile rootCA.pem -untrusted intermediateCA.crt server.crt
Install and use the certificates
Depending on your server (Apache, nginx, etc.), install the generated certificate and key in the appropriate location and configure the server to use them for HTTPS.
Add the CAs to the trust store
Add rootCA.pem and intermediateCA.pem to your system or browser trust store
so end-entity certificates signed by them are trusted.
Notes:
- Keep private keys secure and never expose them.
error 18 at 0 depth lookup: self signed certificate— do not reuse the same Common Name (CN) across the chain.
Encrypt a file
echo "Hello World" | openssl enc -aes-256-cbc -a -salt -pbkdf2 -iter 10000 -pass pass:pa55word > doc
cat doc | openssl enc -aes-256-cbc -d -a -pbkdf2 -iter 10000 -pass pass:pa55word
Self-signed certificate with SAN
# Linux only; Windows does not support process substitution `<(...)`
openssl req -x509 -out localhost.crt -keyout localhost.key \
-newkey rsa:2048 -nodes -sha256 \
-subj '/CN=localhost' -extensions EXT -config <( \
printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")