Kuba Suder's blog on Cocoa and web development

Setting up an HTTPS site on Nginx

Categories: Linux 0 comments

This is my SSL configuration guide. There are many like it, but this one is mine…

Last week I needed to set up my first HTTPS site for Hive Mac, and I went ahead and did the same thing for my new blog domain. It took some figuring out, so I’ve written this all down, if only to save myself some time next time I need to do this.

Here’s what you need to do:

Choose a Certificate Authority and buy a certificate

The way the system works is that the certificate you receive when visiting an HTTPS site is verified by another certificate owned by your CA, which might in turn be verified by another certificate higher up, until you reach a root certificate which is supposed to be already installed on your system, like this:

There are a few dozens of root CAs and you probably have about a hundred of their certificates installed on your OS:

What you need to provide as a server owner is the bottom certificate that belongs to you (which you buy) and all the certificates up the chain (which you can download from the CA’s site) except the one at the top (which the users should already have).

To get your certificate, you need to buy it from one of those CAs. They usually cost somewhere between $10 and $100 a year for the most basic one (“Domain Validation”), a couple times more for a wildcard certificate (for *.yourdomain.com) and much, much more for an EV (Extended Verification) certificate – it’s the one that shows the company name in the address bar apart from a green lock:

mBank certificate

These are much more expensive, because they require passing very thorough and formal verification to ensure that the company is what they claim to be and that they’re following all required procedures.

It might also be possible for you to get a certificate for free. StartSSL offers free certificates for non-commercial sites, GlobalSign offers free certs for open source projects, and Mozilla with a few other organizations are working on a free CA for everyone (currently work in progress).

When choosing a CA, make sure they don’t generate SHA-1-signed certificates. This is an older algorithm that is being phased out because it’s less secure, and if your site uses it, you’ll see a warning like this:

The site is using outdated security settings that may prevent future versions of Chrome from being able to safely access it

New certificates should use the SHA-256 (SHA-2) algorithm instead. Despite that, some CAs (e.g. Nazwa.pl) still issue new certificates using SHA-1, claiming they’re perfectly fine, so be sure to check before you pay. I ended up getting the certs from Certum, a Polish root CA.

Generate a private key

Private key is something that is kept on your server to encrypt all HTTPS communication, and as the name implies, is not supposed to be revealed to anyone. Anyone who has access to it can pose as your site to its visitors.

To generate a key, run this command on the server:

sudo openssl genrsa -out yoursite.key 2048

2048 means that we’re creating a 2048-bit key, which is the recommended length now. Normally the key is kept somewhere in the web server configuration directory, e.g. /etc/nginx/ssl.

You should probably also make sure that only the root can access the key:

sudo chmod 600 yoursite.key

BTW: this article includes a nice list of all relevant OpenSSL commands related to keys, certificates and CSRs that you might need.

Generate a Certificate Signing Request

After (or before) you pay for the certificate, the CA will ask you for a CSR. Some CAs provide web forms that make this easier and create the CSR (together with the private key) for you, but ideally you should also generate the CSR yourself, like this:

sudo openssl req -new -sha256 -key yoursite.key -out yoursite.csr

This command will ask you several questions about your country (two-letter code), city, company name and so on. Some CAs might require all of those to be filled, some might only need the country code (e.g. “PL”) and “common name” (that’s your domain name, e.g. “yoursite.com”). If they don’t specify what they require, you can try submitting a CA with only those two things, and then fix it if it’s rejected.

A few tips about the fields:

  • if you enter an email, it should probably be in the domain you’re creating the certificate for (e.g. admin@yoursite.com)
  • if you’re wondering what to put in the “state or province” field, in Poland it’s e.g. “malopolskie” (lowercase and without diacritics), other countries will have different rules
  • if you want to leave a field empty, enter a single dot – just pressing enter will leave the default value which usually doesn’t make sense
  • take into account that some or all of the fields from the CSR might end up being visible to the end users in the certificate info:

Kontakt Sp. z o.o., Krakow, malopolskie, PL

You can display the details of a generated CSR using this command:

openssl req -text -noout -verify -in yoursite.csr

When you’re ready, paste the CSR text into the text field in the web form.

Confirm ownership of the domain

To issue a certificate for the given domain, the CA obviously needs to verify that you actually own the domain and aren’t just trying to hack it.

The most common way to do this is to receive an email on the domain. You can usually choose one of a few logins (admin@, postmaster@ etc.) and the CA will send you an email with a code to that address. For that, you need to have an SMTP server configured there.

Some CAs will also allow you to verify the domain through other means, e.g.:

  • uploading a file to the server and making it available through HTTP
  • adding a meta tag to a page on your site
  • adding a text entry to the DNS records

Create a certificate bundle

The HTTP server requires a certificate bundle file that includes the entire chain of certificates in the right order. However, for some reason the CAs usually don’t create that for you directly.

Once you complete the verification, you should be able to download a certificate file that includes your own certificate (the one at the bottom of the chain). To get the rest, you need to download it separately – it should be either in the same account area where your certificate is, or on some kind of help/support page (since it’s the same for everyone).

Make sure that both of those files are in text (PEM) format (with the BEGIN/END CERTIFICATE tags). If they’re binary files, use this command to convert them:

openssl x509 -inform der -in certificate.der -out certificate.crt

Once you have the two text files, simply concatenate them together into one file; the certificates should be in reverse order, from the bottom to the top, so your own goes first:

cat certificate.crt certumpl-certs.crt > yoursite.crt

