Harden SSL Security on NGINX

David Oravsky491 VIEWS
Last Updated - May 3, 2019
Summary : In this tutorial, we will show you how to set up secure SSL protection on an NGINX web server to help mitigate malicious attacks.


This tutorial will cover updating OpenSSL to the latest version to mitigate attacks such as Heartbleed, disabling SSL Compression and EXPORT ciphers to mitigate attacks such as FREAK, CRIME, and LogJAM, as well as disabling SSLv3 and lower due to vulnerabilities in the protocol.  We will also configure strong cipher suite and include HSTS to enable direct privacy when possible. Thus, we will have a reliable and advanced SSL configuration which will lead to getting an A + on Qualys SSL Labs Test.

This tutorial works with the stricter requirements of the SSL Labs test announced on the 21st of January 2014 (If you follow this tutorial, you will get an A+).

Techievor's Rating

You can find more info on the topics that we’ll cover using the links below:

We are going to edit the NGINX settings in the file /etc/nginx/sited-enabled/yoursite.com (On Ubuntu/Debian) or in /etc/nginx/conf.d/nginx.conf (On RHEL/CentOS).

For the entire tutorial, you need to edit the parts between the server block for the server config for port 443 (SSL config). At the end of the tutorial, you can find the complete config example.

Make sure you back up the files before editing them!

The BEAST Attack and RC4

In short, by interfering with an encryption algorithm's CBC (Cipher Block Chaining) mode, portions of the encrypted traffic can be secretly decrypted. More information on the above link.

In the latest versions of browsers, client-side mitigation has been enabled for the BEAST attack. It has been recommended to disable all TLS 1.0 ciphers and only offer RC4. However, RC4 has a growing list of attacks against it, many of which have moved from theoretical to practical. Moreover, there is a reason to believe that the NSA has broken RC4, their so-called "big breakthrough."

Turning RC4 off has several consequences. First of all, users with outdated browsers, such as Internet Explorer on Windows XP, will use 3DES instead. Triple-DES is more secure than RC4 but is more expensive, and your server will pay for these users. Secondly, RC4 mitigates BEAST attacks. Thus, disabling RC4 makes TLS 1.0 users susceptible to this attack since it moves them to AES-CBC (the usual BEAST "fix" on the server-side is to prioritize RC4 over all others). I am confident that the shortcomings in RC4 far outweigh the risks from a BEAST attack. Indeed, due to client-side mitigation (which Chrome and Firefox both provide), BEAST is not a problem. But since the risk from RC4 only grows, more cryptanalysis will appear.

Factoring RSA-Export Keys (FREAK)

FREAK is a man-in-the-middle (MITM) vulnerability discovered by a group of cryptographers at INRIA, Microsoft Research, and IMDEA. FREAK stands for "Factoring RSA-EXPORT Keys."

The vulnerability dates back to the 1990s when the US government banned the sale of cryptographic software abroad if it did not use export cipher suites that use encryption keys no longer than 512-bits.

It turns out that some modern TLS clients, including Apple's SecureTransport and OpenSSL, contain a bug. This bug causes them to accept RSA export-level keys, even if the client did not request export-grade RSA. The impact of this error can be quite unpleasant since it allows a 'man in the middle' attack in which an active attacker can reduce the quality of a connection, provided that the client is vulnerable and the server supports RSA export.

The attack consists of two parts since the server must also accept "export-level RSA". 

The MITM attack works as follows:

  • The client’s Hello message asks for a standard RSA cipher suite.
  • A MITM attacker modifies this message to request an "RSA export".
  • The server responds with a 512-bit RSA export key, signed with its long-term key.
  • The client accepts this weak key due to the OpenSSL/SecureTransport bug.
  • The attacker analyzes the RSA module to recover the corresponding RSA decryption key.
  • When a client encrypts a “pre-master secret” on the server, an attacker can decrypt it in order to recover the TLS “master secret”.
  • From this point on, the attacker sees the plain text and can inject anything he wants.

The cipher suite offered here on this page does not include the ciphers of the EXPORT class. Make sure your OpenSSL is updated to the latest available version and encourage your clients to also use the updated software.

Logjam (DH Export)

Researchers from several universities and institutions conducted a study that found a problem in the TLS protocol and reported two methods of attack.

The Diffie-Hellman key exchange allows clients that depend on TLS to negotiate a common key and a secure session through a plain text connection.

