The essential no-excuses security checklist for modern websites.

Sep 16, 2019
Last update: Sep 22, 2020
~ 5 min

The web and its security has come a long way. As always in security there are constantly ways to improve your defences agains bad actors. This is a list of quick and easy, yet powerful tools that all devs should be using.

Update 18 Sep 2019 @ 16:02
As the reddit user zfa suggested I included the link to the Mozilla Observatory for automatic auditing.

  1. Checklist
    1. HTTPS
    2. TLS Versions
    3. Ciphers
    4. HSTS
    5. CSP
    6. X-Frame-Options
    7. X-Content-Type-Options
  2. Useful Libraries & Tools
  3. Considerations
    1. Cookies or LocalStorage for JWTs?
    2. Advanced HTTPS practices
  4. Sources
Photo by MILKOVÍ on Unsplash


Let’s start with the most obvious. It’s almost 2020 and websites not using HTTPS are simply being irresponsible. There is no reason to run plaintext http in the era of Letsencrypt where getting a certificate is easy and free. I won’t go over how to configure that, there are tons of resources out there and generally its as simple as adding a line in your config file.
Also redirect all the http traffic to https automatically, basic stuff.

2. TLS Versions

97.65% of global users support TLS version 1.2, so go disable anything below that in your server as it has knows vulnerabilities!


# Nginx
ssl_protocols TLSv1.2;
# Apache
SSLProtocol -all +TLSv1.2
# Traefik
      minVersion = "VersionTLS12"

3. Ciphers

Similar to the TLS version you should avoid using anything different than AES or ChaCha20. Limit the ciphers to something secure and modern.


# Nginx
ssl_prefer_server_ciphers on;
# Apache
SSLHonorCipherOrder     on
SSLCompression          off
SSLSessionTickets       off
# Traefik
      cipherSuites = [


Once your website runs on HTTPS it’s a good idea to tell the browser not to use HTTP anymore. Thats what the HTTP Strict Transport Security (HSTS) is designed for.

Strict-Transport-Security: max-age=31536000; includeSubDomains

This basically tells the browser to only talk to your domain via HTTPS for the next year. You can exclude the includeSubDomains if you want to just target your root.


# Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

5. Content Security Policy (CSP)

This awesome HTTP header is incredibly powerful! It allows you to specify the allowed origins for all kind of files that will be loaded into your website.

The basic idea is: “Javascript can only be loaded from my domain, images only from the unsplash domain and fonts only are only allowed from google fonts”.

Awesome right? The header for that example would look like this:

Content-Security-Policy: "default-src 'self'; img-src; font-src"

This allows a dev to specify exactly what resources are allowed to load from what domains. Don’t worry, you don’t need to remember the exact syntax:
Use the generator:

6. X-Frame-Options

This is a basic one, but it should not be forgotten. It prevents your website to be displayed inside another one. This prevents shit tons of possible attack vectors. Simply set the header and you’re done.

x-frame-options: SAMEORIGIN


# Nginx
add_header x-frame-options "SAMEORIGIN" always;
# Apache
header always set x-frame-options "SAMEORIGIN"

7. X-Content-Type-Options

Again, a little header that prevents lots of damage. By setting the Content Type header you prevent the browser from guessing what file contents a downloaded resource is. Basically if /image.jpeg is actually a .js file the browser would still run it if you don’t set the header

x-content-type-options: nosniff


# Nginx
add_header x-content-type-options "nosniff" always;
# Apache
header always set x-content-type-options "nosniff"

Libraries & Tools


For some automatic auditing of your website you can use the excellent Mozilla Observatory tool. You can scan a domain and receive information on Header & TLS.


For a lot of this headers there are some good libraries for automating this which already have good default values, so most of them are plug and play.

Express (Node)helmet
Django (Python)django-csp
Dropwizard (Java)dropwizard-web-security
Flask (Python)Talisman
Hapi (Node)blankie
Koa (Node)koa-helmet
PHPSecure Headers
Ruby (and Rails)Secure Headersrack-secure_headers


I did not talk about the XSS header, since it’s not supported at all in Firefox and Chromium will remove it in the near future, so I think it gives a false sense of security to devs not being vulnerable to XSS if they set the header.

Also I did not touch on the new Feature Policy header wich is currently being drafted. It’s very cool and will help a lot in the future. It allows websites to specify what features should be allowed to run, so e.g. if my site does not need the accelerometer just turn it off. That way no 3rd party code is able to access it. Very nice addition, but at the time of writing it’s still very alpha and basically not supported.

Where to store JWTs?

Most websites nowadays make use of JWTs for the authentication. A common question people have is: Where do I store my token? Cookies or LocalStorage? TLDR: LocalStorage.

The general knowledge is that LocalStorage is not vulnerable to CRFS and Cookies not to XSS. However as the reddit user Devstackr described here if you have a XSS vulnerability also your authentication via cookie is compromised, as the injected code can do requests on behalf of the authenticated user.

So while your token cannot be directly stolen from the victim, the attacker can still do everything, including changing the password for example. Additionally you don’t need to worry about CRFS at all, which is a huge bonus.

Advanced Practices

Large websites should additionally consider the following security features:

  • Certificate Authority Authorization (CAA) DNS record which specifies what CA is allowed to sign certificates for the served domain.
  • HTTP Public Key Pinning (HPKP) provides the option to validate the certificate via DNS record. If misconfigured this can break you entire site, so use carefully!

Sources / Further reading