4 Security Headers Every Website Should Have
As web developers and agencies, we're constantly building and optimizing. While performance and features get a lot of attention, security can sometimes take a backseat. This is a mistake. Implementing a few key HTTP security headers is a relatively low-effort, high-reward way to significantly improve your site's defense against common attacks.
These headers are instructions sent from your web server to the user's browser. The browser then enforces these rules, making it harder for attackers to exploit vulnerabilities. Let's cover four essential ones.
HSTS (HTTP Strict Transport Security)
HSTS tells the browser to only connect to your website using HTTPS, even if the user types in http://yourdomain.com or clicks an old http link. This prevents "man-in-the-middle" attacks where an attacker intercepts an HTTP connection to steal or modify data.
The header looks like this: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload.
-
max-age: How long the browser should remember to enforce HTTPS (here, one year). -
includeSubDomains: Applies the rule to all subdomains. -
preload: This is optional but powerful. It allows you to submit your site to a browser-maintained list, so even the first visit defaults to HTTPS.
Common Mistake: Trying to implement HSTS before you are absolutely certain your entire site, including all subdomains and assets, is correctly served over HTTPS. If your HTTPS setup has issues, HSTS will prevent users from accessing your site entirely. Test thoroughly.
Checking with curl:
curl -I https://yourdomain.com
Look for the Strict-Transport-Security header in the output.
Configuration Examples:
Apache: Add to your .htaccess file or virtual host configuration. Ensure mod_headers is enabled.
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</IfModule>
Nginx: Add to your server block.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Cloudflare Workers:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const response = await fetch(request);
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
return response;
}
Content-Security-Policy (CSP)
CSP is a powerful header that helps mitigate cross-site scripting (XSS) and other code injection attacks. It allows you to specify which sources of content (scripts, styles, images, etc.) the browser is allowed to load for your page. It's like a whitelist for your website's resources.
A basic CSP might look like: Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none'; base-uri 'self'.
-
default-src 'self': By default, only allow content from your own domain. -
script-src 'self' https://trusted.cdn.com: Specifically allow scripts from your domain and a trusted CDN. -
object-src 'none': Prevent plugins like Flash or Java from being loaded. -
base-uri 'self': Restrict the URLs that can be used in a page's<base>tag.
CSP can get complex quickly, and it's best to start with a restrictive policy and then loosen it as needed based on your site's requirements. Use report-uri or report-to directives to log violations before going fully enforced.
Checking with curl:
curl -I https://yourdomain.com
Look for the Content-Security-Policy header.
Configuration Examples:
Apache:
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';"
</IfModule>
Note: 'unsafe-inline' for scripts is often necessary for WordPress themes and plugins. Aim to remove it over time by moving inline scripts to external files.
Nginx:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always;
Cloudflare Workers:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const response = await fetch(request);
response.headers.set('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';");
return response;
}
X-Frame-Options
This header controls whether your site can be embedded within an <iframe>, <frame>, <object>, or <applet>. This is crucial for preventing "clickjacking" attacks, where an attacker tricks a user into clicking on something different from what they perceive.
Common values:
-
DENY: The page cannot be displayed in a frame, regardless of the site attempting to do so. -
SAMEORIGIN: The page can only be displayed in a frame on the same origin as the page itself. -
ALLOW-FROM uri: The page can only be displayed in a frame on the specified origin. (This is deprecated and less reliable).
Checking with curl:
curl -I https://yourdomain.com
Look for the X-Frame-Options header.
Configuration Examples:
Apache:
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
</IfModule>
Nginx:
add_header X-Frame-Options "SAMEORIGIN" always;
Cloudflare Workers:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const response = await fetch(request);
response.headers.set('X-Frame-Options', 'SAMEORIGIN');
return response;
}
Referrer-Policy
The Referrer-Policy header controls how much referrer information (the URL of the page that linked to the current page) is sent with requests. Sensitive information can be leaked through the referrer header, especially when navigating from secure to non-secure pages, or between different sites.
Common values:
-
no-referrer: No referrer information is sent. -
no-referrer-when-downgrade: Referrer is sent only when the target scheme is more secure than the current one (e.g. HTTP to HTTPS). -
origin-when-cross-origin: Only send the origin (scheme, host, port) when the destination is cross-origin. -
strict-origin-when-cross-origin: Send the origin only when the destination is cross-origin, and send the full URL only when it's same-origin. -
same-origin: Referrer is sent only when the destination is same-origin.
For most sites, strict-origin-when-cross-origin is a good balance between privacy and analytics functionality.
Checking with curl:
curl -I https://yourdomain.com
Look for the Referrer-Policy header.
Configuration Examples:
Apache:
<IfModule mod_headers.c>
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
Nginx:
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Cloudflare Workers:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const response = await fetch(request);
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
return response;
}
By implementing these four headers, you're taking significant steps to secure your websites and protect your users. Regularly test your headers using tools like curl or online security scanners to ensure they are configured correctly and consistently.
SiteVett checks this automatically as part of a free website QA scan with 60+ checks across security, SEO, content, performance, and more.
Top comments (0)