Ever tried installing an SSL certificate on your website? Sucks, doesn’t it? The whole process around procuring and installing SSL certificates is so archaic and cumbersome that it sends shudders through the body of anyone facing it.
After Edward Snowden let the world know that everyone is watching everything you do online, we started to realise that we should be able to use the Internet without every benign and every private bit of data being visible to others. The answer to this problem: encryption.
Encryption scrambles data between the provider (say, a website, server or application) and its end user, such that if the data is intercepted anywhere between the two, it can’t be read. If you own a website, the way you encrypt data sent to and from it is through an SSL certificate.
Late last year, several do-gooders came together and agreed that the status quo for producing and installing SSL certificates was terrible. So they set about changing it, and with the vision of allowing anyone to produce and install an SSL certificate with the greatest of ease and with zero cost, they created Let’s Encrypt: a non-profit certificate issuing authority.
Like some of the big names in the business which charge anywhere from $10 to many hundreds of dollars for an SSL certificate, Let’s Encrypt gives out functionally-identical certificates for free in the name of facilitating widespread encryption on the Internet. See, this is a big deal.
Just yesterday, Let’s Encrypt moved into a public beta, meaning that everything isn’t quite perfect yet, but they’re ready for the masses to start testing their service. So with that, I set about using it to encrypt two sites which I hadn’t yet gone as far as securing yet.
How to install SSL certificate on LEMP with Let’s Encrypt
I personally run a LEMP (Linux, nginx, MySQL, PHP) stack on Digital Ocean for the two sites that I added SSL certificates to, so this tutorial will be focused on that specific configuration, though the process in general is the same for other configs.
In general, the process goes like this:
- Clone Let’s Encrypt git repo.
- Run Let’s Encrypt which creates the certificates.
- Modify your server blocks to include SSL directives.
- Reload nginx.
Install Let’s Encrypt
Let’s Encrypt is installed by cloning their git repo. In general, these commands will perform that for you:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cd ~ | |
git clone https://github.com/letsencrypt/letsencrypt | |
cd letsencrypt |
Create SSL certificates with Let’s Encrypt
Let’s Encrypt is now installed and ready to start producing certificates. At this point, the automatic installer doesn’t stop nginx to allow it to bind to port 80, so this needs to be done manually:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sudo service nginx stop |
Now you can create the certificates. If you want the certificate to cover the www and non-www version of the domain name, you’ll need to specify both in this command. You can specify as many domains as you want to cover with a single certificate:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
./letsencrypt-auto –server https://acme-v01.api.letsencrypt.org/directory auth -d dave.pe -d www.dave.pe |
This command will cause Let’s Encrypt to check your environment and install any necessary dependencies. Once all that’s done, it should tell you that it has created your new certificates:
Certificates are valid for 90 days to improve their security (compromised certificates aren’t then active for years).
If you haven’t needed to before, you’ll also need to create a set of DH parameters:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
openssl dhparam -out /etc/nginx/dhparam.pem 2048 |
Add SSL directives to nginx
Now you need to tell nginx to use your new certificates. These config files are located in /etc/nginx/sites-available/. My server block previously looked something like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
server { | |
listen 80; ## listen for ipv4; this line is default and implied | |
listen [::]:80; ## listen for ipv6 | |
server_name dave.pe www.dave.pe; | |
root /usr/share/nginx/dave.pe/html; | |
index index.php index.html index.htm; | |
location / { | |
# First attempt to serve request as file, then | |
# as directory, then fall back to index.html | |
try_files $uri $uri/ /yourls-loader.php; | |
# PHP engine | |
location ~ \.php$ { | |
try_files $uri =404; | |
fastcgi_pass unix:/var/run/php5-fpm.sock; # Can be different | |
fastcgi_index index.php; | |
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
include fastcgi_params; | |
} | |
} | |
location /doc/ { | |
alias /usr/share/doc/; | |
autoindex on; | |
allow 127.0.0.1; | |
deny all; | |
} | |
error_page 404 /404.html; | |
# redirect server error pages to the static page /50x.html | |
# | |
error_page 500 502 503 504 /50x.html; | |
location = /50x.html { | |
root /usr/share/nginx/html; | |
} | |
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 | |
# | |
location ~ \.php$ { | |
try_files $uri =404; | |
# fastcgi_split_path_info ^(.+\.php)(/.+)$; | |
# # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini | |
# | |
# # With php5-cgi alone: | |
# # With php5-fpm: | |
fastcgi_pass unix:/var/run/php5-fpm.sock; | |
fastcgi_index index.php; | |
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
include fastcgi_params; | |
} | |
} |
My new server block added a whole lot of SSL-related directives. You can use Mozilla’s SSL config generator to help you with your own. Mine looked like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
server { | |
listen 443 ssl; | |
listen [::]:443 ssl; | |
server_name dave.pe; | |
ssl_certificate /etc/letsencrypt/live/dave.pe/fullchain.pem; | |
ssl_certificate_key /etc/letsencrypt/live/dave.pe/privkey.pem; | |
ssl_session_timeout 1d; | |
ssl_session_cache shared:SSL:10m; | |
# openssl dhparam -out dhparam.pem 2048 | |
ssl_dhparam /etc/nginx/dhparam.pem; | |
ssl_protocols TLSv1.1 TLSv1.2; | |
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK'; | |
ssl_prefer_server_ciphers on; | |
add_header Strict-Transport-Security max-age=15768000; | |
ssl_stapling on; | |
ssl_stapling_verify on; | |
## verify chain of trust of OCSP response using Root CA and Intermediate certs | |
#ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates; | |
ssl_trusted_certificate /etc/letsencrypt/live/dave.pe/chain.pem; | |
resolver 8.8.8.8 8.8.4.4 valid=86400; | |
resolver_timeout 10; | |
root /usr/share/nginx/dave.pe/html; | |
index index.php index.html index.htm; | |
location / { | |
# First attempt to serve request as file, then | |
# as directory, then fall back to index.html | |
try_files $uri $uri/ /yourls-loader.php; | |
# PHP engine | |
location ~ \.php$ { | |
try_files $uri =404; | |
fastcgi_pass unix:/var/run/php5-fpm.sock; # Can be different | |
fastcgi_index index.php; | |
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
include fastcgi_params; | |
} | |
} | |
location /doc/ { | |
alias /usr/share/doc/; | |
autoindex on; | |
allow 127.0.0.1; | |
deny all; | |
} | |
error_page 404 /404.html; | |
# redirect server error pages to the static page /50x.html | |
# | |
error_page 500 502 503 504 /50x.html; | |
location = /50x.html { | |
root /usr/share/nginx/html; | |
} | |
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 | |
# | |
location ~ \.php$ { | |
try_files $uri =404; | |
# fastcgi_split_path_info ^(.+\.php)(/.+)$; | |
# # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini | |
# | |
# # With php5-cgi alone: | |
# # With php5-fpm: | |
fastcgi_pass unix:/var/run/php5-fpm.sock; | |
fastcgi_index index.php; | |
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
include fastcgi_params; | |
} | |
} |
You can see that lines 2-5 in my original config were replaced with lines 2-28 in the new config.
I wanted to achieve a couple of additional things with my new setup – I wanted to force all traffic trying to use the www subdomain to be redirected to the bare domain and to force all HTTP traffic to HTTPS. So, I added a couple of server blocks before this new server block to redirect traffic accordingly:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# redirect http to https | |
server { | |
listen 80; ## listen for ipv4; this line is default and implied | |
listen [::]:80; ## listen for ipv6 | |
server_name dave.pe www.dave.pe; | |
return 301 https://dave.pe$request_uri; | |
} | |
# redirect https://www to https:// | |
server { | |
listen 443 ssl; | |
listen [::]:443 ssl; | |
server_name www.dave.pe; | |
ssl_certificate /etc/letsencrypt/live/dave.pe/fullchain.pem; | |
ssl_certificate_key /etc/letsencrypt/live/dave.pe/privkey.pem; | |
ssl_session_timeout 1d; | |
ssl_session_cache shared:SSL:10m; | |
# openssl dhparam -out dhparam.pem 2048 | |
ssl_dhparam /etc/nginx/dhparam.pem; | |
ssl_protocols TLSv1.1 TLSv1.2; | |
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK'; | |
ssl_prefer_server_ciphers on; | |
add_header Strict-Transport-Security max-age=15768000; | |
ssl_stapling on; | |
ssl_stapling_verify on; | |
## verify chain of trust of OCSP response using Root CA and Intermediate certs | |
#ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates; | |
ssl_trusted_certificate /etc/letsencrypt/live/dave.pe/chain.pem; | |
resolver 8.8.8.8 8.8.4.4 valid=86400; | |
resolver_timeout 10; | |
return 302 https://dave.pe$request_uri; | |
} |
With that, everything was in place, ready for use. A quick reload of nginx was all I needed to start loading my sites over HTTPS:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
service nginx -s reload |
Now, running an SSL test on my site, I was instantly scoring an A+, giving you an idea of just how robust these Let’s Encrypt certificates are.