The first attack method allows a man-in-the-middle to lower a vulnerable TLS connection to 512-bit export-grade cryptography, which allows an attacker to read and modify the data. The second threat is that many servers use the same prime numbers to exchange Diffie-Hellman keys instead of generating their own unique DH parameters.

The team estimates that an academic team can split 768-bit prime numbers and that a nation-state could break a 1024-bit prime number. By breaking one 1024-bit prime, you can eavesdrop on 18 percent of the top one million HTTPS domains. Overcoming a second prime would open 66 percent of VPNs and 26 percent of SSH servers.

In this guide, we create our own unique DH parameters and use a cipher suite that does not include EXPORT class ciphers. Make sure your OpenSSL is updated to the latest available version and encourage your clients to also use updated software. Updated browsers discard DH parameters below 768/1024 bits to fix this.

Cloudflare provides detailed logjam attack guidance.


Heartbleed is a security bug discovered in April 2014 in the OpenSSL cryptography library, which is a widely used implementation of the Transport Layer Security (TLS) protocol. Heartbleed can be used regardless of whether the party using a vulnerable OpenSSL instance for TLS is a server or a client. This is due to incorrect input validation caused by missing boundaries check in the DTLS heartbeat extension (RFC6520) implementation, so the name of the error comes from “heartbeat”. The vulnerability is classified as a buffer overflow, a situation where you can read more data than you should allow.

What version of OpenSSL does Heartbleed affect?

Status of different versions:

  • OpenSSL 1.0.1 through 1.0.1f (inclusive) are vulnerable
  • OpenSSL 1.0.1g is NOT vulnerable
  • OpenSSL 1.0.0 branch is NOT vulnerable
  • OpenSSL 0.9.8 branch is NOT vulnerable

The bug was introduced to OpenSSL in December 2011 and can be found since OpenSSL release 1.0.1 on 14th of March 2012. OpenSSL 1.0.1g, released on 7th of April 2014, fixes this error.

By updating OpenSSL, you are not affected by this bug.

SSL Compression (CRIME Attack)

The CRIME attack uses SSL Compression to compromise systems. SSL compression is turned off by default in NGINX 1.1.6+/1.0.9+ (if using OpenSSL 1.0.0+) and NGINX 1.3.2+/1.2.2+ (if using older versions of OpenSSL).

If you are using an earlier version of NGINX or OpenSSL and your distribution does not transfer this option back, then you need to recompile OpenSSL without ZLIB support. This will disable the use of OpenSSL using the DEFLATE compression method. If you do this, you can still use regular HTML DEFLATE compression.

SSLv2 and SSLv3

SSL v2 is not secure, so we need to disable it. We also disable SSLv3, since TLS 1.0 can suffer from a downgrade attack, allowing an attacker to force a connection to use SSLv3 and, therefore, disable forward secrecy.

Make the following edit to the configuration file to disable these protocols:

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;


SSLv3 allows abuse of the POODLE bug. This is another important reason to disable it.

Google has offered an extension to SSL/TLS named TLSFALLBACKSCSV, which seeks to prevent the forced downgrade of SSL. This is automatically enabled if you upgrade OpenSSL to the following versions:

  • OpenSSL 1.0.1 has TLSFALLBACKSCSV in 1.0.1j and higher.
  • OpenSSL 1.0.0 has TLSFALLBACKSCSV in 1.0.0o and higher.
  • OpenSSL 0.9.8 has TLSFALLBACKSCSV in 0.9.8zc and higher.

More info on the NGINX documentation

The Cipher Suite

Forward Secrecy ensures the integrity of a session key if the long-term key is compromised. PFS does this by forcing the creation of a new key for each session. This means that when the secret key is compromised, it cannot be used to decrypt the recorded SSL traffic.

Cipher suites that provide Perfect Forward Secrecy are suites that use an ephemeral form of Diffie-Hellman key exchange. Their disadvantage is the overhead, which can be improved by using variants of the elliptic curve.

The first cipher suites are recommended by me, and the second from the Mozilla Foundation.

Recommended cipher suite:


Recommended cipher suite for backwards compatibility (IE6/WinXP):


If your version of OpenSSL is old, inaccessible ciphers will be automatically dropped. Always use the full cipher suite described above and let OpenSSL choose which ones it supports.

The order of the cipher suite is very important because it decides which algorithms will be selected in priority order. The above recommendation gives priority to algorithms that provide perfect forward secrecy.

