Red Hat/CentOS: How to add HTTPS to an existing Nginx website (both with and without Let’s Encrypt)

(Yes, you read the title correctly. For extra fun, and to prevent this blog from being too focused on Ubuntu/Debian, this time I’ll be using Red Hat Enterprise Linux / CentOS (and, I assume, Fedora as well.) Later on, I may post a Debian-based version.)

Configuring a basic HTTP site on Nginx

(Note: if you already have a working HTTP site, you can skip to the next section (“Adding encryption…”))

Yes, the post title mentions an “existing” website, which I believe will be the case in most “real world” situations, but installing a new one is actually very easy on CentOS1. First, do:

yum -y install epel-release; yum -y install nginx

Then create a very basic configuration file for the (non-HTTPS) site, as /etc/nginx/conf.d/mysite.conf :

server {
        listen 80;
        server_name mysite.mydomain.com;
        root /var/www/mysite;

        location / {
        }
}

Then, of course, create the /var/www/mysite directory (CentOS doesn’t use /var/www by default, but I’m far too used to it to change. ūüôā ) If you’d like, create an index.html text file in that directory, restart nginx (“service nginx restart” or “systemctl¬†restart nginx“, depending on your system’s major version), and try browsing to http://mysite.mydomain.com . If it works, congratulations, you have a running web server and a basic site.

Adding encryption to the site (not¬†using Let’s Encrypt):

First, you need a key/certificate pair. I have tutorials for creating an RSA certificate or an ECDSA certificate.

Second, edit the site’s configuration file (in the “starting from scratch” example above, it’s “/etc/nginx/conf.d/mysite.conf“), and copy the entire server section so that it appears twice on that text file (one after the other). Pick either the original or the copy (not both!), and, inside it, change the line:

listen 80;

to:

listen 443 ssl http2;

(Note: the “http2” option is only available in Nginx 1.9.5 or newer. If your version complains about it, just remove it, or upgrade.)

Inside that section, add the following options:

ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/privatekey.crt;

(replacing the paths and file names, of course.)

This should be enough — restart Nginx and you should have an HTTPS site as well as the HTTP one.

And what if you want to disable HTTP for that site and use HTTPS only? Just edit the same configuration file, look for the¬†server section you didn’t change (the one that still includes “listen 80;“), and replace the inside of that section with:

listen 80;
server_name mysite.domain.com;
return 301 https://mysite.domain.com$request_uri;

Afterward, while you’re at it, why not go for an A+ rating on SSL Labs¬†(skip the certification creation part from that tutorial, you’ve done it already) and an A rating on securityheaders.io?

Adding encryption to the site (using Let’s Encrypt):

First, install certbot:

yum install python2-certbot-nginx

Then check if Nginx is running and your normal HTTP site is online (it’ll be needed in a short while, to activate the certificate). If so, then enter:

certbot --nginx --rsa-key-size 4096 -d mysite.mydomain.com

(replacing “mysite.mydomain.com” with yours, of course.)

Answer the questions it asks you: a contact email, whether you agree with the terms (you need to say yes to this one), if you want to share your email with the EFF, and finally if you want “No redirect” (i.e. keep the HTTP site) or “Redirect” (make your site HTTPS only).

And that’s it (almost — see the next paragraph) — when you get the shell prompt back, certbot will already have reconfigured Nginx in the way you chose in the paragraph above, and restarted it so that it’s running the new configuration. You may want to add “http2” to the “listen 443 ssl;” line in the configuration file (it’ll probably be the default someday, but as of this post’s date it isn’t), and don’t forget your options for improved security and security headers.

Only one thing is missing: automatically renewing certificates. Strangely, the certbot package configures that automatically on Ubuntu, but not on CentOS, from what I’ve seen (please correct me if I’m wrong). The official Let’s Encrypt docs recommend adding this (which includes some randomization so that entire timezones don’t attempt to renew their certificates at precisely the same time) to root’s¬†crontab:

