Configure nginx to serve HTTPS with a SSL certificate

Setting up nginx with a SSL certificate to serve HTTPS pages may not be simplest task in the world, but you can do it if you follow these instructions (for Linux and OS X).

Note: These instructions have been updated as of January 25, 2015.

Get a SSL certificate

First you need to get a SSL certificate. You can buy the reasnoably affordable Comodo PositiveSSL from Namecheap, currently starting at $7.95/yr for 5 years.

If you don't already have a domain name, you can buy your domain name from Namecheap and get the SSL certificate included on the purchase for less than $2.

Next, you have to activate the certificate. The certificate issuer needs your CSR (Certificate Signing Request), which is a string with domain and company information, signed with your domain's private key.

If you haven't previously generated a private key for you server, you should do it now using the following command:

 openssl genrsa -out host.pem 2048

This generates a host.pem file containing your domain's private key, which is a bunch of lines with weird looking text, like this:

 -----BEGIN RSA PRIVATE KEY-----
 MIIEpQIBAAKCAQEAyS0tKkM/XOCjs9FGGGoAscHXswjj4UV+q3g7X6YiwfZL25xo
 rMhd5WFM7ZlwcCRpa6lMQyXlcvHxE9JGC3MkZM0GL/t5dpHzEi1yAaNrQaB+h2Im
 ...
 pfbdpyueywuzhUnC8D7Lrs2iBn3UmsL/hG/IY1gdZqc5UwAaHFj8i2oqNKSMy64o
 RtVRbAfnSobK91hFjWErQOAxpAkq4EONG4Y4osx1NEEaMP5MwOkq7v8=
 -----END RSA PRIVATE KEY-----

You are now in a position to generate the CSR using this command:

 openssl req -new -sha256 -key host.pem -out host.csr

OpenSSL will ask you for:

Here is what the CSR file looks like when you open it in a text editor:

 -----BEGIN CERTIFICATE REQUEST-----
 MIICoDCCAYgCAQAwWzELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
 ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAxMLZXhh
 ...
 Q4Ymt/fi7xMdGMe0I+v+r5h3A7Kc8PjgdC6oDzxIB+xtEo650oD/u9XLdmOU5Z3M
 vCPdiQ==
 -----END CERTIFICATE REQUEST-----

You can get a more human readable version of the CSR using this command, which also validates that the CSR is correctly formed:

 openssl req -text -in host.csr

Next you copy-and-paste the CSR into the form on Namcheap's web page for certificate activation. You should copy everything including the lines beginning with dashes.

Select web server type "other", and enter your approver email, which has to be on the domain you're creating a certificate for.

Now you have to wait for Comodo to send you an email to certify that you actually have access to the domain. In the email there is a validation code and a link to a web page. Paste the validation code into the form on the web page, and you have completed your part of the validation process.

Next, Comodo sends you the certificate in an email, which also has a zip-file attachment containing the certificate chain (from you domain certificate to the top certificate authority).

Between your certificate and up to the certificates included in browsers there are one or more intermediate certificates. So, for browsers to accept your certificate, you have to provide them with all intermediate certificates. Gather the certificates into one bundle with this command (in the directory with the unpacked the zip-file from Comodo):

cat example_com.crt \
    COMODORSADomainValidationSecureServerCA.crt \
    COMODORSAAddTrustCA.crt \
    AddTrustExternalCARoot.crt > example_com_bundle.crt

(Replace example.com with your actual domain name.)

Configure nginx to serve SSL with your certificate

Having a SSL certificate, you can now configure nginx to use it. You'll find nginx's main configuration file on your server at this path: /etc/nginx/nginx.conf

Here are the suggested directives to include in the main configuration file:

worker_processes auto;

http {
  ssl_session_cache   shared:SSL:10m;
  ssl_session_timeout 10m;
  keepalive_timeout   70s;

  ssl_prefer_server_ciphers on;
  ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;

  ...
}

This may look like gibberish. But don't worry, here is a brief explanation of why this configuration is great:

SSL operations consume extra CPU resources, therefore you benefit from running more than one nginx worker process. When declaring worker_processes auto nginx will start a worker process per available CPU core.

The most CPU-intensive operation is the SSL handshake. You can reduce the number of operations per client by enabling "keepalive" connections to send several requests on the same connection, and to reuse SSL session parameters to avoid SSL handshakes for subsequent connections. Sessions are stored in an SSL session cache shared between nginx worker processes.

If you want an A-level score on the SSL test, you shouldn't use nginx's default SSL cipher suite, instead use a more secure cipher suite. The configuration above includes the default cipher suite used by Mozilla. Consult this discussion on Hacker News for other alternative choices of cipher suite.

Next, you have to configure the (virtual) server to accept SSL connections. You can find the configuration files for individual servers at /etc/nginx/sites-available/

Here is as suggested configuration file for a single server that handles both HTTP and HTTPS requests.

# HTTP and HTTPS server
server {
  listen 80;
  listen 443 ssl;
  server_name example.com;

  # SSL options
  ssl_certificate /path/to/example_com_bundle.crt;
  ssl_certificate_key /path/to/host.key;

  ...
}

When you have configured a server, you have to symlink the configuration file to /etc/nginx/sites-enabled/ and make nginx reload the configuration files using this command:

nginx -s reload

If nginx doesn't complain, your server should now be available over HTTPS. So, go try it in a browser! If it's not working, make sure your server doesn't have a firewall blocking port 443, which is the default port for serving HTTPS.

If it works in your browser, you can now go to SSLlabs homepage and run their free test to confirm that your domain gets an A-level SSL-test score.