Increase SSL and TLS security on nginx and Apache by enabling PFS and HSTS

SSL Labs A+ Rating

The default configuration of SSL is fine on most Linux distributions (you will get an A-Rating at SSL Labs), but still could be done a lot better and more secure.

Goals we want to achieve:

  • Enable Perfect Forward Secrecy (PFS)
  • Enable HTTP Strict Transport Security (HSTS)
  • Disable SSLv2 and SSLv3
  • PCI compliant
  • FIPS-ready (optional)
  • Don’t break IE…

NOTE: the configuration below will break SSL/TLS communication with IE6 and Java6, because it’s simply not possible to have secure communication with them. This configuration is about security and not compatibility.

nginx on CentOS 6

In the default repositories nginx in version 1.0.15 is included, which should support PFS. But, it doesn’t. Why that? It’s because nginx is static linked to an older openssl library, and therefore doesn’t support required ciphers. ECC and ECDHE ciphers are supported in OpenSSL 1.0.1c+.

But luckily, nginx has an official repository which provides more recent versions for CentOS. To add their repo and install nginx execute these commands:

wget http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
rpm -ivh nginx-release-centos-6-0.el6.ngx.noarch.rpm

yum install nginx

Apache 2.2

Unfortunately Apache 2.2 doesn’t support PFS at all. The solution is to configure nginx as a reverse proxy because it supports everything we need, it’s done quickly, and we don’t really lose much performance. PFS is supported in Apache 2.4+.

Use more secure DH key exchange

To have a more secure DH key exchange we have to generate a DH parameter file with a size of 2048 or 4096 bits (this will take some time!):

openssl dhparam -out /etc/pki/tls/private/dhparam.pem 2048
chmod 600 /etc/pki/tls/private/dhparam.pem

Configuration of nginx

Global config for all sites (/etc/nginx/nginx.conf):

http {
    ....
    # Only allow save protocols
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    # Prefer server side protocols for SSLv3 and TLSv1
    ssl_prefer_server_ciphers on;

    # PCI compilant ciphers
    ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";

    # FIPS ready ciphers only
    #ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA DES-CBC3-SHA !RC4 !aNULL !eNULL !LOW !MD5 !EXP !PSK !SRP !DSS !CAMELLIA !SEED";

    # SSL session cache
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    # For Diffie Hellman Key Exchange:
    # Create dhparam.pem: openssl dhparam -out dhparam.pem 2048
    ssl_dhparam /etc/pki/tls/private/dhparam.pem;
    ....
}

Setting for HSTS has to be done in server section or it will not work (at least in my case):

server {
    ....
    # Enable HSTS (HTTP Strict Transport Security)
    add_header Strict-Transport-Security "max-age=15768000;includeSubDomains";
    ....
}

Now, if you use nginx only, configure your virtual hosts as usual.
If you’re using nginx as a reverse proxy for Apache, add the following config:
Site specific configuration (f.ex. /etc/nginx/conf.d/default.conf):

server {
    ....
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;

    # Set headers
    proxy_set_header Accept-Encoding "";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # Most PHP, Python, Rails, Java App can use these headers
    proxy_set_header X-Forwarded-Proto $scheme;
    add_header Front-End-Https on;

    # Disbale redirect
    proxy_redirect off;

    # Depending on your configuration, you may want to place the location in separate files
    location / {
        proxy_pass http://127.0.0.1:80;
    }
    ....
}

A bit off topic, but you definitely want to have compression enabled:
Global config for all sites (/etc/nginx/nginx.conf):

http {
 ....
 gzip on;
 gzip_disable "msie6";
 gzip_comp_level 6;
 gzip_min_length 1100;
 gzip_buffers 16 8k;
 gzip_proxied any;
 gzip_http_version 1.1;
 gzip_types text/plain application/xml text/css text/js text/xml application/x-javascript text/javascript application/json application/xml+rss;
 ....

What about BEAST attacks?

BEAST is a purely client-side vulnerability. When this attack became public, there was the possibility to mitigate the attack on the server side. But in the meantime all common browser have fixed it and therefore it’s not needed to mitigate it on server-side anymore. Read more about BEAST attacks.

What key size should be used?

1024 bit keys are broken. If you sill use them somewhere, replace them by bigger ones as soon as possible.
2048 bit keys are still secure and it’s the minimum key length that should be used. Should be save for some more years.
4096 bit keys are the most secure option and should be secure for many more years. But also uses more CPU and decreases performance a bit.

I personally have started to use 4094 bit keys only since last quarter of 2013, but don’t replace smaller ones until i have to replace them anyway.

Some smartcards and other devices may not support 4096 yet. You can find some further informations here.

Test your configuration

Finally you have to check if everything was configured correctly.
Test your site at SSL Labs.

This entry was posted in Apache, CentOS, Linux, nginx, Security and tagged , , , , , , , , . Bookmark the permalink.

3 Responses to Increase SSL and TLS security on nginx and Apache by enabling PFS and HSTS

  1. Kamal says:

    I am using Plesk 12 in Centos 6

    Where should I enter the
    add_header Strict-Transport-Security "max-age=15768000;includeSubDomains";
    Exactly ??
    When I simply put it in nginx.conf it gives me error and doesn’t work. Please Help !!

    • Urs says:

      It has to be within a “server” section. F.ex. in /etc/nginx/conf.d/default.conf :
      server {
      listen [::]:443 ssl ipv6only=off;
      server_name _;

      ....
      add_header Strict-Transport-Security "max-age=15768000;includeSubDomains";
      ....
      }

      Plesk does a lot of custom stuff and you have to test it very well if you want to use it together with Plesk to work correctly.

  2. bluecon says:

    Thank you very much! Great article.

Leave a Reply

Your email address will not be published. Required fields are marked *