Set up SSL/TLS with Let's Encrypt

From Wiki
Jump to: navigation, search

Let's Encrypt is a Certificate Authority (CA) accepted by popular browsers that provides free certificates to support encrypting websites without drawing browser warnings. It is also supported by a convenient automation tool through the Certbot project. Let's Encrypt certificates support Server Name Indication (SNI), an extension of TLS that allows accepting more than one certificate from one IP address.

This article explains how to configure SSL/TLS with Let's Encrypt on a LEMP web server configured as per the prior articles in this series. Note that the general procedure and configurations may be used with other Certificate Authorities.

Nearly every step in this article requires root level access:

username@servername:~$ sudo -i

Install letsencrypt

The letsencrypt package contains all of the tools necessary to create and manage all of the necessary certificates. Note that if Python is not currently installed, then its various packages will be installed as they are required.

root@servername:~# add-apt-repository ppa:certbot/certbot
root@servername:~# aptitude update
root@servername:~# aptitude install python-certbot-nginx

When a certificate is created, it will be valid for and one subdomain. To verify ownership of a domain, a temporary file will be written to a public directory, and then erased. To support this, make one small edit to the http_server.conf file:

root@servername:~# nano /etc/nginx/global-configs/http_server.conf

Add as the very top line:

location ~ /\.well-known { allow all; }
root@servername:~# service nginx restart

Create the directory:

root@servername:~# mkdir /var/www/
root@servername:~# chown www-data /var/www/
root@servername:~# chmod 775 /var/www/

Create the certificates:

root@servername:~# letsencrypt certonly --rsa-key-size 4096 --webroot -w /var/www/ -d -d -w /var/www/ -d -d

The additional domain of is added to demonstrate using the command on multiple domains. Note that the configuration file name and certificate issuance domain will be assigned based on the first domain stated in the above command.

DNS verification for wildcard cert

Let's Encrypt can now provide wildcard certs for domains. A wildcard cert allows any subdomain for the approved certificate. For example, a wildcard certificate for and * will work for and any subdomain conforming to RFC and web server limitations. The wildcard cert will only work with clients that can use server name indication (SNI). While this is generally a non-issue for serving websites as all modern browsers support SNI, other services may have problems, particularly if the certificate is to be used for sending or receiving email, as many mail servers cannot use SNI.

When creating the certificate, note that each * only works at that level. For example, a certificate for * will not include because of the additional subdomain. Such a domain would require a wildcard certificate of *.*

While the below is text for a renewal, the verification will still run nearly the same for a new certificate.

root@servername:~# certbot certonly --manual --preferred-challenge=dns --email --server --agree-tos -d -d *
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Cert is due for renewal, auto-renewing...
Renewing an existing certificate
Performing the following challenges:
dns-01 challenge for
dns-01 challenge for

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name with the following value:


Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name with the following value:


Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
Waiting for verification...
Cleaning up challenges

 - Congratulations! Your certificate and chain have been saved at:
   Your key file has been saved at:
   Your cert will expire on 2021-06-19. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:
   Donating to EFF:          

When certbot is requesting the DNS TXT record, it is important to set this record to the lowest possible TTL allowed by the DNS server. Do not attempt to run the second verification test until after verifying the TTL has timed out since the first test run.

Change email address

If it is ever desired to change the email address that the Let's Encrypt notices are sent to, use the following command:

root@servername:~# letsencrypt update_account --email

Edit nginx.conf and create the common HTTPS server file


Edit nginx.conf:

root@servername:~# nano /etc/nginx/nginx.conf

Add at the bottom of the http block:

       ssl_session_cache shared:SSL:10m;

The ssl_session_cache will set up a shared cache for SSL sessions across all virtual hosts between all worker processes.


The default OpenSSL Diffe-Helman Ephemeral (DHE) parameter is 1024 bits, which is weaker than the key strength of the server's private key, thus clients using DHE will connect with a weaker encryption than non-DHE clients. Generate a strong DHE parameter. Note that this will take some time to complete and will use approximately 100% of one thread (1.00 load) for the duration of the task.

root@servername:~# openssl dhparam -out /etc/ssl/private/dhparam.pem 4096 && chmod 600 /etc/ssl/private/dhparam.pem

HTTPS server common configuration file

Create the HTTPS server file, which includes the common settings for SSL/TLS in an HTTPS server block:

