In this blog post, I will cover how to configure Nginx to support secure HTTPS and HTTP/2 connections for free using Let's Encrypt, a free SSL/TLS certificate provider. This tutorial blog post assumes your Internet accessible Nginx server is directly serving content to clients and is not routing traffic through a CDN intermediary such as CloudFlare, Akamai, or Amazon CloudFront. You can also support secure connections with Nginx and a CDN, but will encounter provider-specific configuration steps that will not be outlined in this blog post.

You can skip the introduction and get directly to the step-by-step instructions by going to Configuring Nginx with Let's Encrypt

Overview

Unlike the default HTTP protocol supported by Nginx, HTTPS and HTTP/2 are secure protocols that allow clients to verify the authenticity of a server claiming to be the host of a particular domain. Additionally, secure connections ensure the privacy and integrity of data exchanged between a client and a web server. HTTPS adoption is an evolving landscape. Historically, HTTPS was used almost exclusively for payment processing and to facilitate secure web page logins. However, HTTPS everywhere is becoming more widely accepted both for security considerations as well as other reasons mentioned later in this blog post. Google's Transparency Report includes a HTTPS report card with interesting figures on the adoption rate of HTTPS and HTTP/2 connections. As of October 2016, on Google's HTTPS on Top Sites Transparency Report Section, 39 out of the top 100 non-Google sites support serving all content via secure HTTPS or HTTP/2 connections by default. One can only expect this number to increase in the near future.

What's Let's Encrypt?

Certificate authorities issue X.509 certificates that can be used to support secure HTTPS connections. Historically, obtaining a certificate from a authority such as Verisign, GeoTrust, or Comodo has been quite expensive. The Let's Encrypt foundation was created to offer free certificates with the goal of increasing the adoption of secure connections. Let's Encrypt has simplified the certificate configuration process. If you want features, such as extended validation (EV certs), you'll still need to shell out money to a traditional certificate authority. However, Let's Encrypt certificates are sufficient for offering secure connections to a web server.

Reasons to support secure connections

The usage scenario for your Nginx server dictates the importance of offering secure connection support. The primary reason for supporting secure HTTPS and HTTP/2 connections is to provide clients authentication (a means of verifying the identity of the server hosting content), privacy, data integrity (preventing man-in-the-middle attacks etc.). Whenever a site deals with personal user information, it has an ethical obligation to serve sensitive content over secure connections. A large number of sites, such as my blog, host publicly accessible non-sensitive information. It may initially seem less important for this category of sites to offer secure connections. However, from a security standpoint, HTTPS and HTTP/2 protects the integrity of your site, keeping your visitors safe. Additionally, there are also non-security related benefits of supporting secure connections on your server.

Higher search result rankings

According to Google, HTTPS connection support is used as a positive ranking signal for your site. This signal currently carries less weight than other factors such as content. However, Google has indicated in various blog posts and public announcements that it may increase the weight of secure connections in the future. If site rankings are important to you, you may want to consider supporting HTTPS connections for this reason alone.

Improved site performance with HTTP/2

The HTTP standard is widely responsible for making the Internet what it is today. While this protocol has been robust and powered websites for years, it is not well-suited to satisfy some of the evolving needs and usage patterns of modern web sites and web applications. HTTP/2 is an improved protocol intended to make client and server communication faster with lower latency and reduced data transfer sizes. HTTP/2 is notably different from the original HTTP 1.1 protocol. If you are interested in more technical details on the HTTP/2 specification, the HTTP/2 GitHub FAQ is a great starting place. Some improvements in the new protocol include request multiplexing, improved compression of HTTP headers, elimination of redundant HTTP headers, binary transport, and server data pushing.

All major browsers and web severs and proxies support this efficient protocol. In fact, several sites you visit daily including Google, Facebook, Wikipedia, and this fantastic blog are already supporting HTTP/2 on their production sites.

Nginx supports the HTTP/2 application protocol, but can only support HTTP/2 if your server is configured to support connections secured with a SSL/TLS certificate signed by a certificate authority trusted by the requesting client's certificate store. While the HTTP/2 protocol specification does not mandate secure transport, all major browsers only support HTTP/2 over secure connections. A HTTP/2 capable client can indicate that it would like to upgrade a connection to HTTP/2 using with the application-layer protocol negotiation (ALPN) extension. If the responding server supports HTTP/2, it can upgrade the connection to the more efficient protocol.

Certain new HTML5 features may only work over HTTPS and HTTP/2 connections in the future

Proposals for browsers to limit certain HTML5 features depending on the security of their connection are underway. For example, the September 2016 Draft Secure Contexts Specification proposes only allowing features such as Full Screen, Web Bluetooth and GeoLocation API to operate within secure browsing contexts due to the potentially sensitive information that can be obtained from these APIs. The rationale is that you would only want to share such sensitive information with servers that you trust.

Chrome will start indicating HTTP sites as being insecure

Google has stated in a Online Security Blog Post that beginning in January, 2017 it will begin marking HTTP websites as insecure in Chrome version 56. Initially, only pages that collect passwords or credit card information will present insecure warnings to end users. As time progresses, Google plans to ratchet up the warnings to a level where any HTTP site will be marked as insecure. It would be prudent to upgrade your site's security before users start seeing these warnings.

Objections to supporting secure connections
  • Security is hard

    • It takes considerable more effort to configure, maintain, and troubleshoot secure connections. However, Nginx and Let's Encrypt make this process reasonably intuitive.
    • All SSL/TLS certificates have expiration dates, requiring them to be periodically renewed. Once a certificate has expired, clients will receive security errors while visiting your site. Paid SSL/TLS certificate authorities offer certificates valid for a year or longer. However, Let's Encrypt will only grant certificates with a three-month duration, requiring frequent certificate renewal (which you can automate).
  • Mixed content warnings

    • If a HTTPS or HTTP/2 page attempts to load resources over insecure connections, nearly every browser will display warnings for mixed-content and will not execute JavaScript files or load iframes from insecure sites. For more information on this, What Is Mixed Content? is a good starting reference. Wired Magazine recently upgraded their site to support HTTPS connections everywhere and has documented how they addressed mixed content warnings in their article How WIRED Completely Encrypted Itself. It's easier to support secure connections on a new site than it is to update an existing site to support secure connections. If your site loads insecure content from third-party domains, securing your site could be more involved.
  • Performance degradation
    • Some may be hesitant support HTTPS connections out of concern that additional computational resources will be required and their site's performance will ultimately take a hit. At the Progressive Web App Summit 2016, there was a great Mythbusting HTTPS session that attempts to address these concerns and explains how HTTPS can potentially improve your site's performance.
    • HTTP/2 proponents claim the modern protocol makes sites more efficient. The negligible performance hit required to establish a secure connection is overshadowed by the performance gains provided by HTTP/2. Let's Encrypt has a live demonstration of the performance differences between a page served over HTTP and HTTP/2 at HTTPvsHTTPS.com.
  • Cost
    • TLS/SSL certificates have historically been somewhat expensive. Now that firms, such as Let's Encrypt offer free non-EV certificates, the cost of purchasing a certificate should no longer be a limiting factor. For example, this blog is using a free certificate from Let's Encrypt which is supported by every major browser.

Configuring Nginx with Let's Encrypt

  1. Check prerequisites
  2. Install Let's Encrypt client (Certbot)
  3. Configure well-known directory
  4. Request a certificate from Let's Encrypt
  5. Generate a strong Diffie-Hellman parameter group
  6. Configure Nginx
  7. Verify secure connection

Step 1: Check prerequisites

In order to support HTTP/2 connections, Nginx must use OpenSSL version 1.0.2 or higher. More recent versions of OpenSSL support the Application-Layer Protocol Negotiation Extension ALPN (ALPN), which is used to negotiate HTTP/2 connections. The Google Chrome development team announced that it would stop supporting NPN in favor of ALPN for negotiating HTTP/2 connections beginning in May 2016. Ubuntu Server LTS 16.04 or higher ships with OpenSSL 1.0.2 out of the box, without any custom modifications.

Nginx began supporting HTTP/2 in versions 1.9.5 and later. To check the version of Nginx installed on your server as well as what version of OpenSSL it us using, run the nginx -V commmand in a shell.

You should see output similar to:

nginx version: nginx/1.10.0 (Ubuntu)  
built with OpenSSL 1.0.2g-fips  1 Mar 2016 (running with OpenSSL 1.0.2g  1 Mar 2016)  
TLS SNI support enabled  

The instance of Nginx listed above is ready for HTTP/2 connections as well as ALPN.

Step 2: Install Let's Encrypt Client (Certbot)

You'll need to use Let's Encrypt to obtain a certificate. Let's Encrypt has renamed their client certbot, but it continues to be named letsencrypt in Ubuntu's repositories as of version 16.04. Ubuntu's repository naming may change in the near future.

On Ubuntu Server, you can install the Let's Encrypt client as follows:

sudo apt-get update  
sudo apt-get install letsencrypt  

Step 3: Configure well-known directory in Nginx

The Let's Encrypt Certbot supports several plugins that can be used to install and configure a certificate. Unfortunately, Ubuntu 16.04 doesn't currently support automatic certificate installation at the time of this writing, so the best option to use is the webroot plugin with the certonly command.

The webroot plugin will place a special file in the /.well-known directory within Nginx's document root. This file is publicly accessible by external HTTP clients, including Let's Encrypt's clients. By exposing this file, you are able to prove ownership of a domain to Let's Encrypt.

