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.

SSL chiper parameters