What Are Security Headers?
HTTP security headers are directives sent by a web server in its response headers that instruct the browser to enable or disable specific security features. They act as an additional layer of defense, telling the browser how to behave when handling your site's content.
Think of them as security policies your server communicates to every visitor's browser. Without these headers, browsers use their default behavior, which is often permissive. By setting security headers, you restrict what the browser is allowed to do, significantly reducing the attack surface of your web application.
Security headers do not replace secure coding practices -- they complement them. Even if your application has a vulnerability, properly configured headers can prevent or limit exploitation. This is the principle of defense in depth: multiple overlapping protections so that no single failure is catastrophic.
Security headers are configured on the web server (Nginx, Apache, IIS) or in your application code. They cost nothing to implement, require no client-side changes, and can dramatically improve your site's security posture.
Content-Security-Policy (CSP)
Content-Security-Policy is the most powerful security header available. It controls which resources the browser is allowed to load for your page -- scripts, stylesheets, images, fonts, frames, and more. CSP is the primary defense against Cross-Site Scripting (XSS) attacks.
How CSP Works
CSP uses directives to define allowed sources for each resource type. If a resource does not match the policy, the browser blocks it and logs a violation.
# Basic CSP header
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src *; frame-src 'none';
Key Directives
'self' to only allow resources from your own domain.
'unsafe-inline' and 'unsafe-eval' -- they defeat the purpose of CSP. Use nonces or hashes instead.
'unsafe-inline' is sometimes needed for legacy sites but should be avoided if possible.
'self' plus specific CDN domains.
'none' if you do not use iframes.
A misconfigured CSP can break your site by blocking legitimate resources. Use
Content-Security-Policy-Report-Only first to monitor violations
without enforcing the policy. Once you are confident no legitimate resources are
blocked, switch to the enforcing header.
Nginx Configuration Example
# In your Nginx server block
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;
X-Content-Type-Options
This header prevents browsers from MIME-type sniffing. Without it, a browser might
interpret a file differently than the server intended. For example, an attacker could
upload a file with a .jpg extension that actually contains JavaScript.
If the browser sniffs the content and determines it is JavaScript, it might execute it.
X-Content-Type-Options: nosniff
The nosniff value tells the browser to strictly follow the
Content-Type header sent by the server. If the server says a file is an
image, the browser treats it as an image -- period. It will not try to guess the type
from the content.
This is a single-value header with no configuration complexity. There is no reason not to include it. It prevents an entire class of attacks with zero risk of breaking your site.
X-Frame-Options
X-Frame-Options controls whether your page can be embedded inside an
<iframe> on another site. This is critical for preventing
clickjacking attacks, where an attacker overlays your page with transparent
elements to trick users into clicking hidden buttons.
# Prevent any site from framing your page
X-Frame-Options: DENY
# Only allow your own site to frame your pages
X-Frame-Options: SAMEORIGIN
Clickjacking Example
Imagine a banking site that does not set X-Frame-Options. An attacker creates a page with an invisible iframe loading the bank's transfer page, positioned so the "Confirm Transfer" button aligns with a visible "Click here to win a prize" button. The user thinks they are clicking the prize button, but they are actually confirming a bank transfer.
With X-Frame-Options: DENY, the browser refuses to load the banking
page inside the iframe, and the attack fails completely.
The CSP directive frame-ancestors provides the same protection with
more flexibility (you can whitelist specific domains). Modern browsers support both,
but setting both ensures backward compatibility with older browsers.
Strict-Transport-Security (HSTS)
HSTS forces browsers to only communicate with your site over HTTPS. Once a browser receives an HSTS header, it will automatically convert any HTTP request to HTTPS for the specified duration, without ever making an insecure request.
# Enforce HTTPS for 1 year, including all subdomains
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Parameters
Once a browser caches your HSTS policy, it will refuse plain HTTP connections
until max-age expires. If you later need to serve HTTP for any reason,
visitors who received the header will be unable to connect. Start with a short
max-age and increase it only after confirming everything works.
Referrer-Policy
When a user clicks a link on your site to go to another site, the browser normally
sends a Referer header containing the URL they came from. This can leak
sensitive information such as query parameters, session tokens in URLs, or private
page paths.
# Recommended: send origin only when navigating to another site
Referrer-Policy: strict-origin-when-cross-origin
Common Values
Permissions-Policy
Permissions-Policy (formerly Feature-Policy) controls which browser features and APIs your page can use. This includes sensitive capabilities like the camera, microphone, geolocation, payment requests, and more.
# Disable sensitive features not needed by your site
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()
Each feature is set to a list of allowed origins. An empty list () disables
the feature entirely. (self) allows only your own origin to use it.
Specific domains can be listed for features you need from third-party embeds.
# Allow camera only for your domain and a specific video service
Permissions-Policy: camera=(self "https://video.example.com"), microphone=(), geolocation=()
Disable every feature you do not actively use. If your site has no reason to access the camera, microphone, or GPS, disable them explicitly. This prevents any injected script from accessing these APIs even if an XSS vulnerability exists.
Checking Your Headers
After configuring security headers, you need to verify they are being sent correctly. Here are several methods to check.
Using Browser Developer Tools
Open your browser's Developer Tools (F12), go to the Network tab, click on the initial page request, and look at the Response Headers section. All security headers you configured should appear there.
Using curl
# View all response headers
curl -I https://yoursite.com
# Check for specific headers
curl -sI https://yoursite.com | grep -iE "content-security|x-frame|x-content-type|strict-transport|referrer-policy|permissions-policy"
Online Scanners
Several free tools grade your security headers and provide recommendations:
- securityheaders.com -- Scans your site and provides an A-F grade with detailed explanations for each missing or misconfigured header
- Mozilla Observatory (observatory.mozilla.org) -- Comprehensive scan that checks headers plus additional security best practices
- CSP Evaluator (csp-evaluator.withgoogle.com) -- Specifically analyzes your Content-Security-Policy for weaknesses
Complete Nginx Example
# Add to your Nginx server block or include file
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;
always keyword matters.
In Nginx, add_header without always only sends the
header on successful responses (2xx/3xx). Error pages (4xx/5xx) will not include
your security headers. Always use the always parameter to ensure
headers are sent on every response.
Summary
HTTP security headers are a low-cost, high-impact defense layer for any web application. Here is what you learned:
- Content-Security-Policy -- Controls which resources the browser can load, preventing XSS and data injection attacks
- X-Content-Type-Options -- Prevents MIME-type sniffing with a simple
nosniffdirective - X-Frame-Options -- Blocks clickjacking by preventing your pages from being embedded in iframes
- Strict-Transport-Security -- Forces HTTPS connections, eliminating SSL stripping attacks
- Referrer-Policy -- Controls how much URL information leaks when users navigate to other sites
- Permissions-Policy -- Restricts which browser APIs (camera, mic, GPS) your page can access
- Always verify your headers using browser tools,
curl, or online scanners
You now know how to harden web applications using HTTP security headers. These headers work alongside secure coding practices to create multiple layers of protection. Next, learn about Cross-Site Scripting (XSS) to understand one of the attacks these headers help prevent.