nginx with SSL Labs A+ and Security Header A+

It's never really clear with SSL Labs what you actually need to do to get an A+ rating, though that shouldn't be the point should it? The point should be to do things properly and validate that they are indeed done properly.

Such as,

  1. Use certificates from a trusted CA with OCSP responder (and one who didn't charge the earth to provide a poorly validated cert)
  2. Use TLSv1.2 exclusively
  3. Use secure ECDHE based ciphers exclusively
  4. Provide OCSP stapled response as part of TLS negotiation
  5. Support HTTP/2
  6. Deploy a DNS CAA record permitting the CA who signed your cert

Which is what you should be doing anyway in my opinion; provided you're not a laggard enterprise still struggling to upgrade from IE10 and Windows 7.

To get an A+ with Security Headers you do need to deploy all of the sites recommended headers in an enforcing mode. Grades seem to be capped if you're just using report only mode.

Certificates

I'm using Let's Encrypt and using 'dehydrated' as my client. No special config.

https://letsencrypt.org/
https://dehydrated.de/

DNS CAA

I've deployed DNS CAA to authorise Let's Encrypt to issue named certs for my domain. No one gets to issue wildcards. I'm currently using an email report destination in interim.

[[email protected] conf.d]$ dig @8.8.8.8 CAA routedlogic.net +short
0 issuewild "\;"
0 issue "letsencrypt.org"
[[email protected] conf.d]$

Ciphers

The following cipher string is basically all you want to be using right now IMHO... and it should cater for all of your PFS/ECDHE and HTTP/2 needs. You only really need to exclude 3DES and SHA in order to remove the insecure ECDHE options that use things you definitely do not want.

The only client type that might not be able to negotiate is IE8-10 on Win7... and well... if you're still using that combination I don't want to talk to you.

'ECDHE:!3DES:!SHA'

[[email protected] conf.d]$ openssl ciphers 'ECDHE' | sed -r -e "s/:/\n/g" | sort
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES128-SHA
ECDHE-ECDSA-AES128-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES256-SHA
ECDHE-ECDSA-AES256-SHA384
ECDHE-ECDSA-DES-CBC3-SHA
ECDHE-ECDSA-NULL-SHA
ECDHE-ECDSA-RC4-SHA
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-SHA
ECDHE-RSA-AES128-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-SHA
ECDHE-RSA-AES256-SHA384
ECDHE-RSA-DES-CBC3-SHA
ECDHE-RSA-NULL-SHA
ECDHE-RSA-RC4-SHA
[[email protected] conf.d]$ openssl ciphers 'ECDHE:!3DES:!SHA' | sed -r -e "s/:/\n/g" | sort
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES128-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES256-SHA384
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-SHA384
[[email protected] conf.d]$

nginx Version/Main Config

I'm using CentOS 7 and running the latest official nginx package from http://nginx.org/packages/centos/

That's currently the most recent stable 1.12 release,

[[email protected] ~]$ rpm -qi nginx
Name        : nginx
Epoch       : 1
Version     : 1.12.2
Release     : 1.el7_4.ngx

The main nginx config is nothing special and really just incorporates OWASP nginx hardening recommendations; as well as other recommendations for performance.

Follow this if you don't already have some level of hardening sorted: https://www.owasp.org/index.php/SCG_WS_nginx

nginx OCSP

With OCSP, if you're also using IPv6, and your IPv6 network config is good... nginx may still behave weirdly and not staple for IPv6 connections. I'm not 100% sure what the cause is but one work around that I've come across is to self proxy to the appropriate OCSP server and then use that listener as your OCSP responder.

server {
    listen [::1]:81;

    location / {
        proxy_pass http://ocsp.int-x3.letsencrypt.org;
    }
}

I'm hoping to resolve that and get rid of the proxy to Let's Encrypt soon.

nginx Virtual Server Config

This incorporates everything mentioned above; that you can actually do within an nginx configuration.

server {
    server_name %{SERVER_NAME}%;
     listen 80;
     listen [::]:80;

    location ^~ /.well-known/acme-challenge/ {
        alias /var/www/dehydrated/;
        default_type text/plain;
    }

    location / {
        return 301 https://$server_name$request_uri;
    }
}

server {
    %{BREVITY}%
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    %{BREVITY}%
    ssl_protocols TLSv1.2;
    ssl_ciphers ECDHE:!3DES:!SHA;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparam.pem;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_stapling_responder http://[::1]:81;
    %{BREVITY}%
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000;includeSubDomains;preload;" always;
    add_header Referrer-Policy "strict-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' www.googletagmanager.com www.google-analytics.com; style-src 'self' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; img-src 'self' data:; upgrade-insecure-requests; referrer origin; report-uri https://routedlogic.report-uri.com/r/d/csp/enforce;" always;
    add_header Expect-CT "enforce,max-age=30,report-uri='https://routedlogic.report-uri.com/r/d/ct/enforce'" always;
    add_header Expect-Staple "max-age=3600;report-uri='https://routedlogic.report-uri.com/r/d/staple/reportOnly';includeSubDomains;preload;" always;
    %{BREVITY}%
}
Author image
About Colin Stubbs
Brisbane, Queensland, Australia
Space monkey meat popsicle with technology and noise addictions.