0 0,12 * * * python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew 

(Note: It’s possible to use Let’s Encrypt to create ECDSA certificates, but as of this writing you have to do most of the work manually (creating a CSR, etc.), and you lose the automatic renewal, so for the moment I suggest using RSA certificates. I hope this changes in the future.)

Nginx: How to get an A (or A+) rating on securityheaders.io

Some time ago, I wrote about how to set up Nginx to get an A+ rating on SSL Labs (focusing specifically on an ECDSA certificate, but you can certainly achieve the same with a (more common) RSA certificate). SSL Labs' test is very comprehensive, warning the user of most potential problems, including SSL/TLS implementation bugs (update your software!), misconfigurations, problems with the certificate itself, etc.. But there is another similar tool, securityheaders.io, which focuses on something different: security-related headers. Such headers are sent by the web server (such as Nginx or Apache) and affect browsers' security in several ways, unrelated to encryption, protocols or the certificate used (these are tested by SSL Labs).

For more details about those headers, I'd suggest you navigate to securityheaders.io itself, and then check your site in it, then follow the links for each suggestion. Meanwhile, if your site uses Nginx, here's a "quick and dirty" guide for getting an A rating (and, yes, I'll mention what you'd need for an A+ rating, and also why I choose not to try for one).

Nginx setting Explanation
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; Enforces HTTPS on the entire site. Don't use if you still need to provide HTTP, of course.
add_header X-Content-Type-Options nosniff; Prevents browsers from trying to "guess" MIME types and such, forcing them to use what the server tells them.
add_header X-Frame-Options SAMEORIGIN; Stops your site from being included in iframes on other sites.
add_header X-Xss-Protection "1" always;  Activates cross-scripting (XSS) protection in browsers.
add_header Referrer-Policy "unsafe-url";  Makes the site always send referrer information to other sites. NOTE: this is not the most secure setting, but it's the one I prefer; see below.
add_header Content-Security-Policy "default-src https: data: 'unsafe-inline' 'unsafe-eval'";  Forces TLS (don't use if you still need to provide HTTP); prevents mixed content warnings. NOTE: again, this is not the most secure setting; see below.

You're probably wondering about the last two settings. I choose to send referring information to other sites for a simple reason: I like to see where my own visitors come from (I don't sell, share or monetize that in any way, it's just for curiosity's sake), and I'm a firm believer in treating others as I want to be treated. If you don't care about that, you may want to change this to "no-referrer-when-downgrade", or "strict-origin".

As for the Content Security Policy, anything more "secure" than the above prevents (or at least makes it a lot more headache-inducing) the use of inline scripts and/or external scripts, which would mean no external tools/scripts/content (or at least a lot of work in adding every external domain to a list of exceptions), and a lot of hacking on software such as WordPress (seriously: add the setting, but then remove the "unsafe-*" options and see what stops working...). You might use the most paranoid settings for an internally developed, mostly static site, but I fail to see the point. So, this and the paragraph above are the reasons for an A rating, instead of an A+.

How to configure TLS encryption in Dovecot

DovecotAs with most other internet services, Dovecot can be configured to use TLS encryption — and, unlike some others (such as web servers or SMTP servers), there’s little reason not to¬†enforce it.

Notes:

  • Like other recent TLS tutorials on this blog, this is, of course, not a full Dovecot guide — that would be far too complex for a blog post. Instead, it’s just focused on enabling/configuring TLS encryption. I’ll be assuming you already have Dovecot configured, and are able to access mailboxes on your server using an email client.
  • I’ll be focusing on IMAP only, not POP3 or anything else.
  • The server will be configured to require¬†encryption, for both privacy and security reasons. I don’t know of any modern email client that doesn’t allow encrypted connections (and even then, you might work around it by configuring an encrypted tunnel, but that falls out of this post’s scope).

1. Getting a certificate

