HOWTO: A+ with all 100%’s on SSL Labs test using Nginx mainline & stable


Steps to get you all 100% and A+ using Nginx mainline & stable version

  • Certificate Section

This section is easy to get 100% on.
Make sure your cert and chain are in the correct order.
Don’t use SHA1 (use SHA256) for the signature algorithm.
Use a well known/trusted CA.

  • Protocol Support Section

If you use only TLS1.2, you’ll get 100% on this section. (Warning: Older Client will not able to load your site)

ssl_protocols TLSv1.2;

If you use TLS1.1&1.2, you’ll get 95% on this section. (Warning:Some Older Client still will not be able to load your site)

ssl_protocols TLSv1.1 TLSv1.2;
If you use TLS1.0~1.2, you’ll get 90% on this section. (This is the best compatibility option)

ssl_protocols TLSv1.0 TLSv1.1 TLSv1.2;

Please do not use Protocols older than TLS1.0 since it’s been deprecated.

  • Key Exchange Section

Use 4096-bit RSA or secp256 and higher ECC Certificate (This is a must if you want 100% on Key Exchange)

Example (RSA):
_ certbot --rsa-key-size 4096 -(other-arguments) _

Example: (ECC):

First generate ECC key & CSR.

_Please follow step one and two from this tutorial: _

Then use certbot, specify csr
certbot --csr (/ecc-csr-path) -(other-arguments)

Generate 4096 bit (AT LEAST) dhparam file (Ignore it if you don’t want to use dh suites)

cd /etc/ssl/certs (move to folder where you store certs, default is /etc/ssl/certs.)
openssl dhparam -out dhparam.pem 4096 (change 4096 to larger if you want, it’s taking a long time to generate)

Important Note:
_If you are using DH suites and RSA/ECC certificates, ssllab consider the smallest exchange size as the final score. (e.g. If you have 4096 bit RSA and 2048 bit DHparam, you’ll get 90%) _

  • Cipher Strength Section

You’ll need to use ciphers bigger or equal to 256 bit to get 100% (Warning: some client may not able to connect when using those cipher)

Personally, suggest using Ciphers >=128 bit for best compatibility (Will get 80% in score)

Use ECDH curve >= 256bit (optional if you are not using ECDHE suite)
ssl_ecdh_curve secp384r1;

  • Get A+ in letter grade

Enable HSTS with long duration (More than 1 week)

HSTS is a header which can force visitor use https (Warning: browser will not connect to your site if there’s no ssl configured and HSTS is enabled)

Entry level hsts (Just satisified A+)
add_header Strict-Transport-Security "max-age=63072000;";

Hsts setup that would require ssl on all subdomains (After visiting your root domain, your non-https subdomain will not be accessable.)
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains;";

Hsts setup that can request to be hardcoded in browser HSTS list (After adding this option, you can apply to be inside hsts preload list, which is a list that was hardcoded into browser, means your non-https site will present an error message to all visitors even if they never visit your base domain before. it’s very hard to be removed from the list)
` add_header Strict-Transport-Security “max-age=31536000; includeSubDomains;” preload;

  • Other Settings

Enable OCSP Stapling (Optional)
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate your cert path/letsencrypt-full-chain.pem; (the CA&Intermediate CA file for your cert)

Expect CT (Optional)
This is a new header which can tell Browsers if they are trying to find Certificate Transparency (log) (Explained in
Few Usages:
testing only (With no report uri): add_header Expect-CT "max-age=0";
testing only (With report-uri) (Please follow the link above for report-uri’s usage) : add_header Expect-CT "max-age 0, report-uri https://{$subdomain} ";
enforce (go for short duration first): add_header Expect-CT "enforce, max-age 30, report-uri https://{$subdomain} ";

You can download the letsencrypt full CA & intermediate in one file @

Add xframe (Optional)
add_header X-Frame-Options DENY;

Sample 443 File.
Change the variable, domain and paths

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

        server_name  your domain;
        #access_log  your access_log path main;
        error_log your web path/logs/error.log warn;

	ssl_certificate your cert path
	ssl_certificate_key your cert path/

        ssl_protocols TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ecdh_curve secp384r1;
        ssl_session_cache shared:SSL:20m;
        ssl_session_timeout  60m;
        ssl_session_tickets off;
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_trusted_certificate your cert path/letsencrypt-full-chain.pem;
        resolver valid=300s;
        resolver_timeout 5s;
        #add_header Strict-Transport-Security "max-age=63072000;";
        #add_header X-Frame-Options DENY;
        #add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
        add_header Expect-CT "max-age=0";

        # END BLOCK #
	ssl_dhparam /etc/ssl/certs/dhparam.pem;

        location ~ /.well-known {
                allow all;

	# The rest of your server block
        root   website path;
        index index.html index.php index.htm;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
            try_files $uri $uri/ =404;

	## Your only path reference.
        ## This should be in your http block and if it is, it's not needed here.
        location = /favicon.ico {
                log_not_found off;
                access_log off;

	location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;

	location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                expires max;
                log_not_found off;

  • Update:
  1. According to @Osiris, remove some unnecessary part of hsts.
  2. Add more clarification, restructure the doc.
  3. Expect CT Header (Prepare for Certificate Transparency)
  • Credit:

Make key exchange and cipher strengh 100%
Can't access my website via https

Be careful! Only add “includeSubdomains” if that really is what you want.

If you have a subdomain without HTTPS (for example, a separate device which doesn’t support TLS on a separate subdomain), you won’t be able to access that host if the browser has “seen” the HSTS header with the “includeSubdomains” option!

@stevenzhu Please elaborate some more on the different options you’re suggesting. As noted above about HSTS for example. Also, generating DH parameters isn’t neccessary if the user disables non-ECC DH al together. I would suggest giving the user some well informed options.

@stevenzhu The “includeSubdomains” option is powerful and can be very useful! Sorry if I wasn’t clear, but I didn’t mean it should be removed in total. Just a warning to novice users who just copy/paste everything they see without knowing what it is they’re pasting…


This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.