QR Code contains TinyURL of this article.10 Simple Ways to Secure Your Website

8-lever padlock
Credit: . License: CC BY-NC-SA 2.0


I don’t normally write list-type articles, in fact this is the only one I’ve published here.1  But this topic suits the list format.

In this article, I describe the process of setting headers with the Apache web-server, because that’s what I’m working with. However, every web-server sends response headers, even though they differ in their configuration methods. Please refer to the documentation for your web-server software if you are not using Apache.

Let’s begin with an example of how we control the headers that Apache sends. We can only change the headers if the mod_headers module is available. So we wrap our header directives in protective, conditional logic. This ensures that, if mod_headers is not installed, we don’t crash Apache with configuration directives that it doesn’t understand:

<ifModule mod_headers.c>
  Header set header_name "header_value"

This code can reside in either the httpd.conf or .htaccess files. If you don’t know what this means then I strongly recommend you seek help from your server administrator or hosting company to deploy these security directives.

Rule № 1: Avoid Access-Control-Allow-Origin: *

It is not uncommon for a modern website to include content delivered from another domain. For example, your website might include your recent Flickr uploads, or what you recently listened to on Spotify. Flickr and Spotify serve this content through their public API‍s. A specification called Cross Origin Resource Sharing (CORS) is often used as part of the process.

Conversely, you might provide an API to content or resources on your server. Your API might allow for POST, PUT or DELETE requests. These methods are able to add, change or delete content on your database or web-server.

If you provide such an API then it’s likely that your server will send a CORS response header that will look something like this:

Access-Control-Allow-Origin: http://www.trusted-website.com

This means that your server will happily deliver content to http://www.trusted-website.com. Furthermore, and this is the significant part, if your API provides for it, http://www.trusted-website.com can add, alter or delete content on your database or web-server. That’s fine, because you designed your API to allow such transactions and you’ve explicitly given http://www.trusted-website.com permission to use your API.

However, if you’ve been naïve in your implementation, you might send the following header:

Access-Control-Allow-Origin: *

The problem with this is that you are now allowing any website to interact with your API. If it operates in such a “promiscuous” mode then your API better have exceptional security and validation in place.

To summarise then, unless you absolutely understand the consequences and have developed your web application with the appropriate level of defense, don’t send the Access-Control-Allow-Origin: * header.

Rule № 2: Establish a Cross-Domain Meta Policy

The authors of Adobe Flash and PDF documents can embed content from websites in their productions. As webmaster, you can regulate which of your content, if any, these authors can use in their files. You control this with one or more crossdomain.xml files on your server (as an example, take a look at Twitter’s).

When you don’t want to allow content producers to embed your work in their content, ensure you have no crossdomain.xml files within your website’s directory structure. You should also send the following header with each response from your web-server:

X-Permitted-Cross-Domain-Policies: none

To do this, you should add the following to your website’s httpd.conf or .htaccess file:

Header set X-Permitted-Cross-Domain-Policies "none"

Rule № 3: Prohibit MIME Sniffing

Each type of file delivered from a web-server has an associated MIME type (also called a “content-type”) that describes the nature of the content (e.g. image, text, application and so on). For compatibility reasons, MSIE has a MIME-sniffing feature that will attempt to determine the content-type for each downloaded resource. In some cases, MSIE reports a MIME type different than the type specified by the web-server. For instance, if it finds HTML content in a file delivered with the HTTP response header Content-Type: text/plain, MSIE determines that it should render the content as HTML.

Unfortunately, MIME-sniffing provides an attack vector that malicious actors can use to have the web-browser execute embedded scripts in specially crafted resources. We can prevent the execution of such scripts by prohibiting MSIE from MIME-sniffing. To enable this protective measure, your web-server should send the following header with each response:

X-Content-Type-Options: nosniff

To do this, you should add the following to your website’s httpd.conf or .htaccess file:

Header set X-Content-Type-Options "nosniff"

Rule № 4: Remove Server Identifier

Your server probably tells the bad guys all about itself without them even needing to ask. In the HTTP headers that your web-server returns with each web-page (or asset) there is probably a string that looks similar to this:

Server: Apache/2.2.17 (Fedora)

That innocent looking string tells the hacker what web-server software they’re dealing with (Apache), what the version of that software is (2.2.17) and even what operating system is running on the web-server (Fedora). The problem here is that you’re giving away too much information. If the hacker knows of any vulnerabilities affecting that particular server/version/OS combination then she can get right on with attacking your website, without delay.

It’s surely better to not reveal this information if we can help it.2  3

Sadly, it is difficult to remove this header with the Apache web-server. There are only two ways, that I know of, to not send it:

  1. Edit the Apache source code and re-compile;
  2. Install the ModSecurity Web Application Firewall, then add the following to your website’s httpd.conf or .htaccess file:
<ifModule ModSecurity.c>
  SecServerSignature ''

№ 1 isn’t really an option for the majority. № 2 is more practical, but ModSecurity is a complicated module and it is beyond the scope of this article to go into it further.

So, at least where it concerns the Apache web-server, this step is something of a red herring. Other web-server software (nginx, lighttpd, etc.) might provide for the easy removal of their server header. Where you can remove it, I recommend doing so, because it might just slow the bad actors down a little.

Rule № 5: Remove X-Powered-By

If PHP is responsible for generating or serving your web-pages (or assets), it is nice enough to tell the world about it with a header of its own:

X-Powered-By: PHP/5.2.13

This one’s similar to Rule № 4 in that your server is exposing information that could be useful to an attacker. Notice that we get a version number with this header too. Nice for the attacker, not so nice for you.

Add the following to your website’s httpd.conf or .htaccess file:

Header unset X-Powered-By

image of a padlock overlaid on computer code
System LockCredit: . License: CC BY 2.0

Rule № 6: Enable Strict MSIE XSS Protection

Microsoft’s Internet Explorer (MSIE) software has integrated protection against XSS attacks. You can instruct MSIE in how to behave when it encounters a suspected XSS attack. The best protection is to totally block a suspect website.

Add the following to your website’s httpd.conf or .htaccess file:

Header set X-XSS-Protection "1; mode=block"

Rule № 7: Prohibit Content Framing

Clickjacking is an attack where the hacker instructs the web-browser to render your website content beneath an invisible layer that has malicious click destinations overlaid atop your links. It’s a clever attack that deceives users into clicking links they wouldn’t otherwise interact with, but do because the links appear to be within your legitimate — and once trusted — content.

Clickjacking has the potential to be massively costly and/or destructive. The good news, you can protect against all but the most audacious clickjacking hacks with a simple server header.

Add the following to your website’s httpd.conf or .htaccess file:

Header set X-Frame-Options "DENY"

Rule № 8: Content Security Policy

A Content Security Policy (CSP) is a set of directives in your web-server configuration that govern content. They dictate what type of content you may use on your website and from which sources it may originate. The fine-grained control offered by a good CSP rule-set is probably the best defence against XSS attacks that you can achieve. Furthermore, all modern browsers are able to parse and act on a CSP.

Unfortunately a CSP is complex and can take time to understand, configure and test.4  I recommend you start by reading a good primer on the subject, such as Mike West’s “An Introduction to Content Security Policy.”

As an example, let’s take a look at the CSP of this website:

Header set X-Content-Security-Policy "default-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self' 'unsafe-inline'; connect-src 'self';"

IMPORTANT: This is a particularly strict rule-set. If you copy this into your own httpd.conf or .htaccess file then I can pretty much guarantee that it will break something on your website.

If you are trying to add a CSP to an existing website, then I recommend you start small… start with just the img-src, for example, then test your configuration thoroughly. Wash, rinse, repeat with the next rule.

It’s easier if you are creating a CSP for a new build website. Start with the strictest set of rules and adjust as necessary as your website develops.

Rule № 9: Deploy TLS

If you’re really serious about securing your website, you need to acquire an X.509 certificate so that your users can communicate with your web-server using TLS. This brings all kinds of security benefits:

When your users see that padlock in their web-browsers, they know that their connection is with your website, a trusted source. They know that nobody has tampered with the information they are reading. They know that a third-party cannot read or manipulate any data they might submit to your web-server.5

In the past, there were justifiable reasons not to use TLS, or to only use it for form submissions (particularly those of logging-in and where a user would enter personal details):

  • X.509 certificates were expensive;
  • The process of acquiring an X.509 certificate was tedious;
  • There was a need for a dedicated IP Address for each domain where you would deploy TLS;
  • The encryption process was slow and expensive in terms of CPU time;
  • Your users suffer security warnings if you have any HTTP content on your HTTPS pages.

However, the landscape of technology is constantly changing:

  • X.509 certificates are now inexpensive (or free);
  • You can now acquire a X.509 certificate in minutes;
  • With the SNI extension, TLS no longer requires a dedicated IP address for each domain. All modern web-servers and web-browsers support SNI;
  • Modern hardware performs encryption so quick you can barely notice any delays. The performance of this website, amongst others, shows us that, to all intents and purposes, TLS does not have a negative impact.
  • You can avoid mixed secure/insecure content warnings by proxying HTTP content through your HTTPS server.
    See: Mixing Secured and Unsecured Assets Without Browser Errors.

There is simply no reason why you shouldn’t use TLS today.6

Rule № 10: Strict Transport Security

If you’ve gone to the trouble of creating a TLS-capable website, then your owe it to yourself and your users to ensure that you make full use of the facility. The HSTS header tells the client web-browser to always request content over HTTPS. The header looks like this:

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

This instructs the web-browser to request content over HTTPS, that the directive applies for one year following the original request and that the security policy also applies to all sub-domains.

Add the following to your website’s httpd.conf or .htaccess file:

Header set Strict-Transport-Security "max-age=31536000; includeSubDomains"

You can add to this a configuration that automatically redirects all HTTP traffic to HTTPS. The following code assumes that mod_rewrite is available on your web-server:

# URL Rewriting
RewriteEngine on

# Rewrite any HTTP requests to HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://www.your-domain.com/$1 [R=301,L]

Ultimate Protection: ModSecurity

“ModSecurity is an open source, cross-platform web application firewall (WAF) module. Known as the ‘Swiss Army Knife’ of WAF‍s, it enables web application defenders to gain visibility into HTTP(S) traffic and provides a power rules language and API to implement advanced protections.”

It just doesn’t get any better than this. If you’re serious about the security of your web-server, you can’t go wrong with ModSecurity. It’s a beast of program: it’s nightmarish to configure and the tiniest mistake will lock all visitors out of your website. But, get it right, and nobody’s getting through this baby. Unless Kevin Mitnick has targeted your web-server, you can sleep easy, knowing that ModSecurity’s got your back.

Further Reading


It has taken some time to research this article and test the features I have written about. If I have made any errors or omissions then please do let me know and I’ll revise this document accordingly.

Discuss this subject on Hacker News…

  1. At the time of writing. ↩︎

  2. We call this “security through obscurity.” ↩︎

  3. Actually, any hacker worth her salt won’t miss this header if it’s absent. She can use “server fingerprinting” to tell her what she needs. But, if removing the header prevents just one attack, then it’s worth the effort. ↩︎

  4. Testing your CSP is essential. The stricter your rule-set the more likely it is that something will break on your website. ↩︎

  5. How the NSA, and your boss, can intercept and break SSL ↩︎

  6. With TLS established on your web-server, you should also consider the deployment of SPDY, which brings a performance boost and security features of its own. ↩︎