Using Let's Encrypt free SSL/TLS certificates with Nginx and achieve A+ rating in Qualys SSL Labs

The Let's Encrypt is a certificate authority that offers free and automated SSL/TLS certificates for your website(s). It issues certificates trusted by most browsers. In this article will show how to setup and automate the renewal of Let's Encrypt SSL/TLS certificates with Nginx.

It is assumed that Nginx is already installed in your system. But if it is not please follow the steps in this article. The following procedures are tested on Linode server running Centos 7 64-bit Linux distribution.

The following steps are not updated. Please follow the updated steps in this article.

  1. Download the Let's Encrypt client:

          
    yum install -y git
    git clone https://github.com/certbot/certbot /opt/letsencrypt
          
        

    Note: to update the Let's Encrypt client execute:

          
    cd /opt/letsencrypt
    git pull
          
        
  2. Let's Encrypt client validates ownership of your domain by creating a temporary file (a token) in [webroot-path]/.well-known/acme-challenge/. We will use /var/www/letsencrypt as our [webroot-path]. Let's create it and set permission:

          
    mkdir -p /var/www/letsencrypt
    chown apache. /var/www/letsencrypt
          
        

    Note: 'apache' is a user set in nginx.conf. If this is not the case in your setup, please change it to user set in your nginx.conf file.

  3. The Let's Encrypt client sends an HTTP request to validate the temporary file created in /var/www/letsencrypt/.well-known/acme-challenge/ which serves as verification that your domain's DNS record resolves to the server running it. Skip this step if you came from and followed this article. To allow the HTTP request to access the temporary file, add the following nginx script snippet:

          
    location /.well-known/acme-challenge {
      root /var/www/letsencrypt;
    }
          
        

    ... in your virtual servers. Example, applying it in 'yourwebsite.com' and 'yourotherwebsite.com' virtual servers nginx script:

          
    ...
    server {
      listen XXX.XXX.XXX.XXX:80; ## IPv4
      listen [XXXX:XXXX::XXXX:XXXX:XXXX:XXXX]:80; ## IPv6
      server_name yourwebsite.com www.yourwebsite.com;
      ...
      location /.well-known/acme-challenge {
        root /var/www/letsencrypt;
      }
      ...
    }
    ...
    server {
      listen XXX.XXX.XXX.XXX:80; ## IPv4
      listen [XXXX:XXXX::XXXX:XXXX:XXXX:XXXX]:80; ## IPv6
      server_name yourotherwebsite.com www.yourotherwebsite.com;
      ...
      location /.well-known/acme-challenge {
        root /var/www/letsencrypt;
      }
      ...
    }
    ...
          
        

    Note: The XXX.XXX.XXX.XXX is your server's IPv4 address and XXXX:XXXX::XXXX:XXXX:XXXX:XXXX is your server's IPv6 address.

    Restart Nginx:

          
    systemctl restart nginx
          
        
  4. Create Let's Encrypt configuration file /etc/nginx/letsencrypt/domain.conf and add the following to it:

          
    # Domains (100 maximum)
    domains = yourwebsite.com,www.yourwebsite.com,yourotherwebsite.com,www.yourotherwebsite.com
    
    # Key size 2048 or 4096
    rsa-key-size = 2048
    
    # The current closed beta (as of 2015-Nov-07) is using this server
    server = https://acme-v01.api.letsencrypt.org/directory
    
    # This email will receive renewal reminders
    email = [email protected]
    
    # Configure it to make non-interactively to be able to run as a cronjob
    text = True
    agree-tos = True
    renew-by-default = True
    
    # Authenticate by placing a file in the webroot (under .well-known/acme-challenge/)
    # and then letting LE fetch it
    authenticator = nginx
    webroot-path = /var/www/letsencrypt/
          
        

    As of this writing, Let's Encrypt does not support wildcard certificates yet. Include all the domains and their sub-domains in 'domains' parameter inside your Let's Encrypt configuration file. Take note that Let's Encrypt can only holds a maximum of 100 domains/sub-domains per certificate. If you have more than 100 domains/sub-domains, you will need to split or create new Let's Encrypt configuration file the another set of 100 domains/sub-domains.

    Use 2048 bits for 'rsa-key-size' if performance is your priority and use 4096 if more security strength is needed. Plus 2048 bits uses less CPU during encryption and authentication; and less CPU means less battery drain than 4096 bits.

  5. We can now request the certificate by executing:

          
    /opt/letsencrypt/letsencrypt-auto --config /etc/nginx/letsencrypt/domain.conf certonly
          
        

    If successful, we should see message something like this at the end:

          
    IMPORTANT NOTES:
     - Congratulations! Your certificate and chain have been saved at
       /etc/letsencrypt/live/yourwebsite.com/fullchain.pem. Your cert will
       expire on 2017-03-08. To obtain a new or tweaked version of this
       certificate in the future, simply run letsencrypt-auto again. To
       non-interactively renew *all* of your certificates, run
       "letsencrypt-auto renew"
          
        

    Note: the path name /etc/letsencrypt/live/yourwebsite.com generated by the Let's Encrypt client is based on the first domain name listed in 'domains' parameter inside your Let's Encrypt configuration file /etc/nginx/letsencrypt/domain.conf. In our case, it is 'yourwebsite.com' and the certificate files generated inside /etc/letsencrypt/live/yourwebsite.com are already for 'yourwebsite.com', 'www.yourwebsite.com', 'yourotherwebsite.com' and 'www.yourotherwebsite.com' domains.

  6. Integrate the Let's Encrypt generated certificate with Nginx. Skip this step if you came from and followed this article. Open your /etc/nginx/nginx.conf and add the following nginx script snippet inside the 'http' context:

          
    ## Timeout for keep-alive connections.
    ## Server will close connections after this time.
    keepalive_timeout 75 75;
    ## Use a SSL/TLS cache for SSL session resume. This needs to be
    ## here (in this context, for session resumption to work. See this
    ## thread on the Nginx mailing list:
    ## http://nginx.org/pipermail/nginx/2010-November/023736.html.
    ## 1MB store about 4000 sessions:
    ## In this case, it store about 80000 sessions in 1 day
    ssl_session_cache shared:SSL:20m;
    ssl_session_timeout 1d;
    ## The server dictates the choice of cipher suites.
    ssl_prefer_server_ciphers on;
    ## Use only Perfect Forward Secrecy Ciphers.
    ## No SSLv3 support (SSLv3 POODLE Vulnerability)
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ## Old Mozilla SSL Configuration 
    ## https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.10.2&openssl=1.0.1e&hsts=no&profile=old
    ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305: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:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP';
    ## Pregenerated Diffie-Hellman parameters 2048.
    ssl_dhparam key/dh_param.pem;
    ## Curve to use for ECDH.
    ssl_ecdh_curve prime256v1;
    ## Enable OCSP stapling. A better way to revocate server certificates.
    ssl_stapling on;
    ## Enable verification of OCSP stapling responses by the server.
    ssl_stapling_verify on;
    ## Server certificate and key (using Let's Encrypt free SSL certificate).
    ssl_certificate /etc/letsencrypt/live/yourwebsite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourwebsite.com/privkey.pem;
    ## Verify chain of trust of OCSP response using Root CA and Intermediate certs
    ssl_trusted_certificate /etc/letsencrypt/live/yourwebsite.com/chain.pem;
    ## Use Google's DNS
    resolver 8.8.8.8 8.8.4.4;
          
        

    Generate DH parameters file with 2048 bit long safe prime:

          
    openssl dhparam 2048 -out /etc/nginx/key/dh_param.pem
          
        

    Restart Nginx:

          
    systemctl reload nginx
          
        
  7. The Let's Encrypt certificate is only valid for three months. Automating the renewal of the certificate is possible with Let's Encrypt client using cronjob. Let's create a bash script that cronjob will execute to renew the Let's Encrypt certificate(s). Create /etc/nginx/letsencrypt/renew.sh file and add the following to it:

          
    #!/bin/sh
    
    for conf in $(ls /etc/nginx/letsencrypt/*.conf); do
      /opt/letsencrypt/letsencrypt-auto --config "$conf" certonly
    done
    
    systemctl reload nginx
          
        
  8. Execute crontab -e and append this line to run the script every two months:

          
    0 0 1 */2 * /bin/sh /etc/nginx/letsencrypt/renew.sh
          
        
  9. Test your website's SSL rating at https://www.ssllabs.com/ssltest.

Comments

did all the steps, but I get this error:
nginx: [emerg] BIO_new_file("/etc/letsencrypt/live/example.com/fullchain.pem") failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/letsencrypt/live/example.com/fullchain.pem','r') error:2006D080:BIO routines:BIO_new_file:no such file) nginx: configuration file /etc/nginx/nginx.conf test failed

Let's Encrypt only works on a site with registered domain name (so it will not work on localhost). You get that error because the Let's Encrypt script you have executed (below) failed to generate the certificate. Thus, your /etc/letsencrypt/live/mydjroom.com/ is empty.
  
/opt/letsencrypt/letsencrypt-auto --config /etc/nginx/letsencrypt/domain.conf certonly
  

when loading a https page, I get:

An error occurred during a connection to mydjroom.com. SSL received a record that exceeded the maximum permissible length. Error code: SSL_ERROR_RX_RECORD_TOO_LONG

Is the protocol not changing to https?

In sites-enabled .conf I changed the listen to "listen 443 ssl;" instead of listen " listen 0.0.0.0:443;" . Now it works.
But why do I need to put these lines in my sites-enabled? Aren't they already loaded via nginx.conf?

This is my sites-enabled\example.com.conf:

server {
listen 0.0.0.0:80;
listen 443 ssl;
server_name example.com;
location / {
root /home/ericson/public_html/example.com;
index index.html index.htm;
try_files $uri $uri/ =404;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
proxy_set_header X-Forwarded-Proto https;
}

Is it all correctly done?

Your website's domain(s) should be define under server context which tells that your nginx is(are) handling your website(s). In your sites-enabled folder is where your active site(s) configuration files located. Actually, you can have have all your nginx configuration files' content written in one file nginx.conf and remove the rest of configuration files and folders. But we don't do that. We separate the configuration into multiple files for the sake of best practice (code readability, organization, etc.) and call these files in our main nginx.conf. The lines:
  
listen 0.0.0.0:80;
listen 443 ssl;
  
Ideally, you should only define one port and listen directive for each server context. If you prefer your website as SSL version, create two server context and redirect the non-SSL to SSL server context.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.