After moving my site behind HTTPs, something I have been meaning to do for a while now, I decided to use a multi-domain certificate. I have three domains that I wanted to secure vendion.me, vendion.us, vendion.net, so this sounded like the perfect use case for this kind of certificate. In my setup I’m using nginx as a reverse proxy and caching server in front of the Go server that is my site. It is due to this that I will be doing SSL termination at nginx, instead of putting the extra load on the backend Go server. Here is what I learned throughout the process.

Some of the information presented here actually came from other blog posts, I am just posting the information here to merge the information into a single post in hopes to make it easier for the next person trying to the same thing I was. Most notably Enabling SPDY with Nginx and Optimizing HTTPS on Nginx.

Create a CSR for multiple domains

The first thing to do is to generate a CSR (Certificate Signing Request) and key for all of the domains, this is done by adding the domains to the SubjectAltName field of the certs. As my domains are unlikely to change much I made a copy of my OpenSSL configuration file and add the following to both [ v3_req ] and [ v3_ca ] sections.

[ v3_req ]
subjectAltName=DNS:www.siteone.com,DNS:www.sitetwo.com,DNS:wwwsitethree.com

[ v3_ca ]
subjectAltName=DNS:www.siteone.com,DNS:www.sitetwo.com,DNS:wwwsitethree.com

With that in place run openssl like you normally would, just make sure to pass it the path to the new configuration file.

# openssl req -new -x509 -newkey rsa:2048 -days 365 -nodes -out /etc/ssl/private/site.com.csr \
    -keyout /etc/ssl/private/site.com.key -config /etc/ssl/multi-openssl.cnf

This will ask the normal questions when generating a certificate, when done you will see a site.com.pem, site.com.key, site.com.csr.

Once the key and csr file have been created it is a good idea to limit access to these files:

# chmod 400 /etc/ssl/private/site.com.key /etc/ssl/private/site.com.csr

Then send the CSR to your certificate authority and wait until it gets approved.

Optimizing Nginx for SSL

Compared to HTTP, HTTPS requires additional overhead and server resources. To optimize nginx for this extra load there are a few changes needed. First fix is to increase the number of worker processes in the root of the configuration to equal the number of CPUs on the server.

worker_processes 2;

Next within the http {} block add the following to control and limit SSL session settings:

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

This will create a cache shared between all worker processes. The cache is specified in bytes, in the above example it will use 10 MB cache. This cache will be stored for 10 minutes before it is expired.

Then set the keepalive timeout for the server also in the http {}:

keepalive_timeout 65;

Configure Basic SSL host

If running nginx 0.7.14 or later then setting up a SSL host looks like the following example:

server {
    #  [...]

    listen 443 ssl;
    ssl_certificate          /etc/ssl/private/site.com.pem;
    ssl_certificate_key /etc/ssl/private/site.com.key;

    # [...]
}

If you have a commercially signed certificate then replace the pem file in ssl_certificate with the crt file. With you should have nginx working with HTTPS. Technically this is all that you have to do, but why stop there? The rest of this post will be on steps that you can take to further optimize the server.

Enabling SPDY

SPDY is a new protocol created by Google that focuses on speeding up the web, and is being used in the draft for HTTP 2.0 spec as a working base. In order to make use of SPDY the server needs to already be making use of SSL.

First make sure your version of nginx is built with SPDY support, best way to check is to get nginx’s version information.

$ nginx -V

Make sure that you see -with-http_spdy_module somewhere in the output. If your server has support for it, enabling it is done by changing one line in the configuration, adding spdy to the http {} block like so

listen 443 ssl spdy;

After reloading nginx your secure site should be accepting SPDY connections, you can verify this with SPDYCheck.org.

Disable SSL

SSL (Secure Sockets Layer) has been superseded by TLS (Transport Layer Security), see here for more information about these protocols.

SSL contains several weaknesses, there have been various attacks on implementations and is vulnerable to certain protocol downgrade attacks. TLS is not perfect either, take the Heartbleed bug for example, but it is still preferred over SSL.

All modern browsers support TLS encryption, the last major browser that did not support it was IE 6. With Windows XP hitting EoL (End of Life) IE 6 should no longer be an issue. Web developers everywhere rejoice! The most recent version of TLS is 1.2, but a lot of browsers and libraries still only support TLS 1.0.

Here we will be enabling TLS 1, 1.1, and 1.2, in the server {} block change the ssl_protocols line to

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

Optimizing the cipher suites

To increase security changing the cipher suites would be a good idea. First nginx needs to instruct the client that there is a preferred order of available cipher suites:

ssl_prefer_server_ciphers on;

There are two good options to take:

Option 1: Highly secure, not so compatible

 ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:!ADH:!AECDH:!MD5;

Here is a chart detailing the compatibility of this option

Option 2: Very secure, very compatible

ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

Here is a chart detailing the compatibility of this option

The biggest difference between the two options is that the first only makes use of PFS suites.

Enable OCSP stapling

Online Certificate Status Protocol (OCSP) is a protocol for checking the revocation status of the presented certificate. When a proper browser is presented a certificate, it will contact the issuer of that certificate to check that it hasn’t been revoked. Nginx can be set to do this at regular intervals, instead of making the browser do it.

In order to verify that the response is valid Nginx needs to be able to read the CA’s root and the intermediate certificates. Using a Positive Multi-domain SSL certificate the root certificate would be AddTrustExternalCARoot.crt and the intermediate would be PositiveSSLCA2.crt. The easiest way to set this up would be to copy the contents of both files into a single file that Nginx reads.0

# cat AddTrustExternalCARoot.crt PositiveSSLCA2.crt > trustchain.crt

In order perform the stapling and verification add the following to the server {} block:

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/private/trustchain.crt;
resolver 8.8.8.8 8.8.4.4;

Nginx needs to be able to resolve DNS lookups, in this case I am using Google’s public DNS servers, but you can use what ever service you want to use. If you don’t like Google or what an alternative OpenDNS is an option as well.

Strict Transport Security

Even though HTTP requests should all get redirected to HTTPS, to make SPDY work, it is a good idea to enable Strict Transport Security to avoid having to do the redirects. STS is enabled in modern browsers, and all the server has to do is set the response header Strict-Transport-Security with a max-age value.

If the browser has seen this header, it will not try to contact the server over regular HTTP again for the given time period. It will actually interpret all requests to this hostname as HTTPS, no matter what. All that needs done is adding the following to the server config:

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

The max-age is set in seconds, 31536000 seconds is equivalent to 365 days.

This can also be applied to all subdomains as well, to do that use the following instead:

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

That is all that needs done.