root@servername:~# nano /etc/nginx/global-configs/https_server.conf

Add to the file:

location ~ /\.well-known { allow all; }
location ~ /\. { access_log off; log_not_found off; deny all; }
location ~ ~$ { access_log off; log_not_found off; deny all; }

location = /favicon.ico { log_not_found off; access_log off; }
location = /robots.txt { allow all; log_not_found off; access_log off; }
# TLSv1 and TLSv1.1 may be added when desired to support older devices.
# TLSv1.3 can be added, but note it requires a 128-bit cipher
# See for more details
ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on; 
ssl_ecdh_curve secp384r1;
ssl_dhparam /etc/ssl/private/dhparam.pem;

ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver <DNS resolver location> valid=300s;
resolver_timeout 5s;

add_header Strict-Transport-Security "max-age=63072000;includeSubDomains";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;

SSL/TLS protocols and ciphers

The ssl_protocols are configured to leave out all variants of the SSL protocol and include only TLS. This is to protect against the POODLE attack. The server should still be able to support the vast majority of devices, and given that it is already operating on SNI, most devices that don't support SNI also don't support any of the TLS protocols.

The ssl_ciphers used are the strongest ciphers available and will yield a score of 100% on the Qualys SSL Labs SSL Report testing. The !aNULL directive instructs openssl not to use any cipher suites that offer no authentication.

Enabling ssl_prefer_server_ciphers instructs the server to prefer server ciphers over client ciphers.

Setting the ssl_ecdh_curve instructs the server to use a stronger curve for ECDHE ciphers from the default curve of prime256v1.

The ssl_dhparam directive tells the server the location of the file to obtain DH parameters for DHE ciphers.

OCSP Stapling

Most clients will verify the validity of a server's certificate through either a Certificate Revocation List (CRL) or Online Certificate Status Protocol (OCSP). OCSP is typically preferred because the size of the CRLs is quite huge and pinging a server for a single record is a much faster method.

OCSP stapling is configured through several directives.


ssl_stapling directive enables stapling of OCSP respones

ssl_stapling_verify enables verification of the responses by the server;


The resolver configures name servers used to resolve names of upstream servers into addresses. This can be either a domain or an IP address (both IPv6 or IPv4) and can be configured to accept only IPv4. Here are some examples:

Cloudflare IPv4 & IPv6:

resolver [2606:4700:4700::1111]:535 [2606:4700:4700::1001]:5353 valid=300s;

OpenDNS IPv4:

resolver valid=300s;

OpenDNS IPv6:

resolver [2620:0:ccc::2]:5353 [2620:0:ccd::2]:5353 valid=300s;

OpenDNS with both IPv4 and IPv6:

resolver [2620:0:ccc::2]:5353 [2620:0:ccd::2]:5353 valid=300s;

Google Public DNS:

resolver [2001:4860:4860::8888]:5353 [2001:4860:4860::8844]:5353 valid=300s;

A local network gateway using only IPv4:

resolver ipv6=off valid=300s;

The resolver_timeout sets a timeout for resolver name resolution.


OCSP stapling requires that the entire certificate chain be available. This is configured in the sites-available file with the ssl_trusted_certificate directive.


To verify OSCP stapling is working properly, use either the Qualys SSL Labs SSL Server Test and look in the test results for OCSP stapling Yes or run the following command:

root@servername:~# openssl s_client -connect -tls1 -tlsextdebug -status

Note that this command may not work for servers behind firewalls.

The output of a test failure will include the line:

OCSP response: no response sent

The output of a successful test will include:

OCSP response:
OCSP Response Data:
    OCSP Response Status: successful (0x0)

HTTP headers

Several directives setting the HTTP headers can be used to enhance security.


HTTP Strict Transport Security (HSTS) protects against downgrade attacks by telling a browser that after it has established a secure connection, it should only communicate using HTTPS.

Clickjacking prevention

This setting can prevent clickjacking by setting controls on how a server may be viewed in a frame or an iframe. The example configuration of SAMEORIGIN will allow the site to be viewed in frames of the same origin server, which is a common feature in various popular PHP tools, including WordPress. Optionally, the DENY option could be used to prevent framing from any server. Note that there is an option for ALLOWFROM <URI>, but this is not well support by all popular browsers.

Content Security Policy (CSP) is being adopted and will replace X-Frame-Options with its frame-ancestors option, but this is not yet widely supported.