While I believe most modern email clients will support ECDSA certificates, such clients are not a known quantity like, say, web browsers are, so I suggest that you create an RSA certificate, unless you already have one whose Common Name (CN) matches your¬†server’s public name (e.g. mail.domain.com). Dovecot versions 2.2.31 and newer support configuring¬†alternative certificates so that you could support both kinds at the same time, but there’s probably little gain in doing that, since most clients will likely default to RSA anyway (and, unless you’re using Let’s Encrypt, certificates are not free).

So, put your certificate and private key in /etc/dovecot/. Let’s assume the certificate is called myserver-full.crt¬†1, and the private key is myserver.key. You should protect the private key from any users on your server, so do, for instance,

chown root:dovecot /etc/dovecot/myserver.key
chmod 640 /etc/dovecot/myserver.key

2. Configuring Dovecot for TLS

I’m assuming your Dovecot installation has a basic configuration file in /etc/dovecot/dovecot.conf, but the real “meat” of the configuration is in included files in /etc/dovecot/conf.d/. Your system may be slightly different, but I’m sure you can adapt. ūüôā Also, the numbers at the beginning of the file names may differ in your system.

This one isn’t really related to TLS, but it’s a good idea: edit /etc/dovecot/conf.d/20-imap.conf, and look for a line like this:

mail_plugins = $mail_plugins

and add to it (separated by a space):

imap_zlib

After all, there’s no reason not to use compression here, and bandwidth (especially on mobile) is still precious.

Now, edit /etc/dovecot/conf.d/10-ssl.conf, and add 2:

ssl = required
ssl_cert = </etc/dovecot/myserver-full.crt
ssl_key = </etc/dovecot/myserver.key
ssl_cipher_list = ECDHE-RSA-CHACHA20-POLY1305:ALL:!LOW:!SSLv2:!EXP:!aNULL
ssl_protocols = !SSLv2 !SSLv3
ssl_prefer_server_ciphers = yes

(The ssl_cipher_list line, besides setting secure defaults, sets the ChaCha20 protocol as the first one to be tried, since it’s considered one of the fastest and most secure. Note that it’ll require Dovecot linked to LibreSSL or OpenSSL 1.1.x to use that cipher, though Dovecot won’t complain if it doesn’t have access to it; it’ll just use the normal, secure defaults.)

If you don’t change anything else from the default configuration, the server will be listening on port 143 (IMAP) and port 993 (IMAPS). “But,” you ask, “isn’t standard IMAP unencrypted? I thought you were enforcing encryption…” Yes, but the standard IMAP port can be “upgraded” to TLS by entering the STARTTLS command, and email clients not only support that, but typically default to it (if not, just make sure you enable it). The server will refuse to authenticate any users on port 143 before they’ve “STARTTLSed”.

If you wanted to use only IMAP+STARTTLS, or only IMAPS, just edit /etc/dovecot/conf.d/10-master.conf,¬† look for the “service” configuration, and disable the one you don’t want. But I see no problem with keeping both enabled.

Thoughts? Questions?

(To do the same to SMTP / Postfix, please see How to configure TLS encryption in Postfix.)

How to create an RSA certificate

Whether it’s a web server, an email server (two, in fact, assuming you’re using one for SMTP and another for IMAP, as is common), or other types of applications, to have encrypted connections a TLS certificate¬†1 is required. It can be self-signed (say, if you’ll be the only one needing to access that server), but browsers and email clients will complain (loudly!), therefore, if you want your server to be universally accessed, a “real” certificate is needed, so let’s show how to get one.

This recipe creates an RSA certificate (strongly suggested for, say, Postfix, since you can’t control what other email servers support, so you should go for the most common option); for web servers (accessed by standard browsers), an ECDSA certificate might be a good alternative.