Look inside to make sure everything looks right in case e.g. some newlines were missing at the end etc. Then upload the certificate to your server.

You can also show the details of a certificate using openssl:

openssl x509 -text -noout -in yoursite.crt

Configure Nginx

The bare minimum you need to do in your Nginx config is enable SSL and set the location of the private key and certificate bundle:

listen 443 ssl;
ssl_certificate /etc/nginx/ssl/yoursite.crt;
ssl_certificate_key /etc/nginx/ssl/yoursite.key;

This can be either in the same server block as the HTTP version of your site, if you want to share all the settings, or (usually) in a separate server block.

You should also make sure that the server is configured to use the right protocols and ciphers. A good idea is to use the list recommended by Mozilla (“Intermediate compatibility”). You’ll definitely want to make sure that SSLv3 (enabled by default) is disabled, since it has some vulnerabilities.

It should look more or less like this:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers .........;
ssl_prefer_server_ciphers on;

In general, it might be good to read the whole “Configuring HTTPS servers” and “Module ngx_http_ssl_module” doc pages on the Nginx site.

You should also consider tweaking some performance settings. I won’t pretend I know exactly what the right settings are, but this seems to be the most often recommended setup:

ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;

And something not specific to Nginx, but just as important: remember to enable the HTTPS port (TCP 443) on your firewall :)

DHE parameters file

If you use the Mozilla cipher list linked above, then some of the ciphers will require an additional “dhparam” file with some large numbers used by the cipher algorithm when establishing connection (see the Mozilla wiki page again for more info). It will work without one, but then it will use default values which aren’t good enough anymore (as of this week, SSLLabs marks the 1024-bit version of those ciphers as “Weak”).

To fix that, you need to do two additional things:

1) Generate a 2048-bit dhparam file using OpenSSL (it took about a minute on my VPS):

sudo openssl dhparam -out dhparam.pem 2048

2) Tell Nginx to use it:

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

Test the certificate

The basic test is to just open your HTTPS site in a browser. If it works, you don’t get any alerts, you see a green lock and no warnings in the certificate details panel, then you should be good. If not, you’ll need to debug the problem.

The SSL Labs Server Test tool is a great way to test everything in your SSL certificate and setup. It might not tell you exactly what to do, but if anything is wrong, it will find it.

You can also use this openssl command:

openssl s_client -connect yoursite.com:443 -CAfile /usr/local/share/ca-bundle.crt -status

You’ll need to provide a bundle file with the root certificates – if you’ve installed anything from homebrew or macports, it’s quite likely that you have a copy somewhere in /usr. The output from this command will probably be hard to read, but it can also lead you in the right direction if you can’t figure out the problem.

Here are a few errors you can get:

“This server’s certificate chain is incomplete”, “Extra download”, “unable to get local issuer certificate”

Some intermediate certificates are missing from the bundle; you’ve either forgotten to include the shared certificates from the CA, or you’ve downloaded an incorrect version of those (e.g. SHA-1 instead of SHA-2).

“This server accepts the RC4 cipher, which is weak”

Use the cipher list from Mozilla, and make sure the config file is actually included by Nginx.

“The server does not support Forward Secrecy with the reference browsers”

Make sure you include ssl_prefer_server_ciphers on in the Nginx config.

“Incorrect certificate because this client doesn’t support SNI”

This is something that can happen when you have more than one HTTPS server configured on the same IP. Because of how older versions of HTTPS worked, the server couldn’t know which hostname the request was for (and so which certificate it should serve) until after a secure connection was already established. This was usually solved by just using separate IPs.

The problem went away when browsers and servers implemented a new extension called SNI – you shouldn’t normally worry about it unless you need to support e.g. IE6, Android 2.3 or Windows XP.

“Contains anchor”

This is just a warning, it means your certificate bundle includes the certificate at the top of the chain, which is not required (since users already have it); simply delete the last CERTIFICATE block from the bundle to fix this.

Fix content warnings

It’s quite likely that at first you will see “mixed content” warnings in the browser when you open your site (open the inspector panel or error console). This means that you need to look for assets (images, scripts, stylesheets) that are included via HTTP and make them use HTTPS (or you can use scheme-less links like //server/host, in which case the right prefix will be added automatically depending on how you access the site).

Redirect to HTTPS

Once you’re sure that everything works, you can move all site configuration to the HTTPS server block and leave a separate HTTP block that simply redirects everyone to the secure site:

server {
  server_name yoursite.com;
  return 301 https://yoursite.com$request_uri;

Enable HSTS

When you’re really sure that everything works, you should also make either the server or your webapp send HSTS headers. HSTS is something that tells the browser that it should remember to always access the site only through HTTPS and never try to load the HTTP version, even if you explicitly try to do that. This makes your visitors secure against attackers who might trick them into accessing the plain HTTP version. Even if you have the above redirect enabled, a single request might be enough to leak session cookies and gain access to their accounts if they’re accessing the site through an insecure WiFi.

Once you set this, you can’t remove the HTTPS version anymore, because the users' browsers will have remembered that they should never ever access the HTTP version, and will try to access the HTTPS version no matter what you do (as they should), so only enable this if you’re sure about it.

If you decide to set the headers in the Nginx config (and not e.g. in your Rails app), this is how it should look:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";

You can also start with a smaller timeout and then raise it later:

add_header Strict-Transport-Security "max-age=86400"; 

Leave a comment


This will only be used to display your Gravatar image.

Are you a human? (yes/no) *