Older versions of OpenSSL may not return a complete list of algorithms. AES-GCM and some ECDHE have recently appeared and are not present in most versions of OpenSSL shipped with Ubuntu or RHEL.

Prioritization logic

  • ECDHE+AESGCM ciphers are selected first. These are TLS 1.2 ciphers. Currently, none of the known attacks target these ciphers.
  • PFS cipher suites are preferred, first ECDHE, then DHE.
  • AES-128 is preferable to AES-256. There were discussions about whether the additional security of AES-256 was worth the cost, and the result is far from obvious. Now, AES-128 is preferable because it provides good security, is fast and seems more resistant to timing attacks.
  • In a backward compatible cipher suite, AES is preferred to 3DES. BEAST attacks on AES are mitigated in TLS 1.1 and higher, and difficult to achieve in TLS 1.0. In a non-backward compatible cipher suite, 3DES is not present.
  • RC4 is removed completely. 3DES is used for backward compatibility. See discussion in #RC4_weaknesses.

Mandatory discards

  • aNULL contains unauthenticated Diffie-Hellman key exchanges that are subject to Man-In-The-Middle (MITM) attacks.
  • eNULL contains ciphers with zero encryption (cleartext).
  • EXPORT are obsolete weak ciphers that have been marked as exported by US law.
  • RC4 contains ciphers that use the outdated ARCFOUR algorithm.
  • DES contains ciphers that use the outdated Data Encryption Standard.
  • SSLv2 contains all ciphers that were defined in the old version of the SSL standard and is not recommended
  • MD5 contains all ciphers that use the outdated Message Digest 5 as a hashing algorithm.

Extra Settings

When choosing a cipher during an SSLv3 or TLSv1 handshake, the client's preference is usually used. If this directive is enabled, the server's preference will be used instead.  Make sure you also add these lines:

    ssl_prefer_server_ciphers on; 
ssl_session_cache shared:SSL:10m;

More info on ssl_prefer_server_ciphers
More info on ssl_ciphers

Reject Illegal Host Headers

