The free SSL certificate authority Let's Encrypt went into public beta earlier this month, and I updated all of my sites to use SSL now. I still had several more months before kirsle.net's old certificate from Namecheap expired, but I switched to the Let's Encrypt certificate because I could include all my subdomains instead of only the www
one.
Check out their website and get free SSL certificates for your sites, too. I'm just writing this blog with some personal tips for how I configured my nginx server and a Python script I wrote to automate the process of renewing my certificates every month (Let's Encrypt certs expire every 90 days).
Because all of my sites now use SSL encryption, and nginx has a handful of options for configuring the cipher suites and other settings (you need to play around with these to get a good score on the SSL Labs security test), I created a file named /etc/nginx/ssl_params
to put all of the common parameters. Then, each individual site includes that file.
/etc/nginx/ssl_params
# Common SSL security settings
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_dhparam /etc/ssl/kirsle.net-2015/dhparams.pem;
# So the Acme client can use the htdocs method
location /.well-known {
alias /var/www/html/.well-known;
}
Notice how I also made a global alias to point the /.well-known
URI to the /var/www/html/.well-known
path. This is useful for running the ACME client (I use the standard letsencrypt-auto
client); I can use the "web root" method and then I don't need to stop my nginx web server (I don't trust a program to automatically edit my nginx config, and I don't want to bring down all my sites in order to run letsencrypt-auto
in stand-alone mode, where it runs its own server temporarily).
And then, for my websites that use SSL certs, I include the ssl_params
file so that I don't need to manage redundant settings in a dozen different places. Here's an example for my rpm.kirsle.net subdomain, which is where I put random Linux packages:
server {
server_name rpm.kirsle.net;
listen [2604:a880:1:20::46:5004]:443 ipv6only=on;
listen 443 ssl;
ssl on;
ssl_certificate /etc/letsencrypt/live/www.kirsle.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.kirsle.net/privkey.pem;
include ssl_params;
root /home/kirsle/subdomains/rpm;
index index.html;
location / {
try_files $uri $uri/ /index.html;
autoindex on;
}
}
# Forward port 80 to SSL
server {
server_name rpm.kirsle.net;
listen [2604:a880:1:20::46:5004]:80 ipv6only=on;
listen 80;
return 301 https://rpm.kirsle.net$request_uri;
}
As touched on before, I didn't want to have to do service nginx stop
in order to run the letsencrypt-auto
client in standalone mode. (The reason I would have to is that many of my sites are Python web apps, so they don't have a traditional document root directory like some simpler sites would, such as many sites that would be hosted by an Apache server).
So for example, on Kirsle.net the majority of requests are handled directly by the Python app, so there is no root document directory (I make aliases for certain subdirectories, though, for serving static files because this is better handled by nginx than by Python). So, a workaround is to configure your nginx server to alias the /.well-known
URI to a document root on disk, because the ACME client will want to create files underneath the /.well-known/acme-challenge/
directory to prove ownership of your domain name.
To keep things simple and DRY, I put that URI alias in the shared SSL parameters file.
Let's Encrypt is still in beta at the time of writing, and they haven't yet implemented a way to automatically renew certificates before they expire; and Let's Encrypt certificates expire every 90 days, unlike most traditional certificate authorities that give you 12 month leases at a time.
So in the mean time, I wrote a simple Python script for myself that renews all my certificates regularly. I installed it in my root user's crontab on my server, to run the script on the first of every month. The script checks all the existing certificates to see if any should be renewed (if they're at least a month old), and runs the letsencrypt-auto
client to update them and restart my web server.
Here's the source code. For the raw plain text version, check https://sh.kirsle.net/renew-certs.py. There's a configurable section at the top of the script that can be edited to put in your own domain names.
#!/usr/bin/env python3
# Cron script to renew LetsEncrypt certificates.
#
# --Kirsle
# https://sh.kirsle.net/
import os
import subprocess
import time
################################################################################
# Configuration Section Begins #
################################################################################
# Let's Encrypt directories
LE_APPDIR = "/opt/letsencrypt" # Where `letsencrypt-auto` lives
LE_CERTS = "/etc/letsencrypt/live" # Where live certificates go
# Common arguments to letsencrypt-auto
COMMON = ["./letsencrypt-auto", "certonly", "--renew",
"--webroot", "-w", "/var/www/html"]
# Domains and their subdomains; one array element per certificate, with each
# array element being a list of domains to include in the same cert.
CERTS = [
[ "www.kirsle.net", "kirsle.net", "www.kirsle.com", "kirsle.com",
"www.kirsle.org", "kirsle.org", "sh.kirsle.net", "rpm.kirsle.net",
"minecraft.kirsle.net", "mc.kirsle.net", "rophako.kirsle.net" ],
[ "noah.is", "www.noah.is", "petherbridge.org", "www.petherbridge.org",
"noah.petherbridge.org", "noahpetherbridge.com",
"www.noahpetherbridge.com" ],
[ "rivescript.com", "www.rivescript.com", "static.rivescript.com" ],
]
# Minimum lifetime for certificate before renewing it?
LIFETIME = 60*60*24*30 # Once a month.
# Command to run after finishing if certs were renewed.
RESTART_COMMAND = ["service", "nginx", "reload"]
################################################################################
# End Configuration Section #
################################################################################
def main():
os.chdir(LE_APPDIR)
# If any certs were renewed, we'll schedule the restart command at the end.
any_renewed = False
# See which certificates are ready to be renewed.
print("Checking SSL certificates for renewal")
for cert in CERTS:
ready = False # Ready to renew this one
primary = cert[0]
# Find its existing live certificate file.
home = os.path.join(LE_CERTS, primary)
chain = os.path.join(home, "cert.pem")
# When was it last modified?
if not os.path.isfile(chain):
print("NOTE: No existing cert file found for {} ({})".format(
primary,
chain,
))
ready = True
else:
mtime = os.stat(chain).st_mtime
if time.time() - mtime > LIFETIME:
print("Cert for {} is old; scheduling it for renewal!"\
.format(primary))
ready = True
# Proceed?
if ready:
print("Renewing certificate for {}...".format(primary))
command = []
command.extend(COMMON)
# Add all the domains.
for domain in cert:
command.extend(["-d", domain])
print("Exec: {}".format(" ".join(command)))
subprocess.call(command)
any_renewed = True
# If any certs were changed, restart the web server.
if any_renewed:
print("Restarting the web server: {}".format(" ".join(RESTART_COMMAND)))
subprocess.call(RESTART_COMMAND)
if __name__ == "__main__":
main()
There is 1 comment on this page. Add yours.
Thanks a lot - your post had the in-depth details of the Let's Encrypt process I needed to get my own hacked-together solution up and running.
I also see you have a Minecraft server - I'm half convinced I should mine you a few blocks of diamond in thanks. Hmm...
Either way, thanks a bunch!
0.0076s
.