Adding the X-Content-Type-Options prevents MIME-sniffing and is most important on sites that host user-uploaded content.

Add HTTPS server block to sites-available file

Open the sites-available file and add the HTTPS server block.

root@servername:~# nano /etc/nginx/sites-available/

Add below the HTTP server block:

# HTTPS server

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    root /var/www/;
    access_log /var/www/;
    error_log /var/www/;
    include global-configs/https_server.conf;

    ssl_certificate /etc/letsencrypt/live/;
    ssl_certificate_key /etc/letsencrypt/live/;
    ssl_trusted_certificate /etc/letsencrypt/live/;

    location / {
        try_files $uri $uri/ =404;

Test then restart nginx.

root@servername:~# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
root@servername:~# service nginx reload

Navigating to (or should load /var/www/ in a secure session.

Note that if the the local device's ISP has not yet updated to the new DNS records for the server, entering the IP address of the server should bring up the page, but with the browser's certificate warnings. Proceeding through these warnings should allow the user to view the certificate and verify its settings are correct. Presumably, once the DNS records have updated, the certificates should function normally.

Configure automated renewals

Let's Encrypt certificates expire after only 90 days. Fortunately, one of the most useful and convenient tools available through certbot is the automation of certificate renewal, which can be done through a simple cron job.

root@servername:~# crontab -e

Add the following at the bottom:

30 2 * * 1 /usr/bin/letsencrypt renew >> /var/log/le-renew.log
40 2 * * 1 /bin/systemctl reload nginx

This will run the renew script every Monday at 02:30, save an output to a log file, and then reload nginx at 02:40 so that the new certificate is used.

Install ntp

The ntp package maintains the system clock instead of having only the default method for time synchronization, ntpupdate, which runs only once, when Ubuntu starts up. It is important for the clock to be synchronized because, theoretically, if a server goes a long time without being restarted, or a motherboard battery failure causes system clock malfunctions, it may end up having issues with the certificate authority.

root@servername:~# aptitude install ntp

Add CAA records

The DNS Certification Authority Authorization (CAA) record adds an additional layer of security by declaring which CAs are permitted to issue certificates for a domain and should be added when the DNS server supports it.

Based on the configurations in this article, CAA records should include at least two entries per domain, and at least two entries per subdomain:        issue        issuewild    ;    issue    issuewild    ;

Further configuration assistance is available from SSLMate's CAA Record Helper.

Common configurations

The HTTPS server block is just as configurable as the HTTP server block, but some configurations are commonly desired by adminstrators.

Require location to load in HTTPS

To require a file or directory to load only in HTTPS, perform the following.

root@servername:~# nano /etc/nginx/sites-available/

In the HTTP server block, add the following:

    location ^~ /path/to/directory/or/file {
        return 301 https://$server_name$request_uri;

Require subdomain or site to load in HTTPS

To require a subdomain or site to load only HTTPS, perform the following:

root@servername:~# nano /etc/nginx/sites-available/

Add the following server block:

server {
    listen 80;
    listen [::]:80;
    access_log off;
    error_log /var/www/;
    return 301$request_uri;


The Qualys Labs SSL Server Test is regularly updated based on new vulnerabilities and changes in the SSL/TLS standard. Periodically testing a server there is useful for making sure the server is configured correctly and securely.

Qualys Labs SSL Server Test Report for

Next step

Install PHP, that oh-so-popular server-side scripting language.

External links

Let's Encrypt


Cerbot User Guide

Strong SSL Security on nginx - is currently one of the most complete tutorials on the web for SSL/TLS settings, and this wiki article borrows heavily from it. Note that the link to the HSTS article from the tutorial page is broken. Reading all of this tutorial is strongly advised. Note that admins desiring to use HTTP Public Key Pinning should thoroughly read through all precautions. - Strong Ciphers for Apache, nginx, and Lighttpd, as well as other configs such as Postfix, OpenSSH, etc.

Securing your webserver with SSL/TLS | Ars Technica

Certificates and security | Official Ubuntu 18.04 Server Guide

Qualys SSL Labs SSL Server Test

Weak Diffie-Hellman and the Logjam Attack

Rackspace nameservers by datacenter - Note that each datacenter has both two IPv4 addresses and two IPv6 addresses. To find the IPv6 address, do a reverse DNS check on each IPv4 address, then use host on each returned result to see the IPv6 address for the server.