Malicious bots or vulnerability probing usually sends requests with an incorrect or empty Host header. By default, a “catch-all virtual hosts” technique is used to block such attempts.  However, in some cases, for example, if your site is encrypted using SSL / TLS, you cannot use the virtual host by default.It is recommended to block all requests with an invalid host header with the following configuration (example.com is your site in this example):

    if ($http_host !~* ^(http://www.example.com)$ ) { 
return 444;

The returned HTTP error code 444 is used in the NGINX logs to indicate that the server did not respond to the client and closed the connection (useful for blocking malicious requests).

Block Empty User Agents

Based on my experience, visits with empty user-agent data indicate attempts to scrape/spam (usually scraping) made by "headless browser" bots.You should block any custom HTTP agents using GET / POST requests that scrape your content or try to exploit a software vulnerability.

    if ($http_user_agent = "") { 
return 403;

 if ($http_user_agent = "-") {
   return 403;

Forward Secrecy and Diffie-Hellman Ephemeral Parameters

The concept of forward secrecy is simple, the client and server agree on a key that never gets in line and is destroyed at the end of the session. Private RSA from the server is used to sign the Diffie-Hellman key exchange between the client and the server. The provisional master key obtained from the Diffie-Hellman handshake is then used for encryption. Since the pre-master key is specific to the connection between a client and a server and is used only for a limited period, it is called Ephemeral.

When using Forward Secrecy, if an attacker takes possession of the server's private key, they will not be able to decrypt past connections. The private key is only used to sign the DH handshake, which does not reveal the pre-master key. Diffie-Hellman ensures that pre-master keys never leave the client and the server and cannot be intercepted by MITM attacks.

All versions of NGINX, starting with 1.4.4, use OpenSSL to enter Diffie-Hellman (DH) parameters. Unfortunately, this means that Ephemeral Diffie-Hellman (DHE) will use OpenSSL defaults, which include a 1024-bit key for key-exchange. Because we use a 2048-bit certificate, DHE clients will use weaker key-exchange than non-ephemeral DH clients.

We need to create a stronger DHE parameter:

    cd /etc/ssl/certs 
openssl dhparam -out dhparam.pem 4096

And then tell NGINX to use it for DHE key-exchange:

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

OCSP Stapling

When connecting to a server, clients must verify the validity of the server certificate using either a Certificate Revocation List (CRL) or an Online Certificate Status Protocol (OCSP) record. The problem with CRLs is that the lists have become huge and their download takes forever.

OCSP is much lighter since only one entry is extracted at a time. But the side effect is that OCSP requests must be sent to a third-party OCSP responder when connecting to a server, which adds delay and possible failures. In fact, OCSP respondents managed by certificate authorities are often so unreliable that the browser silently crashes if the response is not received in a timely manner. This reduces security by allowing an attacker to perform a DoS attack against an OCSP responder to disable the check.

The solution is to allow the server to send its cached OCSP entry during the TLS handshake, therefore bypassing the OCSP responder. This mechanism retains the two-way communication between the client and the OCSP responder and is called OCSP Stapling.

The server will send a cached OCSP response only if the client requests it, announcing support for the TLS status_request extension in its CLIENT HELLO.

Most servers will cache OCSP responses for up to 48 hours. At regular intervals, the server will connect to the OCSP CA responder to receive a new OCSP record. The OCSP responder location is taken from the Authority Information Access field for the credential information of the signed certificate.

Check out our article OCSP Stapling for NGINX and Apache for more information.

HTTP Strict Transport Security

If possible, you should enable HTTP Strict Transport Security (HSTS), which instructs browsers to communicate with your site via HTTPS only.

Check out our article HTTP Strict Transport Security for NGINX and Apache for more information.


If you have applied the above config lines you need to restart NGINX:

    # Check config first then restart
sudo nginx -t & service nginx restart

Now use the SSL Labs test to see if you get an A. And, of course, have a secure, reliable and promising SSL configuration!

If you any questions or thoughts on the tutorial, feel free to reach out in the comments below.

If you like this article, consider sponsoring us by trying out a Digital Ocean VPS. With this link you'll get $100 credit for 60 days.

The following is the final configuration discussed in this tutorial:

# Don't send the nginx version number in error pages and Server header
server_tokens off;

server {
   listen  80 default_server;
   listen  [::]:80 default_server;
   server_name  host_name.com www.host_name.com;
   rewrite  ^/(.*) https://host_name.com/$1 permanent;

   # Deny if the user agent is missing empty or contains just a single hyphen.
   if ($http_user_agent = "") {
     return 403;

   if ($http_user_agent = "-") {
     return 403;

 # Settings for a TLS enabled server.
 server {
   listen  443 ssl http2 default deferred;
   listen  [::]:443 ssl http2 default deferred;
   server_name  host_name.com www.host_name.com;

   # Deny illegal Host headers
   if ($http_host !~* ^( host_name.com|www. host_name.com)$ ) {
     return 444;

   # Deny if the user agent is missing empty or contains just a single hyphen.
   if ($http_user_agent = "") {
     return 403;

   if ($http_user_agent = "-") {
     return 403;

   ssl_certificate "/etc/nginx/ssl/ host_name.com.chained.crt";
   ssl_certificate_key "/etc/nginx/ssl/private.key";

   # Enable session resumption to improve https performance
   # http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html
   ssl_session_cache shared:SSL:50m;
   ssl_session_timeout 1d;
   ssl_session_tickets off;

   # Diffie-Hellman parameter for DHE cipher suites, recommended 4096 bits
   ssl_dhparam /etc/nginx/ssl/dhparam.pem;

   # Enables server-side protection from BEAST attacks
   ssl_prefer_server_ciphers on;
   ssl_session_cache shared:SSL:10m;
   # Disable SSLv3(enabled by default since nginx 0.8.19) since it's less secure then TLS
   # http://en.wikipedia.org/wiki/Secure_Sockets_Layer#SSL_3.0
   ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
   # ciphers chosen for forward secrecy and compatibility
   # http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html

   # Enable ocsp stapling (mechanism by which a site can convey certificate revocation information to visitors in a privacy-preserving, scalable manner)
   # http://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/
   ssl_stapling on;
   ssl_stapling_verify on;
   ssl_trusted_certificate /etc/nginx/ssl/star_forgott_com.crt;

   # config to enable HSTS (HTTP Strict Transport Security)
   # https://developer.mozilla.org/en-US/docs/Security/HTTP_Strict_Transport_Security
   # to avoid ssl stripping https://en.wikipedia.org/wiki/SSL_stripping#SSL_stripping
   # also https://hstspreload.org/
   add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";

   # ... the rest of your configuration

Additional Reading: