How to harden your webserver without fscking up your browser compatibility?


ssllabs Icon Encryption and security is a must-have these days. The more secure, the better. A good start is to have a solid ssl configuration on your webserver. With ssllabs you can test your webserver's ssl configuration and its certificates.

As of today, the following apache httpd config snippets may give you an A+ rating, if your SSL certificate is valid, trusted, has a key size of at least 2048 bits and it's hash algorithm is sha2/sha256.

SSLv2 and SSLv3 are considered insecure and should be disabled:

SSLProtocol all -SSLv2 -SSLv3

This cipher list suits all modern browsers and operating systems, only leaving IE6/XP behind (who needs that anyway?). ECDSA ciphers are only relevant for ECDSA certificates. As far as I know only very few certificate authorities provide ECDSA, while RSA is common. Since POODLE does not affect TLS (yet), it is assumed safe to allow CBC ciphers for TLSv1.

SSLHonorCipherOrder on

Modern browsers support (at least) the elliptic curves secp256r1, secp384r1 and secp521r1. To force stronger curves, use the following directive:

SSLOpenSSLConfCmd Curves secp521r1:secp384r1

I recommend using the cipher list and curves for mail, vpn, ldap and other servers/services, too.

SSL compression

To prevent the CRIME attack, disable SSL compression. Which is the default behaviour.

SSLCompression off

HTTP Strict Transport Security

To prevent downgrade attacks (i.e. switching from https to http), you should add HSTS headers with a long duration. If your hostname has subdomains (e.g. you should add includeSubDomains. You might want to add your HSTS policy to a central database browsers like Firefox, Chrome and IE use. In that case, you must add preload to the header.

<IfModule headers_module>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

HTTP Public Key Pinning

You can make man in the middle attacks more difficult by providing a list of your certificate's / private key's fingerprints using an HPKP header. Keep in mind the first access to your webserver is still "unprotected", as at this point the HPKP header is not known the client yet. You should always include a backup key in case your current private key gets compromised.

get the fingerprint from your certificate:

$ openssl x509 -pubkey -noout -in certfile.pem | sed '1d;$d' | base64 -d | openssl dgst -sha256 -binary | base64

get the fingerprint from your private key:

$ openssl pkey -pubout -in keyfile.key | sed '1d;$d' | base64 -d | openssl dgst -sha256 -binary | base64

and finally add the header:

<IfModule headers_module>
    Header always set Public-Key-Pins "max-age=86400; pin-sha256=\"current_fingerprint=\";pin-sha256=\"backup_fingerprint\";"

OCSP stapling

To show that your certificate was not revoked, you may want to use OCSP stapling to add a timstamped OCSP response to your certificate. The validity duration depends on the certificate authority, StartCom has switched from one day to five days a few months ago.

<IfModule socache_shmcb_module>
    SSLUseStapling On
    SSLStaplingCache shmcb:/var/cache/httpd/ssl/ssl_stapling(512000)
    SSLStaplingStandardCacheTimeout 21600
    SSLStaplingErrorCacheTimeout 600
    SSLStaplingResponseMaxAge 432000 # 5 days
    SSLStaplingResponderTimeout 10
    SSLStaplingReturnResponderErrors Off


securityheaders Icon

To prevent clickjacking, set the SAMEORIGIN policy.

<IfModule headers_module>
    Header always append X-Frame-Options SAMEORIGIN


Avoid falsely detected MIME types and thus execution of malicious code by disabling MIME-sniffing for the IE:

<IfModule headers_module>
    Header always append X-Content-Type-Options nosniff


To set the browser's built-in reflective XSS protection use the following line. mode=block is recommended, not setting it will result in the browser sanitising the script instead of blocking it.

<IfModule headers_module>
    Header always set X-Xss-Protection "1; mode=block"


Content-Security is a bigger chapter. You'll need to find all scripts, css, images, fonts, etc. your website uses, either loaded from your domain, externally, or used inline, and specify which sources are valid for them. See this website's header for example. The most basic and most secure setting is the following. But it probably will fsck up your website. See this site for a very good reference about Content-Security-Policy.

<IfModule headers_module>
    Header always set Content-Security-Policy "default-src 'none'"


Prevent client side scripts accessing the protected cookie:

<IfModule headers_module>
    Header always edit Set-Cookie "(?i)^((?:(?!;\s?HttpOnly).)+)$" "$1; HttpOnly"

And secure the cookies by using SSL:

<IfModule ssl_module>
<IfModule headers_module>
    Header always edit Set-Cookie "(?i)^((?:(?!;\s?secure).)+)$" "$1; Secure"