Code Father's Blog

/dev/null/Projects - Oct 14, 2018

Make Your SSL Get an A+ Rating


Update (2020-12-22): The Feature-Policy header option is being replaced by the Permissions-Policy header.  The headers have been updated to add the Permissions-Policy header.

Update (2019-02-09): Please keep in mind that these settings are for cPanel servers running WebHost Manager (WHM) and Apache.  If you’ve got an NGINX installation, see this post.

Update (2018-11-17): While this article primarily talks about a FreeSSL, these steps apply to any SSL really since the SSL is only as good as the encryption that the webserver actually provides.  This article changes the web server settings and as such, changes the encryption offered for any SSL certificate.  These steps have provided PCI DSS, HIPAA, and NIST compliance per ImmuniWeb.

So, you have website hosting, they’ve given you a FreeSSL (a.k.a. AutoSSL) from cPanel and you’ve visited   Qualys SSL LabsImmuniWeb, or some other SSL testing site and found that you’re getting a “B” grade at best due to something called “Forward Secrecy Question” or ECDHE limiting your grade. How do you get that grade to an “A+” you might ask. Some would think that you need to buy an SSL when the fact of the matter is that you probably just need to make a few tweaks to your .htaccess file, your Apache configuration, or both.

One reason that we prefer the High-Tech Bridge site is that the report clearly shows if your site is PCI DSS, HIPAA, or NIST security compliant.  So if you accept credit cards, then you’ll want to make sure that your site is PCI DSS compliant so that accepting credit cards won’t be a problem since some payment processing companies perform their own scans to ensure that your SSL certificate meets regulations.

Security Headers

To start, we’ll begin with the security headers. These headers control a number of items that browsers read and as such it tells a browser how long to cache files, find pages and how to display websites. There are about 30 lines of code that you can add to your .htaccess file that will help right out of the gate. Those lines are:

#<#<#< BEGIN HIGH-GRADE SSL HEADERS >#>#>#
# These force additional security for all pages. These values result in an "A+"
# grade at https://securityheaders.com/. These also help the Qualsys SSL Labs and
# ImmuniWeb tests get an "A+" rating. Visit at:
# Qualsys SSL: https://www.ssllabs.com/ssltest/index.html
# ImmuniWeb: https://www.immuniweb.com/ssl/ <<-- Preferred
Header always set Content-Security-Policy "report-uri https://your-domain.report-uri.com/r/d/csp/reportOnly"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-UA-Compatible "IE=Edge"
Header always set Referrer-Policy "strict-origin"
Header set Cache-Control "no-transform"
Header set Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
Header set Feature-Policy "vibrate 'self'; sync-xhr 'self' https://your-domain.com"
Header set NEL "{\"report_to\":\"default\",\"max_age\":31536000,\"include_subdomains\":true}"
Header set Expect-Staple "report-uri='https://your-domain.report-uri.com/r/d/staple/reportOnly'"
Header set Expect-CT "max-age=0, report-uri='https://your-domain.report-uri.com/r/d/ct/reportOnly'"
Header set Report-To "{\"group\":\"default\",\"max_age\":31536000,\"endpoints\":[{\"url\":\"https://your-domain.report-uri.com/a/d/g\"}],\"include_subdomains\":true}"
Header set NEL "{\"report_to\":\"default\",\"max_age\":31536000,\"include_subdomains\":true}"

# Rewrite any session cookies to make them more secure
# Make ALL cookies created by this server set as HttpOnly and Secure
Header always edit Set-Cookie (.*) "$1;HttpOnly;Secure"

# Strip off double Secure or HttpOnly settings as if App and Apache sets above you can sometimes get both
Header edit Set-Cookie ^(.*);\s?Secure;?\s?(.*);\s?Secure;?\s?(.*)$ "$1; $2; $3; Secure"
Header edit Set-Cookie ^(.*);\s?HttpOnly;?\s?(.*);\s?HttpOnly;?\s?(.*)$ "$1; $2; $3; HttpOnly"

# Strip off double ;; settings
Header edit Set-Cookie ^(.*);\s?;\s?(.*)$ "$1; $2"
#<#<#< END HIGH-GRADE SSL HEADERS >#>#>#

Make sure that you change the parts to your actual domain.  You can also omit the “report-uri” directive if you want since it’s really not needed.