A few notes:

  • There are alternatives (e.g. key sizes, etc.) for basically every parameter I’m using, but I’m not going¬†into those. This is supposed to be as quick and easy as possible, after all.
  • Similarly, I’m not going into¬†Let’s Encrypt; instead, I’m assuming a “normal” certificate authority such as Comodo. If you do use Let’s Encrypt, just use it to create the certificate instead of the “send the CSR to the certification authority” part.
  • The certificate (and server) will be compatible with most browsers and applications (email clients, etc.), but that “most” won’t include any Microsoft browsers on¬†Windows XP (Firefox or Chrome on that abomination of an OS will still work).

Generating the key and the Certificate Signing Request (CSR):

openssl req -nodes -newkey rsa:4096 -keyout myserver.key -out myserver.csr

The command will ask you for details about your server/company (location, etc.). You should fill in every field, although the only mandatory one is “Common Name” (CN), which must¬†match your server’s public name (not necessarily the machine’s name, but the hostname people will type in the browser/email client, such as “zurgl.com” or “mail.something.com“. Note that a certificate for “domain.com” also includes “www.domain.com” (so don’t include the “www.” in the CN), but if the server is reached at “subdomain.domain.com“, then¬†that’s what the CN needs to be.

Ordering and receiving the new certificate:

Now go to a certification authority (CA), order a new certificate, and when asked for a CSR, send them (usually you can just copy and paste it into a text entry window) that myserver.csr file.

If everything went well, then the CA should email you the new certificate in a short while. Typically they send you two files: the certificate itself, and a couple of “intermediate” certificates. Only the first is really needed, but I’ve had best results with¬†concatenating your certificate (first) and the intermediate certs (last) into a single file, which you might call myserver-full.crt¬†.

That file is, to all intents and purposes, your new certificate, and it’s ready to be used in application servers. You can use the same certificate for several services (e.g. an HTTPS website, an SMTP server, an IMAP server), as long as the hostname matches the certificate’s CN (so a certificate for “mail.myserver.com” won’t work for “www.myserver.com“, and vice-versa, but that’s because the names don’t match — not because they’re different services).

Adding TLS encryption (with your shiny new certificate) to internet services:

Here’s a (growing) list of tutorials:

(Note: most of this post’s content originally appeared in¬†a previous one. ¬†The difference is that that one refers to ECDSA certificates, while this one is about the older, more common RSA certificates.)

How to create an ECDSA certificate

Whether it’s a web server, an email server (two, in fact, assuming you’re using one for SMTP and another for IMAP, as is common), or other types of applications, to have encrypted connections a TLS certificate¬†1 is required. It can be self-signed (say, if you’ll be the only one needing to access that server), but browsers and email clients will complain (loudly!), therefore, if you want your server to be universally accessed, a “real” certificate is needed, so let’s show how to get one.

In this tutorial, I’ll be creating an ECDSA certificate with an EC key, instead of the more usual RSA type; ECDSA is more modern and is theoretically more secure even with smaller keys. If you need an RSA certificate (which I’d recommend for Postfix, for instance, since you can’t control what other email servers around the world support, so, you should go for the most common option; for web servers, ECDSA is fine), see How to create an RSA certificate.

A few notes:

  • There are alternatives (e.g. key sizes, etc.) for basically every parameter I’m using, but I’m not going¬†into those. This is supposed to be as quick and easy as possible, after all.
  • Similarly, I’m not going into¬†Let’s Encrypt; instead, I’m assuming a “normal” certificate authority such as Comodo (which supports ECDSA certificates; if you choose another, you should¬†make sure¬†of that¬†in advance). If you do use Let’s Encrypt, just use it to create the certificate instead of the “send the CSR to the certification authority” part.
  • The certificate (and server) will be compatible with most browsers and applications (email clients, etc.), but that “most” won’t include any Microsoft browsers on¬†Windows XP (Firefox or Chrome on that abomination of an OS will still work).

Generating the key and the Certificate Signing Request (CSR):

openssl ecparam -genkey -name secp384r1 | openssl ec -out myserver.key

openssl req -new -key myserver.key -out myserver.csr

The second command will ask you for details about your server/company (location, etc.). You should fill in every field, although the only mandatory one is “Common Name” (CN), which must¬†match your server’s public name (not necessarily the machine’s name, but the hostname people will type in the browser/email client, such as “zurgl.com” or “mail.something.com“. Note that a certificate for “domain.com” also includes “www.domain.com” (so don’t include the “www.” in the CN), but if the server is reached at “subdomain.domain.com“, then¬†that’s what the CN needs to be.

Ordering and receiving the new certificate:

Now go to a certification authority (CA), order a new certificate, and when asked for a CSR, send them (usually you can just copy and paste it to a text entry window) that myserver.csr file.

If everything went well, then the CA should email you the new certificate in a short while. Typically they send you two files: the certificate itself, and a couple of “intermediate” certificates. Only the first is really needed, but I’ve had best results with¬†concatenating your certificate (first) and the intermediate certs (last) into a single file, which you might call myserver-full.crt¬†.

That file is, to all intents and purposes, your new certificate, and it’s ready to be used in application servers. You can use the same certificate for several services (e.g. an HTTPS website, an SMTP server, an IMAP server), as long as the hostname matches the certificate’s CN (so a certificate for “mail.myserver.com” won’t work for “www.myserver.com“, and vice-versa, but that’s because the names don’t match — not because they’re different services).

Adding TLS encryption (with your shiny new certificate) to internet services:

Here’s a (growing) list of tutorials:

(Note: most of this post’s content originally appeared in¬†a previous one. ¬†The reason for its creation is that I’m planning several other TLS-related posts where a certificate will be required and, to avoid repeating the “how to create a certificate” instructions in every single one of them, I’d rather have this post to link to when needed.)

How to set up an Nginx HTTPS website with an ECDSA certificate (and get an A+ rating on SSL Labs)

Most people who maintain web servers (or email servers, or…) have probably had to deal with SSL/TLS certificates in the past, but the process is, in my opinion, typically more complex than¬†it should be, and¬†not that well documented, typically forcing the user to consult several tutorials on the web so that they can adapt parts of each for their needs. Since I had to renew a couple of certificates last week, I thought about¬†writing a quick and easy tutorial for a particular¬†case: HTTPS on Nginx (although the certificate itself could, of course, be used for¬†other¬†services; right now¬†I have one I’m using for IMAP (Dovecot) and SMTP (Postfix) as well). For extra fun, I’ll be creating/using an ECDSA certificate with an EC key, instead of the more usual RSA type; ECDSA is more modern and is theoretically more secure even with smaller keys.

A few notes:

  • There are alternatives (e.g. key sizes, etc.) for basically every parameter I’m using, but I’m not going¬†into those. This is supposed to be as quick and easy as possible, after all.
  • Similarly, I’m not going into¬†Let’s Encrypt; instead, I’m assuming a “normal” certificate authority such as Comodo (which supports ECDSA certificates; if you choose another, you should¬†make sure¬†of that¬†in advance). If you do use Let’s Encrypt, just use it to create the certificate instead of the “send the CSR to the certification authority” part.
  • The certificate (and server) will be compatible with most browsers, but that “most” won’t include any Microsoft browsers on¬†Windows XP (Firefox or Chrome on that abomination of an OS will still work).
  • If you already have a certificate (whether ECDSA or not, whether from Let’s Encrypt or not) and you’re here just for the A+ rating on SSL Labs, skip to “Setting up Nginx” below.

Generating the key and the Certificate Signing Request (CSR):

openssl ecparam -genkey -name secp384r1 | openssl ec -out myserver.key

openssl req -new -key myserver.key -out myserver.csr

The second command will ask you for details about your server/company (location, etc.). You should fill in every field, although the only mandatory one is “Common Name” (CN), which must¬†match your server’s public name (not necessarily the machine’s name, but the host name people will type in the browser, such as “zurgl.com“. Note that a certificate for “domain.com” also includes “www.domain.com” (so don’t include the “www.” in the CN), but if the server is reached at “subdomain.domain.com“, then¬†that’s what the CN needs to be.

Ordering and receiving the new certificate:

Now go to a certification authority (CA), order a new certificate, and when asked for a CSR, send them (usually you can just copy and paste it to a text entry window) that myserver.csr file.

If everything went well, then the CA should email you the new certificate in a short while. Typically they send you two files: the certificate itself, and a couple of “intermediate” certificates. Only the first is really needed, but I’ve had best results with¬†concatenating your certificate (first) and the intermediate certs (last) into a single file, which you might call myserver-full.crt¬†. Put it somewhere Nginx can access (e.g. /etc/nginx), and also the key you generated earlier (in this example, myserver.key). You¬†don’t need the CSR there, by the way; it was just needed for ordering the new certificate.

Setting up Nginx:

This is, of course, not a complete Nginx tutorial (that would take a lot more space than a single post), just a simple recipe for configuring an HTTPS site with your new certificate, and have it be secure (and get a great score at SSL Labs, too). I’m assuming you can take care of all the non-HTTPS bits.

So, inside a virtual host, you need the server section:

server {
        listen 443 ssl http2; # if your nginx complains about 'http2', remove it, or (better yet) upgrade to a recent version
        server_name mysite.com # replace with your site, obviously

        # all the non-HTTPS bits (directories, log paths, etc.) go here

        # the rest of the recipe -- see below -- can go here
}

See the last comment? OK, let’s begin adding stuff there (I won’t indent any configuration lines from now on, but they look better if aligned with the rest of the configuration inside the { } ).

ssl_certificate /etc/nginx/myserver-full.crt; # the certificate and the intermediate certs, as seen above...
ssl_certificate_key /etc/nginx/myserver.key; # ... and the key

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS:!3DES';
# the above comes from Mozilla's Server Side TLS guide

add_header X-Content-Type-Options nosniff;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;	# basic defaults.

Great, you now have a basic HTTPS server! I suggest you try it now: save the configuration, restart Nginx, and test it. Access the URL with a browser (and check if the browser doesn’t complain about the certificate: if it did, something went wrong), and run it through SSL Labs’ Server Test. Check if it complains about something; if so, something needs fixing (ask in the comments, maybe I or someone else can help).

If all went well, you probably got a decent score, maybe even an A. But how to get an A+, like I promised at the beginning?

An A+ score on SSL Labs’ Server Test:

As of now (September 2017), SSL Labs only asks for one more thing: “HTTP Strict Transport Security (HSTS) with long duration”. HSTS is a mechanism to tell browsers: “this site should be accessed through HTTPS only; if you attempt to connect through normal HTTP, then deny access”. This information is actually cached by the¬†browser, it’s not the¬†server that refuses HTTP connections (though it’s, of course, possible to configure Nginx to do just that — or simply do 301 redirects to the equivalent HTTPS URL. But I digress…). In short, it protects against downgrade attacks. As for the¬†“with long duration” part, that’s simply the time that browsers should cache that information.

IMPORTANT: don’t proceed until you have the basic HTTPS site running, and SSL Labs reporting no problems (see the previous section)!

ALSO IMPORTANT: if you *do* want your site to be accessible through both HTTP and HTTPS, stop here, as the rest of the configuration will make it HTTPS-only! That means, however, giving up on the A+ score.

So, add the following:

add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";

Restart Nginx, try SSL Labs’ test again. Did you get an A+?

If not, or you have any questions, feel free to ask.

For extra fun: have your Nginx use¬†LibreSSL or OpenSSL 1.1.x, and enjoy a few more modern ciphers (look for “ChaCha20” on SSL Labs) without any configuration changes from the above.