You need to ensure that the /.well-known directory is accessible to Let's Encrypt for validation by adding a .well-known location entry to the Nginx configuration file containing your server block, which is typically located in /etc/nginx/sites-available/.

Open the default Nginx configuration file replace %configuration_filename% with the name of your configuration file:

sudo nano /etc/nginx/sites-available/%configuration_filename%  

If it does not already exist, add the following location block entry to the configuration file inside the server block:

 # lets encrypt well-known webroot directory 
 location ~ /.well-known {
                allow all;
        }

While this file is open, verify what root directory is configured as the document root for Nginx. By default, the root directory is configured as root /var/www/html;

Save this file and exit.

Check the validity of your Nginx configuration file by running the following command:

sudo nginx -t  

If the configuration file is ok and error free, you should see a message similar to

nginx: syntax is ok  
nginx: test is successful  

If no errors were detected, restart Nginx with the following command

sudo service nginx restart  

Step 4: Request a certificate from Let's Encrypt

Assuming that Nginx's webroot path is set to /var/www/html, we can request the webroot plugin to request a SSL certificate for our server's domain(s). At a minimum, you should request certificates for both exampledomain.com and www.exampledomain.com. Replace exampledomain.com with the actual domain name for your Nginx server (e.g. chrisebert.net).

You can request a certificate for exampledomain.com and www.exampledomain.com by running the following command (replace exampledomain with your site's domain):

sudo letsencrypt certonly -a webroot --webroot-path=/var/www/html -d exampledomain.com -d www.exampledomain.com

If this is your first time requesting a certificate from Let's Encrypt, you will be prompted to enter your email address and agree to Let's Encrypt's terms of service.

If Let's Encrypt was successful, you should see an output message similar to:

IMPORTANT NOTES:  
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/exampledomain.com/fullchain.pem. Your cert will
   expire on 2017-01-02. To obtain a new version of the certificate in
   the future, simply run Let's Encrypt again.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
Generated files

After successfully obtaining a certificate from Let's Encrypt, the following PEM-encoded files will be accessible via a symbolic link at /etc/letsencrypt/live/exampledomain as follows:

  • cert.pem - the certificate for your server's domain (this is shared to the public)
  • chain.pem - The Let's Encrypt chain certificate
  • fullchain.pem - the combination of cert.pem and chain.pem
  • privatekey.pem - the private key for your certificate (this is a secret you keep)

You must note the location of these certificate files in order to reference them in your Nginx configuration files in a future step.

Step 5: Generate a strong Diffie-Hellman parameter group

The Diffie–Hellman (DH) algorithm is used by OpenSSL to help a client and a server to agree upon a shared secret, a large prime number, between them. An important step of the DH exchange is to ensure that both the client and the server are using the same set of DH parameters. Parameter generation is computationally expensive, and is usually performed once and reused for many key exchange sessions. For more detailed information about DH and its use in OpenSSL, you can refer to the this OpenSSL Diffie-Hellman Wiki Article. You'll likely want to generate a DH parameter group of wither 2048 or 4096 bits in length. 2048 bits is generally considered long enough to be sufficiently safe protection from brute force cracking and to not be too burdensome for connection handshakes. If you wear tin foil hats, you may opt to use a 4096-bit DH parameter group, but it is considered overkill in 2016.

To generate a 2048 bit DH parameter group for OpenSSL, run the following command:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048  

Step 6: Configure Nginx

Create a SSL configuration snippet

Create a Nginx SSL configuration snippet for your domain (replace exampledomain.com with the name of your server's domain):

sudo nano /etc/nginx/snippets/ssl-exampledomain.com.conf  

Once the configuration file is open add the following entries (these should match the locations outputted by the Let's Encrypt client in Step 4.

ssl_certificate /etc/letsencrypt/live/exampledomain.com/fullchain.pem;  
ssl_certificate_key /etc/letsencrypt/live/exampledomain.com/privkey.pem;  

Save the configuration snippet after you have made your modifications.

Configure SSL Parameters for Nginx

Next, create a Nginx snippet file to specify the TLS/SSL configuration parameters to be used for Nginx.

sudo nano /etc/nginx/snippets/ssl-params.conf  

The site cipherli.st has some great default configurations that can be used to securely configure Nginx and also obtain an A+ rating on SSL Lab's report. You can tweak these parameters for your specific needs. A default configuration you can paste into /etc/nginx/snippets/ssl-params.conf is:

# from https://cipherli.st/ October 2016
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;  
ssl_prefer_server_ciphers on;  
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";  
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0  
ssl_session_cache shared:SSL:10m;  
ssl_session_tickets off; # Requires nginx >= 1.5.9  
ssl_stapling on; # Requires nginx >= 1.3.7  
ssl_stapling_verify on; # Requires nginx => 1.3.7  
resolver 8.8.8.8 8.8.4.4 valid=300s;  
resolver_timeout 5s;  
# This enables Strict-Transport Security, if enabled clients will only support
# HTTPS connections to your domain following the initial successful HTTPS connection 
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";  
add_header X-Frame-Options DENY;  
add_header X-Content-Type-Options nosniff;

ssl_dhparam /etc/ssl/certs/dhparam.pem;  

After you have finished configuring the SSL parameters snippet, save the file.

  • This sample configuration file enables HSTS. You may want to consider not enabling strict-transport-security if you do not accept the ramifications of the HTTP Strict-Transport Security Policy. In a nutshell, enabling this parameter informs clients that they should only access your domain using secure connections for a specified period of time. If you enable this feature, and set a large max-age value, clients will not connect to your website unless it provides a valid, secure connection.
  • The example TLS/SSL configuration is considered secure, utilizing strong cipher suites. If you need to support older clients (Internet Explorer 9 or older, old versions of Java, older mobile operating systems, etc.), you'll need to use a less secure cipher configuration.
  • Note that we have configured the ssl_dhparam setting to point to the strong Diffie-Hellman parameter group we created at /etc/ssl/certs/dhparam.pem as part of Step 5
Configure Nginx to accept secure connections on port 443

Open your Nginx configuration file that contains your server block (typically located in /etc/nginx//sites-enabled) with sudo nano %configuration_file_name% where %configuration_file_name% is the name of your Nginx configuration file.

Find your server block and remove any listen entries for port 80 (which traditionally serves HTTP traffic):

listen 80 default_server;  
listen [::]:80 default_server ipv6only=on; #will not be present on non IPv6 server  
  • If your server does not support IPv6, you will not have a [::]:80 IPv6 listen entry.

Replace the old listen entries with the new entries, which will update your server to listen for secure connections on port 443 and also support HTTP/2 connections:

listen 443 ssl http2 default_server;  
listen [::]:443 ssl http2 default_server ipv6only=on;  #Remove line if not IPv6 server  
server_name exampledomain.com *.exampledomain.com localhost; # replace domain  
include snippets/ssl-exampledomain.com.conf; #step 6, replace domain  
include snippets/ssl-params.conf; #step 6  
  • Replace the server name domain values with your server's domain name
  • Remove the IPv6 listen entry if your server is not IPv6 capable
Configure Nginx to redirect port 80 connections to port 443

You'll want to add a Nginx server block to redirect traffic from port 80 (which traditionally serves HTTP traffic) to port 443 (which serves HTTPS traffic). This will automatically upgrade any HTTP connection requests to HTTPS or HTTP/2 connections.

Add a new "port 80" server block to the Nginx configuration file (your configuration file will now have two server blocks):

server {  
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on; #Remove line if not IPv6 server
    server_name exampledomain.com *.exampledomain.com localhost;
    return 301 https://$server_name$request_uri;
}
  • Replace the server name domain values with your server's domain name
  • Remove the IPv6 listen entry if your server is not IPv6 capable

Save the configuration file.

Verify that the configuration file is valid and does not have errors by running the following command:

sudo nginx -t  

If there are no errors, restart nginx with the following command:

sudo service nginx restart  

At this point, you should be able to access your website and verify that your browser is using a secure connection.

Step 7: Verify secure connection

Check SSL Labs Report Grade

Now that you have successfully configured your Nginx server to accept secure connections, you'll likely want to verify that your site is configured securely. SSL labs provides a free SSL Server Test, which will grade the security of the secure connections provided by your server. If you used the suggested configuration suggestions provided above, you should be able to achieve an A+ ratting as of October 2016. Below is an example report generated for chrisebert.net:
SSL Labs Report SSL Labs Report Part 2

Verify HTTP/2 connection support

There are several ways to verify whether your site supports HTTP/2 connections.

  • One method is to install the HTTP/2 and SPDY indicator Google Chrome extension. This extension will provide a visual indication when a server offers HTTP/2 connection support. If a site is offering HTTP/2 connections, a blue lightning bolt icon will appear near your address bar: Blue Indicator

  • An alternative method is to use Key CDN's HTTP/2 test page. If you supply a domain name, this test page will confirm whether or not the server supports HTTP/2 connections. From my experience, this page's availability is somewhat unreliable. However, when it is available it is quite useful.

  • There are also several command-line tools, such as curl or is-http2, which can be used to determine whether a site offers HTTP/2 connections

Next steps

Once you have configured Nginx to support HTTPS and HTTP/2 connections, you'll likely want to devise a way to automate the renewal of your Let's Encrypt certificate in order to avoid letting your certificate expire. Let's Encrypt will send an email reminder a few week prior to a certificate's expiration, informing its owner that the certificate must be renewed shortly.