For those of you WordPress users that use the Really Simple SSL plugin, that second section does the secure cookies that you have to pay extra for.  The same goes for that Content-Security-Policy entry for HSTS. You’re welcome.

Side note: If you want to view full error reporting for the URI, then pay a visit to Report URI for a free account.

If you haven’t already, then make sure you’re also forcing HTTPS on your visitors so that all pages are only served via secure channels.  You can achieve this by adding the following lines above the header entries above:

# FORCE HTTPS FOR ALL TRAFFIC
RewriteEngine On
RewriteCond %{SERVER_PORT} 80
RewriteCond %{REQUEST_URI} !^/[0-9]+\..+\.cpaneldcv$
RewriteCond %{REQUEST_URI} !^/[A-F0-9]{32}\.txt(?:\ Comodo\ DCV)?$
RewriteCond %{REQUEST_URI} !^/\.well-known/pki-validation/[0-9a-zA-Z_-]+$
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# END FORCE HTTPS

Now that you’ve added those, head on over to the Security Headers website and test your site to make sure that all of the security headers are valid. If so, you should have an “A+” score. If not, scroll down and read the warnings or errors that are causing to not get the full “A+” score.

Setting the Encryption in Apache and WHM

Now that the headers are done, you can visit  Qualys SSL Labs or High-Tech Bridge to test your SSL certificate to see if you get an “A+” grade there as well. If not, you’re probably missing some encryption ciphers or you have encryption ciphers that decrease the encryption or widen it for support of more devices and more browsers (i.e. older browsers that you shouldn’t support if security matters — looking at you IE 6 and Outlook Express. If that’s the case, read on.

To configure TLS for Apache, i.e. your web server, log in to your Web Hosting Manager (WHM) and navigate to Home -> Service Configuration -> Apache Configuration -> Global Configuration. The protocol and cipher settings will be the first two in that interface. The following is the default for cPanel version 68 and higher for the SSL Cipher Suite:

ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256

Change that to be:

ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:!DSS

For the SSL/TLS protocols, it probably just has All -SSLv2 -SSLv3, so you need to add -TLSv1 to the list so that TLS v1.0 is turned off, which is required to obtain PCI DSS compliance, like so:

All -SSLv2 -SSLv3 -TLSv1

Save the Apache Global configuration, then scroll down and click “Rebuild Configuration and Restart Apache.”

Usually, the client’s preference will be used when choosing the protocol and cipher that will be used when establishing a secure connection. If may need to use the server’s preference, add the following in Home > Service Configuration > Apache Configuration > Include Editor > Pre VirtualHost Include, select “All versions” from the pull-down menu, then add the following to the text box that appears:

# Enable SSLUseStapling
SSLUseStapling On
SSLStaplingCache "shmcb:/tmp/stapling_cache(150000)"
SSLHonorCipherOrder On
SSLCompression Off
SSLSessionTickets Off

# Enable HTTP Strict Transport Security
Header set Strict-Transport-Security "max-age=31536000" env=HTTPS
Header always set X-XSS-Protection "1; mode=block;"
Header always set X-Content-Type-Options "nosniff"

# DISABLE CACHING
Header set Cache-Control "max-age=0, private, no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires 0

# Enable GZIP Compression.
SetOutputFilter DEFLATE

# Netscape 4.x has some problems...
BrowserMatch ^Mozilla/4 gzip-only-text/html

# Netscape 4.06-4.08 have some more problems
BrowserMatch ^Mozilla/4\.0[678] no-gzip

# MSIE masquerades as Netscape, but it is fine
# BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# NOTE: Due to a bug in mod_setenvif up to Apache 2.0.48
# the above regex won't work. You can use the following

# workaround to get the desired effect:
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
# Don't compress images
SetEnvIfNoCase Request_URI .(?:gif|jpe?g|png)$ no-gzip dont-vary

# Make sure proxies don't deliver the wrong content
Header append Vary User-Agent env=!dont-vary

Click the “Update” button and then click “Restart Apache”.  That should take care of it. Save your Apache settings, then rebuild and restart the Apache service so that the changes can go into effect. From there, visit  Qualys SSL Labs or ImmuniWeb again to retest your site to see if any further changes are needed.

We’re interested to know how this works for you. Leave a comment below!