How to get an ‘A+’ in SSL Labs Server Test with NginX configuration
This blog covers the steps I took, to get an ‘A+’ rating on my server. If you just want a ready-to-go NginX config file to get an ‘A+’ rating, you can find it here.
I have my NginX setup on Ubuntu. I will assume that you already have a server setup with default NginX configuration and SSL/TLS working with a valid certificate. You can get your current rating from Qualys here. If you were to run the SSL Labs test on your server with the default NginX configuration, you would probably get a C grade — barely passing.
But don’t worry, you will easily end up getting a much higher rating by following the rest of the blog.
Keep a backup of your NginX config file
First and foremost, before making any changes to your NginX config file, make sure that you have a backup of that file.
cp /etc/nginx/sites-enabled/nginx.conf /home/ubuntu/nginx.conf
I made the backup copy to my home directory, you can chose any location of your choice. Next, we will go through each rating categories and try to get the maximum score possible.
This section is fairly easy to get 100% on. Use a well known or trusted CA(Certificate Authority) and make sure your certificate and chain are in the correct order. The most important certificate configuration setting is the fingerprint hashing algorithm. Ensure that you have created a SHA256-signed certificate and not SHA1. Until recently, most certificates were signed using SHA1. However, its becoming dangerously insecure as computing power advances. In its place, new certificates should be signed using SHA256.
By default Nginx still enables SSLv3, which has been vulnerable to the POODLE attack since October 2014. SSL Labs rightly limits your server’s SSL score to C if SSLv3 is enabled, so this is the first thing to change.
Using only TLSv1.2
If you want to score 100% in this section, you will have to use only TLSv1.2 as your supported SSL protocols. However, most of the older clients will not able to load your site.
Using TLSv1.1 & v1.2 will get you a score of 95% in this section, but some of the older clients will still not be able to load your site.
Although using this suite will give you a score of 90%, this would be the best compatible option suggested by me, and you will still be able to score an overall ‘A+’. Don’t use protocols older than TLS1.0 since it’s been deprecated.
# SSLv3 is broken by POODLE as of October 2014
# ssl_protocols TLSv1.2; # Score=100
# ssl_protocols TLSv1.2 TLSv1.1; # Score=90
ssl_protocols TLSv1.2 TLSv1.1 TLSv1; # Score=95 (recommended)
To increase the Key Exchange score, we need to increase the size of the key used in the DH exchange. Nginx uses OpenSSL’s default 1024 bit key size as an input to the DH key exchange, resulting in a 1024 bit key. Ideally, we should be using a key number smaller than our SSL certificate which is commonly 2048 or even 4096 bits.
Many web-server installations reuse the default DH numbers. Using the same numbers will be make it easier to eaves drop on the communication. Thus, a new key needs to be generated with custom DH parameters. So, we will generate a new set of DH parameters and store them in a file. For this, we need to ask OpenSSL to make the new parameters for us.
openssl dhparam -out /etc/nginx/dhparam.pem 4096
Running the OpenSSL command will take a few minutes. You can drop the size down to 2048 bits if you like, but this mainly depends on the key size in your SSL certificate. Next, we’ll configure Nginx to use these parameters. Add the following line to your NginX configuration:
The most difficult configuration decision on your server will be your supported cipher suites. With a near infinite number of possible combinations, there are many of each of these algorithms, all with varying features, performance, cryptographic strength, and browser support. Many of them have known weaknesses that make them unsuitable for use today. Using the latest version of a browser is usually enough to protect an individual user but, unfortunately, a lot of older browsers have insecure defaults. Thus, we need to consider a lot of options while configuring cipher suite to ensure compatibility with as many browsers as possible, without compromising security & performance.
Some algorithms have known or suspected vulnerabilities, and we can disable or limit their use where appropriate.The following algorithms in particular should be disabled:
The MD5 hashing algorithm is commonly used but has had known weaknesses. Today, MD5 is famously vulnerable to collisions, especially with GPUs. It isn’t safe to use any more.
The RC4 cipher is also commonly used, and was even widely recommended. However, research showing theoretical vulnerabilities in RC4, the possibility that there are working attacks against RC4 in the wild, is too plausible to ignore.
For the same reasons we generated a certificate request using SHA256 instead of SHA1, we also have to disable cipher suites that use SHA1 as the hashing algorithm.
Some ciphers like Camellia, Seed, etc. are not common and disabling them just simplifies things and reduces vulnerability.
Make server choose the cipher suite
Many browsers, especially old ones, will make poor cipher suite choices on their own. By enabling the cipher order, when a browser provides its list of supported ciphers, the server will choose the best one possible. This way it can be ensured that the best possible cipher is always included over the weaker ones.
For scoring 100% in Cipher Strength, you need to disable not only any old protocols, but also any 128-bit ciphers. This is not something you likely want to do (yet) for a production, world-accessible site, as it will require relatively modern browsers, and you typically can’t control what visitors use. I would suggest it for a site where you can “control” the users, such as a small or medium-sized company’s webmail or an employee portal.
# ssl ciphers list
# support only believed secure cipher suites following priority:
# 1.) prefer PFS enabled ciphers
# 2.) prefer AES128 over AES256 for speed
# 3.) Support DES3 for IE8 support
# disable the following ciphersuites completely
# 1.) null ciphers
# 2.) ciphers with low security
# 3.) known vulnerable cipers (MD5, RC4, etc)
# 4.) little-used ciphers (Camellia, Seed)
ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !RC4 !EXP !PSK !SRP !CAMELLIA !SEED';
# ssl_ciphers AES256+EECDH:AES256+EDH:!aNULL; # Score=100
# make the server choose the best cipher instead of the browser
Achieving the A+ SSL rating by enabling HSTS
At this stage, restarting the NginX server and reevaluating the score will give us the following:
Unfortunately, SSL Labs does not clearly show what more needs to be done to get the A+ rating. Luckily, it’s not hard: we need to add the HTTP Strict Transport Security (HSTS) header. Note that HSTS makes your site HTTPS-only, so you can’t use it if you want to keep an HTTP version — in which case you can’t get better than an ‘A’ rating.
By enabling HSTS, the browsers will not allow users to attempt any connection using HTTP and always enforce the use of HTTPS. This means that, after a first visit from a client to the server, subsequent visits to any HTTP pages will be converted by the browser to HTTPS before any request is made. This saves us some round trip time and increases security, especially against many SSL stripping attacks. HSTS also prevents the constant need for you to redirect clients from HTTP to HTTPS and many other configuration errors that are easy to make. This protocol can be added to config file by:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
The 31536000 seconds are roughly around a year. So, the browser will remember for a year that it should always visit your website through HTTPS. If, for any reason, you want to stop supporting HTTPS for your website, you will have a problem with browsers that have already received this header. Would you redirect any HTTPS traffic back to HTTP, such browsers would redirect again to HTTPS resulting in an infinite loop. Therefore, if you are just playing around with SSL and aren’t sure you will keep it for your website, don’t enable HSTS for that website.
The includeSubDomains clause tells the browser to also enable HSTS for sub-domains. So remove this, if not all your sub-domains are ready for HTTPS.
With the first HTTP request, a browser is still potentially vulnerable. Using “preload” clause, we tell browsers not to make even one HTTP request to the server. This is done by submitting your website to the HSTS preload list. By submitting your website to the HSTS preload list, you tell browsers to never visit your website through HTTP. Addition to the preload list will have semi-permanent consequence and is irreversible, so only use this option once you’re comfortably set up running HTTPS with no problems.
The “always” after the header contents tell NginX to provide this header with every possible response code, instead of only for the “200, 201, 204, 206, 301, 302, 303, 304, or 307” status codes.
Note that an entry level HSTS:
add_header Strict-Transport-Security "max-age=63072000;"; with duration ≥1 Week is sufficient to get an ‘A+’.
Even though we already achieved the A+ rating, there is some more we can still improve. These changes won’t affect your scoring but are necessary to improve performance.
With each certificate a browser receives, it performs a request to a Certificate Authority to check whether the certificate has expired. This causes extra waiting time and it decreases security. Instead of the browser checking the status of the provided certificate, the server can be made to check this on a regular interval using the Online Certificate Status Protocol (OCSP). The OCSP responses are signed by your Certification Authority, so browsers will be able to trust them, even if they come directly from your server.
Add the following line to your NginX config file:
# OCSP stapling
#the CA & Intermediate CA file for your cert
resolver 188.8.131.52 184.108.40.206 valid=300s; #Google DNS
The “ssl_trusted_certificate” is a reference to the file that includes all trusted certificates (therefore, including the root certificate). This list is never sent to clients but instead used mainly for OCSP.
The “resolver” configuration setting are needed so that NginX knows how to resolve DNS queries. I have used Google’s DNS, you can use any other of your choice.
SSL Session Caching (Session Resumption)
To minimise the impact of the SSL overheads on your server, you can reduce the number of handshakes that a client needs to perform. The handshake is the most expensive part of serving content over SSL. With session resumption, the server specifies some information to the client during the initial TLS handshake. In a second connection, the client can then use this information to skip the entire TLS handshake and immediately have a secure connection setup. We can enable the SSL cache to remove the need for a handshake on subsequent or parallel connections.
SSL Labs doesn’t assume that SNI is available to the client, so it only tests the default virtual server. So, we need to set this globally to make it work across all the nginx virtual servers (including the default virtual server).
The “shared SSL” cache of 10Mb will hold around 40,000 connections so should be more than enough for most sites.
The “session_timeout” defines how long a session can remain in the cache and remain valid before it is discarded. The default value for this is 5mins.
Loading Your NginX Configuration
Finally, after all the NginX configurations has been modified and saved, you need to reload the NginX server. This will check for any syntax errors and then reload the configuration. Now, head over to the SSL Test and check out your new score. Once the test is completed, you should see something similar to my score at the top.
Congratulations, you have just scored an ‘A+’ for your server.