Let’s Encrypt on Nginx and SSL Grade A+

Posted on October 28, 2016 at 6:38 pm

A few steps to install Let’s Encrypt on Debian with Nginx and score a A+ grade on SSL Labs. We will install Certbot to simplify the creation and renew of SSL certificates with Let’s Encrypt. Simple tutorial that can help you setup Let’s Encrypt with Nginx in just a few minutes.

Login via SSH on your server as root.

Install Certbot via apt-get using -t jessie-backports on Debian 8 Jessie:

apt-get update
apt-get install certbot -t jessie-backports

*** Make sure to enable jessie-backports on APT sources.list.

On Debian 9 Stretch you can use apt-get like this:

apt-get update
apt-get install certbot -t stretch-backports

Or install the latest version with git:

apt install -y git
cd /opt
git clone https://github.com/certbot/certbot
cd certbot
./certbot-auto -h

Or install the latest compiled binary version:

cd /usr/bin/
wget https://dl.eff.org/certbot-auto
chmod a+x certbot-auto
mv certbot-auto certbot

Generate DH param (we’ll use this later):

openssl dhparam -out /etc/letsencrypt/dhparams.pem 2048

Let’s Encrypt will try to validate your domain name with an HTTP GET request on a special folder that is automatically created by certbot on your website root path, example:

"GET /.well-known/acme-challenge/%RANDOM_STRING% HTTP/1.1"

Make sure Nginx does not block access to “/.well-known/” folder:

# Deny access to .htaccess-like files
location ~ /\. {
    deny all;
}
 
# Allow access to .well-known directory (Let's Encrypt)
location ^~ /.well-known/ {
    allow all;
}

Generate the certificate for example.com (and www.example.com);

certbot certonly --webroot -w /var/www/vhosts/example.com/public -d www.example.com -d example.com --non-interactive --agree-tos --email your@email.com

You should see a new folder named “www.example.com” on:

/etc/letsencrypt/live/

Edit the Nginx vhost config file as this:

# Redirect HTTP traffic to HTTPS
server {
        listen 80;
        server_name example.com www.example.com;
        access_log off;
        error_log off;
        return 301 https://www.example.com$request_uri;
}
# Redirect https://example.com to https://www.example.com
server {
        listen 443 ssl http2;
        server_name example.com;
        ssl on;
        ssl_ciphers  EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:EDH+aRSA:HIGH:!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!SEED:!DSS:!CAMELLIA;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:50m;
        ssl_session_timeout 5m;
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_certificate      /etc/letsencrypt/live/www.example.com/fullchain.pem;
        ssl_certificate_key  /etc/letsencrypt/live/www.example.com/privkey.pem;
        ssl_trusted_certificate /etc/letsencrypt/live/www.example.com/chain.pem;
        ssl_dhparam /etc/letsencrypt/dhparams.pem; # Generated by running "openssl dhparam -out /etc/letsencrypt/dhparams.pem 2048" as root user
        add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
        add_header X-Frame-Options SAMEORIGIN;
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
        resolver 8.8.8.8;
        access_log off;
        error_log off;
        return 301 https://www.example.com$request_uri;
}
# Handle https://www.example.com
server {
        listen 443 ssl http2;
        server_name www.example.com;
        ssl on;
        ssl_ciphers  EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:EDH+aRSA:HIGH:!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!SEED:!DSS:!CAMELLIA;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:50m;
        ssl_session_timeout 5m;
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_certificate      /etc/letsencrypt/live/www.example.com/fullchain.pem;
        ssl_certificate_key  /etc/letsencrypt/live/www.example.com/privkey.pem;
        ssl_trusted_certificate /etc/letsencrypt/live/www.example.com/chain.pem;
        ssl_dhparam /etc/letsencrypt/dhparams.pem; # Generated by running "openssl dhparam -out /etc/letsencrypt/dhparams.pem 2048" as root user
        add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
        add_header X-Frame-Options SAMEORIGIN;
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block"; 
        resolver 8.8.8.8;
 
        access_log /var/www/vhosts/example.com/logs/access.log main;
        error_log /var/www/vhosts/example.com/logs/error.log warn;
	root /var/www/vhosts/example.com/public;
	index index.html index.htm index.php;
        [...]

Some online tools that can help you configure Nginx for HTTPS:

Security/Server Side TLS – MozillaWiki
Mozilla SSL Configuration Generator

Restart Nginx service:

/etc/init.d/nginx restart

Now test your website on SSL Labs and you should get A+ grade:

ssl-labs-grade-a

The SSL certificate created by Let’s Encrypt is valid for 90 days.

To renew the certificate before it is expired, you can use this command that is used to renew all SSL certificates present in the /etc/letsencrypt/renew/ directory:

certbot renew --webroot --noninteractive --post-hook "service nginx reload"

Example output:

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/www.example.com.conf
-------------------------------------------------------------------------------
 
-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/www.test.com.conf
-------------------------------------------------------------------------------
 
The following certs are not due for renewal yet:
  /etc/letsencrypt/live/www.example.com/fullchain.pem (skipped)
  /etc/letsencrypt/live/www.test.com/fullchain.pem (skipped)
No renewals were attempted.

For automatic renewals you may add this line of code in /etc/crontab file:

0 */12 * * * root certbot renew --webroot --noninteractive --post-hook "service nginx reload"

* But make sure to delete /etc/cron.d/certbot if you use /etc/crontab!

You should read this:

Lets Encrypt certificate failed to renew
Renew an expired Lets Encrypt / Certbot certificate

Another way to auto-renew the certificates is by editing this file (recommended):

/etc/cron.d/certbot

By adding the “–post-hook” code to reload the web server:

# /etc/cron.d/certbot: crontab entries for the certbot package
#
# Upstream recommends attempting renewal twice a day
#
# Eventually, this will be an opportunity to validate certificates
# haven't been revoked, etc.  Renewal will only occur if expiration
# is within 30 days.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
 
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew --post-hook "service nginx reload"

References and useful links:

How to Correctly Backup and Restore /etc/letsencrypt
Let’s Encrypt – Free SSL/TLS Certificates
SSL Server Test (Powered by Qualys SSL Labs)
Mozilla SSL Configuration Generator
‘certbot -renewal’ is not renewing the certificate
Check your HTTP Security Headers

Updated on October 23, 2018 at 10:31 am

Receive updates via email

Other Posts

Updated Posts