To generate valid SSL certificates, I use a free open source service provided by Let’s Encrypt.
These certificates have a validity period of 90 days, but they can be renewed. The only thing you need to be wary of are
their rate limits. I suggest you test using the option parameter
--dry-run to test for validation checks, omitting this when you need to generate the actual certificate to use on your
server. The prerequisite for validation is that the domain you want to use must be publicly reachable. There are
multiple validation methods, but I'll be using DNS as it's the most
clean and easy method (nothing to create and clean up afterwards).
If you don't have your own domain, you could use a free service like Heroku with http validation.
For this guide, it is assumed you are using a development environment that is UNIX/Linux based. If your setup is different, there are other clients that may be compatible with your system. My development environment is...
Windows Subsystem for Linux (WSL)
Ubuntu 18.04 Distribution
First step is to install certbot, so start up your favourite shell and enter the following commands:
$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository universe
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install certbot python3-certbot-dns-cloudflareOn the last command, python3-certbot-dns-cloudflare is the DNS plug-in name. My choice is to use Cloudflare, but you
could use any of the other supported DNS Plugins.
For a production environment, you'll most likely want a single certificate for each site due to security risks. For
development, it makes more sense to have a 'catch-all' domain so we can use the same certificate for all our development
sites. I'd suggest something like, dev.yourdomain.com or local.yourdomain.com. For our local sites, we then set them
up as sub-domains and modify our hosts file appropriately to point to 127.0.0.1.
Create a text file to store your account email and API key and make sure the permissions of the file are something like
600 root:root to prevent access from prying eyes. The file should contain something like:
dns_cloudflare_email = "your.email@example.com"
dns_cloudflare_api_key = "your-api-key"Run certbot commands with
-qflag to suppress output, and-nfor non-interactive mode
$ sudo certbot certonly -d "dev.example.com,*.dev.example.com" --dns-cloudflare --dns-cloudflare-credentials <path_to_file>/cloudflare.ini --dns-cloudflare-propagation-seconds 10The above command can also be used to renew certificates for specific domains. Generated certificate files are not
physically in the live directory as the letscrypt output suggests - these are sym links to actual files in 'archive'
directory. The files we are interested in for our site(s) are cert and privkey pem files. These correlate to
<domain>.crt and <domain>.key respectively. On a production system, you should also implement the chain file for
OCSP Stapling.
$ sudo certbot renew --dns-cloudflare --dns-cloudflare-credentials <path_to_file>/cloudflare.ini --dns-cloudflare-propagation-seconds 10The Cloudflare dns authentication automatically adds/removes the required dns entries for us, but it is also possible to use shell scripts as hooks to have a custom process (useful for custom installation of ssl certificates on webserver). See the Certbot Documentation for more details on this.
It may be helpful to set up the renew command with -n (and suppress output with -r if you don't care) in a cron job to keep your certificates up to date.
Here is a helpful shell script you can use to update certificates for your domain (including sub-domains) and automatically rename/copy them to the source script's directory.
#!/bin/bash
AUTHFILE=cloudflare.ini
DOMAIN='dev.example.com'
DOMAINS="${DOMAIN},*.${DOMAIN}"
# Optionally, set your docker NginX certs directory (without trailing '/') below to also copy the generated certs there
NGINX_CERTDIR=''
# Color vars
_RST_='\033[0m' #reset
_BLD='\033[1m' #bold
_UND='\033[4m' #underline
# Normal Colors
Black='\033[38;5;0m'
Red='\033[38;5;1m'
Green='\033[38;5;2m'
Yellow='\033[38;5;3m'
Blue='\033[38;5;4m'
Magenta='\033[38;5;5m'
Cyan='\033[38;5;6m'
White='\033[38;5;7m'
# High Intensty
IBlack='\033[38;5;8m'
IRed='\033[38;5;9m'
IGreen='\033[38;5;10m'
IYellow='\033[38;5;11m'
IBlue='\033[38;5;12m'
IMagenta='\033[38;5;13m'
ICyan='\033[38;5;14m'
IWhite='\033[38;5;15m'
# Check for SUDO for access to generated certs
if [[ $(id -un) != 'root' ]]; then
echo -e "${_BLD}${Red}Error${_RST_}: require ${_BLD}sudo${_RST_} permissions - run: ${_BLD}sudo $0${_RST_}"
exit 1
fi
# Set working directory is the location of this file to ensure relative file paths are correct
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
cd $DIR
# Check for authentication details
if [[ ! -f ./${AUTHFILE} ]]; then
echo -e "${_BLD}${Red}Error${_RST_}: ${_BLD}${AUTHFILE}${_RST_} not found in ${_BLD}${DIR}${_RST_}"
fi
certbot certonly -d ${DOMAINS} --dns-cloudflare --dns-cloudflare-credentials ./${AUTHFILE} --dns-cloudflare-propagation-seconds 10
OUTPUT_PATH="/etc/letsencrypt/live/${DOMAIN}"
USER=$SUDO_USER
USER_GROUP=$(id -gn $USER)
if [[ $? == 0 ]]; then
cp $OUTPUT_PATH/cert.pem $DIR/$DOMAIN.crt
cp $OUTPUT_PATH/privkey.pem $DIR/$DOMAIN.key
chmod 400 $DIR/$DOMAIN.crt
chmod 400 $DIR/$DOMAIN.key
chown $USER:$USER_GROUP $DIR/$DOMAIN.crt
chown $USER:$USER_GROUP $DIR/$DOMAIN.key
echo -e "Certificates generated successfully and copied to ${_BLD}${DIR}${_RST_}"
fi
# Optional
if [[ ! -z "$NGINX_CERTDIR" ]] && [[ -d "$NGINX_CERTDIR" ]]; then
cp -f $DIR/$DOMAIN.crt $NGINX_CERTDIR
cp -f $DIR/$DOMAIN.key $NGINX_CERTDIR
echo -e "Certificates copied to ${_BLD}${NGINX_CERTDIR}${_RST_}"
fiUsing the script above, we can configure this to run when the certificates are older than 60 days, (normal renewals are permitted once expiry is within 30 days). Modify the commands with the appropriate file paths to existing cert and the cert renewal script.
If you want to set this up as a server cron job (root user) then:
$ echo -e "#Renew Certs if older than 60 days\n0 1 * * * test \$(( (\$(date +\%s) - \$(date -r path_to.crt +\%s) + 43200)/86400 )) -gt 60 && path_to_renew_scipt\n" >> /var/spool/cron/rootAlternative, you can add it to your shell startup script (.zshrc):
echo -e "\n#Renew Certs if older than 60 days" >> ~/.zshrc
echo -e "test \$(( (\$(date +\%s) - \$(date -r path_to.crt +\%s) + 43200)/86400 )) -gt 60 && echo 'sudo permissions required to renew SSL certificates...'" >> ~/.zshrc
echo -e "test \$(( (\$(date +\%s) - \$(date -r path_to.crt +\%s) + 43200)/86400 )) -gt 60 && sudo path_to_renew_scipt" >> ~/.zshrc