1# acme-tiny 2 3[![Build Status](https://travis-ci.org/diafygi/acme-tiny.svg)](https://travis-ci.org/diafygi/acme-tiny) 4[![Coverage Status](https://coveralls.io/repos/diafygi/acme-tiny/badge.svg?branch=master&service=github)](https://coveralls.io/github/diafygi/acme-tiny?branch=master) 5 6This is a tiny, auditable script that you can throw on your server to issue 7and renew [Let's Encrypt](https://letsencrypt.org/) certificates. Since it has 8to be run on your server and have access to your private Let's Encrypt account 9key, I tried to make it as tiny as possible (currently less than 200 lines). 10The only prerequisites are python and openssl. 11 12**PLEASE READ THE SOURCE CODE! YOU MUST TRUST IT WITH YOUR PRIVATE ACCOUNT KEY!** 13 14## Donate 15 16If this script is useful to you, please donate to the EFF. I don't work there, 17but they do fantastic work. 18 19[https://eff.org/donate/](https://eff.org/donate/) 20 21## How to use this script 22 23If you already have a Let's Encrypt issued certificate and just want to renew, 24you should only have to do Steps 3 and 6. 25 26### Step 1: Create a Let's Encrypt account private key (if you haven't already) 27 28You must have a public key registered with Let's Encrypt and sign your requests 29with the corresponding private key. If you don't understand what I just said, 30this script likely isn't for you! Please use the official Let's Encrypt 31[client](https://github.com/letsencrypt/letsencrypt). 32To accomplish this you need to initially create a key, that can be used by 33acme-tiny, to register an account for you and sign all following requests. 34 35``` 36openssl genrsa 4096 > account.key 37``` 38 39#### Use existing Let's Encrypt key 40 41Alternatively you can convert your key, previously generated by the original 42Let's Encrypt client. 43 44The private account key from the Let's Encrypt client is saved in the 45[JWK](https://tools.ietf.org/html/rfc7517) format. `acme-tiny` is using the PEM 46key format. To convert the key, you can use the tool 47[conversion script](https://gist.github.com/JonLundy/f25c99ee0770e19dc595) by JonLundy: 48 49```sh 50# Download the script 51wget -O - "https://gist.githubusercontent.com/JonLundy/f25c99ee0770e19dc595/raw/6035c1c8938fae85810de6aad1ecf6e2db663e26/conv.py" > conv.py 52 53# Copy your private key to your working directory 54cp /etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org/directory/<id>/private_key.json private_key.json 55 56# Create a DER encoded private key 57openssl asn1parse -noout -out private_key.der -genconf <(python conv.py private_key.json) 58 59# Convert to PEM 60openssl rsa -in private_key.der -inform der > account.key 61``` 62 63### Step 2: Create a certificate signing request (CSR) for your domains. 64 65The ACME protocol (what Let's Encrypt uses) requires a CSR file to be submitted 66to it, even for renewals. You can use the same CSR for multiple renewals. NOTE: 67you can't use your account private key as your domain private key! 68 69``` 70# Generate a domain private key (if you haven't already) 71openssl genrsa 4096 > domain.key 72``` 73 74``` 75# For a single domain 76openssl req -new -sha256 -key domain.key -subj "/CN=yoursite.com" > domain.csr 77 78# For multiple domains (use this one if you want both www.yoursite.com and yoursite.com) 79openssl req -new -sha256 -key domain.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:yoursite.com,DNS:www.yoursite.com")) > domain.csr 80``` 81 82### Step 3: Make your website host challenge files 83 84You must prove you own the domains you want a certificate for, so Let's Encrypt 85requires you host some files on them. This script will generate and write those 86files in the folder you specify, so all you need to do is make sure that this 87folder is served under the ".well-known/acme-challenge/" url path. NOTE: Let's 88Encrypt will perform a plain HTTP request to port 80 on your server, so you 89must serve the challenge files via HTTP (a redirect to HTTPS is fine too). 90 91``` 92# Make some challenge folder (modify to suit your needs) 93mkdir -p /var/www/challenges/ 94``` 95 96```nginx 97# Example for nginx 98server { 99 listen 80; 100 server_name yoursite.com www.yoursite.com; 101 102 location /.well-known/acme-challenge/ { 103 alias /var/www/challenges/; 104 try_files $uri =404; 105 } 106 107 ...the rest of your config 108} 109``` 110 111### Step 4: Get a signed certificate! 112 113Now that you have setup your server and generated all the needed files, run this 114script on your server with the permissions needed to write to the above folder 115and read your private account key and CSR. 116 117``` 118# Run the script on your server 119python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /var/www/challenges/ > ./signed_chain.crt 120``` 121 122### Step 5: Install the certificate 123 124The signed https certificate chain that is output by this script can be used along 125with your private key to run an https server. You need to include them in the 126https settings in your web server's configuration. Here's an example on how to 127configure an nginx server: 128 129```nginx 130server { 131 listen 443 ssl; 132 server_name yoursite.com, www.yoursite.com; 133 134 ssl_certificate /path/to/signed_chain.crt; 135 ssl_certificate_key /path/to/domain.key; 136 ssl_session_timeout 5m; 137 ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 138 ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA; 139 ssl_session_cache shared:SSL:50m; 140 ssl_dhparam /path/to/server.dhparam; 141 ssl_prefer_server_ciphers on; 142 143 ...the rest of your config 144} 145 146server { 147 listen 80; 148 server_name yoursite.com, www.yoursite.com; 149 150 location /.well-known/acme-challenge/ { 151 alias /var/www/challenges/; 152 try_files $uri =404; 153 } 154 155 ...the rest of your config 156} 157``` 158 159### Step 6: Setup an auto-renew cronjob 160 161Congrats! Your website is now using https! Unfortunately, Let's Encrypt 162certificates only last for 90 days, so you need to renew them often. No worries! 163It's automated! Just make a bash script and add it to your crontab (see below 164for example script). 165 166Example of a `renew_cert.sh`: 167```sh 168#!/usr/bin/sh 169python /path/to/acme_tiny.py --account-key /path/to/account.key --csr /path/to/domain.csr --acme-dir /var/www/challenges/ > /path/to/signed_chain.crt.tmp || exit 170mv /path/to/signed_chain.crt.tmp /path/to/signed_chain.crt 171service nginx reload 172``` 173 174``` 175# Example line in your crontab (runs once per month) 1760 0 1 * * /path/to/renew_cert.sh 2>> /var/log/acme_tiny.log 177``` 178 179NOTE: Since Let's Encrypt's ACME v2 release (acme-tiny 4.0.0+), the intermediate 180certificate is included in the issued certificate download, so you no longer have 181to independently download the intermediate certificate and concatenate it to your 182signed certificate. If you have an bash script using acme-tiny <4.0 (e.g. before 1832018-03-17) with acme-tiny 4.0.0+, then you may be adding the intermediate 184certificate to your signed_chain.crt twice (not a big deal, it should still work fine, 185but just makes the certificate slightly larger than it needs to be). To fix, 186simply remove the bash code where you're downloading the intermediate and adding 187it to the acme-tiny certificate output. 188 189## Permissions 190 191The biggest problem you'll likely come across while setting up and running this 192script is permissions. You want to limit access to your account private key and 193challenge web folder as much as possible. I'd recommend creating a user 194specifically for handling this script, the account private key, and the 195challenge folder. Then add the ability for that user to write to your installed 196certificate file (e.g. `/path/to/signed_chain.crt`) and reload your webserver. That 197way, the cron script will do its thing, overwrite your old certificate, and 198reload your webserver without having permission to do anything else. 199 200**BE SURE TO:** 201* Backup your account private key (e.g. `account.key`) 202* Don't allow this script to be able to read your domain private key! 203* Don't allow this script to be run as root! 204 205## Feedback/Contributing 206 207This project has a very, very limited scope and codebase. I'm happy to receive 208bug reports and pull requests, but please don't add any new features. This 209script must stay under 200 lines of code to ensure it can be easily audited by 210anyone who wants to run it. 211 212If you want to add features for your own setup to make things easier for you, 213please do! It's open source, so feel free to fork it and modify as necessary. 214