A+ SSL Configuration for Nginx

Qualys SSL Labs has a wonderful tool to help evaluate your server's SSL configuration. I recommend you take a moment to scan your site and see how you fare. Go do that now. I'll wait. If you didn't get a perfect score, you aren't alone.

If you're on a shared server or at the mercy of control panel software like cPanel or Plesk, unfortunately there probably isn't anything you can do. But if you have root access and a ken of the command-line, this article will help you harden your configuration to get that perfect A+ score.

This article focuses on Nginx — clearly the best server software —but if you use Apache or Lighttpd, the broader points should still apply.

Obtaining a Certificate

An SSL certificate has three ingredients: a private key, a signing request, and a certificate.

Generate a Key and CSR

To generate a new key, use:

mkdir /var/www/domain.com/ssl
cd /var/www/domain.com/ssl
openssl req -new -newkey rsa:4096 -nodes -keyout domain.com.key -out domain.com.csr

Make sure the folder you choose is outside your web root (to prevent evil robots from casually reading it). Your key should be at least 4096 bytes as anything less would defeat the purpose in this modern era.

The openssl command will quiz you about your domain and organization. The Common Name (CN) needs to be the Fully Qualified Domain Name (FQDN) for which you are purchasing the certificate. Remember, domain.com and www.domain.com are technically two different domains, so enter the CN exactly as visitors are expected to reach it. Some certificates, such as Comodo's PositiveSSL, are magically valid for both www and non-www variants, but others aren't.

You should now find two files in your ssl/ directory: domain.com.key and domain.com.csr. That key is meant to be private, so take a moment to update its permissions. If 600 is too restrictive for your environment, 640 might do the trick.

chmod 0600 domain.com.key

Get a Certificate

Now go buy a certificate from somewhere like Namecheap or generate one for free using Let's Encrypt. Every Certificate Authority (CA) has a slightly different validation process, so just follow their instructions to make it all official.

Upload and Combine

Once your certificate has been issued, upload it (along with the bundled certificates, if any) to the directory containing your key and csr files.

If you were provided with more than one certificate file, you'll need to combine them for Nginx, which only wants to see one damn file. The order matters; start with the domain-specific one, then add the bundle(s).

cat domain.com.crt domain.com-bundle.crt > domain.com.combined.crt

Nginx Set Up

Your Nginx configurations should be split into two parts: global (nginx.conf) and virtual host (mydomain.conf).

Global Changes

By default, OpenSSL uses a weak 1024 byte key for Diffie Hellman key exchanges. Let's bump this up to 4096 by running the following command. It can take a while to complete, so go make a sandwich:

openssl dhparam -out /etc/nginx/dhparams.pem 4096

Now let's make Nginx's SSL configuration a little more secure by adding the following code to the http{} block of your /etc/nginx/nginx.conf file:

http {

	# SSL Settings

	## force modern protocols and ciphers and enable
	## SSL cache for a small performance boost

	ssl_prefer_server_ciphers On;
	ssl_protocols TLSv1.2 TLSv1.3;
	ssl_ecdh_curve secp384r1;
	ssl_session_cache shared:ssl_session_cache:10m;
	ssl_dhparam /etc/nginx/dhparams.pem;


Virtual Host

Now that we have the general out of the way, let's move onto site-specific configurations. Open the configuration file corresponding to your site, something like /etc/nginx/sites-enabled/domain.com.conf.

Let's start by adding a server block to redirect www.domain.com to domain.com, and also force HTTPS connections for everything. If www.domain.com is your main domain, know that you're on the wrong side of history simply reverse the www and non-www occurrences in the following examples.

server {
	listen 80;
	listen [::]:80;

	server_name www.domain.com domain.com;

	return 301 https://domain.com$request_uri;

Now in your main server block for the domain, add the following:

server {
	listen 443 ssl http2;
	listen [::]:443 ssl http2;

	server_name domain.com www.domain.com;

	# point to the combined certificate and key we generated earlier.
	ssl_certificate /home/domain.com/ssl/domain.com.combined.crt;
	ssl_certificate_key /home/domain.com/ssl/domain.com.key;

	# enable HSTS (in supported browsers) to make sure all subsequent
	# user requests are done over HTTPS regardless of protocol typed.
	add_header Strict-Transport-Security "max-age=31536000";

	# redirect non-canonical domain over SSL.
	# this will only work if your SSL certificate also covers www.domain.com.
	if ($host != 'domain.com' ) {
		return 301 https://domain.com$request_uri;

Special Flags:


Older versions of Nginx require ipv6only=on be appended to the first instance of any IPv6 listen directives for a given port. If your site has multiple hosts, it doesn't matter which, just make sure you have one listen [::]:80 ipv6only=on and one listen [::]:443 ssl http2 ipv6only=on somewhere.


http2 is the successor to spdy. If your version of Nginx supports http2, it is highly recommended. If it does not, remove it from the listen lines.

Test and Restart

Before you do anything else, make sure your configurations validate.

nginx -t

# If no errors or warnings are reported, restart Nginx.
service nginx restart

Last Thoughts

You should now have pretty darn good SSL support! But the fun doesn't end here!

New threats or vulnerabilities can pop up any time, to say nothing of the inevitable march of progress (advances in technology will eventually make your once great setup laughably inadequate). Make sure you regularly apply any security patches made available to your distribution. You should also periodically rerun the Qualys SSL Labs scan to see if any tweaks are needed to keep your edge.

Josh Stoik
20 August 2015
Next Using Let's Encrypt on Debian