How to Implement Security HTTP Headers to Prevent Common Vulnerabilities
Add browser-enforced HTTP security headers safely: configure a baseline, roll out CSP in report-only mode, and verify responses across your site.

To implement security HTTP headers, configure your web server, reverse proxy, CDN, gateway, or application middleware to add the intended response headers, then verify them on representative responses. Start with low-risk controls such as X-Content-Type-Options: nosniff and a deliberate Referrer-Policy. Roll out Content Security Policy (CSP) in report-only mode, inspect violations, and enforce it only after accounting for the application’s real scripts, styles, images, fonts, frames, workers, and connections. Add HSTS only when HTTPS is correctly deployed; include subdomains only when all covered subdomains are ready.
Headers are browser-enforced controls, not replacements for output encoding, sanitization, authentication, authorization, secure TLS configuration, or dependency management. OWASP and MDN describe them as one layer of application security. OWASP’s HTTP Headers Cheat Sheet and the MDN CSP guide provide the primary references for the decisions below.
1. Find the component that owns response headers
A response may pass through several layers: application code, web server, reverse proxy, CDN, and API gateway. Identify which layer should own the policy. If multiple layers set the same header, they may overwrite one another or send duplicate policies. Keep one documented source of truth, and confirm that it applies to the response types you intend to protect.

Check the whole delivery path, including redirects, error pages, API responses, static assets, and authenticated pages. Middleware that runs only for successful HTML responses can leave other responses uncovered. Conversely, some headers are most meaningful on documents: clickjacking controls protect pages that can be framed, while a JSON API response does not present an interactive page to frame.
2. Choose a baseline that fits the application
This is a starting point, not a universal policy. The CSP and feature restrictions are intentionally strict: add origins or browser capabilities only when the application needs them. The HSTS line assumes the host serves HTTPS correctly; omit includeSubDomains until every affected subdomain is HTTPS-ready.
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
| Header | What it controls | Decision to make |
|---|---|---|
Strict-Transport-Security |
Asks supported browsers to use HTTPS for future requests to the host. | Confirm HTTPS and certificate coverage first; consider the scope and duration carefully. |
X-Content-Type-Options: nosniff |
Prevents browsers from guessing a different MIME type than the server declared. | Send correct Content-Type values for every resource. |
Content-Security-Policy |
Restricts which resources and behaviors a document may use; directives can also control framing. | Build it from actual dependencies and roll it out gradually. |
Referrer-Policy |
Limits referrer information sent during navigation or resource requests. | Choose how much same-origin and cross-origin URL detail should be exposed. |
Permissions-Policy |
Restricts selected browser features in the page and embedded frames. | Disable features the application does not need; explicitly allow required ones. |
X-Frame-Options |
Legacy-compatible framing control. | Consider DENY for compatibility when CSP frame-ancestors is also used. |
HSTS needs an operational decision
HSTS is learned from an HTTPS response and affects subsequent browser requests. Do not set it on a host that still depends on HTTP. Begin with a limited max-age if you are still validating deployment, then increase it when you are confident in TLS and redirect behavior. includeSubDomains extends the policy to subdomains; it can strand users if even one covered host is unavailable over HTTPS. HSTS preload is a separate, long-lived operational commitment and requires a readiness review before adoption. See MDN’s Strict-Transport-Security reference.
Choose framing rules deliberately
If no other site should embed your page, use frame-ancestors 'none'. If only your own origin may embed it, use frame-ancestors 'self'. For selected partners, enumerate their exact origins. The directive does not inherit from default-src, and it must be sent as an HTTP header rather than relying on a meta element. X-Frame-Options: DENY can provide compatibility or defense in depth, but do not treat it as a replacement for a deliberate CSP framing policy. OWASP identifies frame-ancestors as the modern framing control for supporting browsers.
3. Roll out CSP without breaking the site
A strict policy can block legitimate assets, so inventory dependencies before enforcement. Start with report-only mode, exercise the site’s important flows, and review the violations. Classify each one: a legitimate payment script may need an explicit origin, while an unexpected script may be an unnecessary dependency worth removing. Avoid broad wildcards and 'unsafe-inline' as shortcuts; where inline scripts are required, consider a nonce- or hash-based design appropriate to the application.

Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
Report-only policies observe violations but do not block the resources. To receive browser reports through the Reporting API, configure a Reporting-Endpoints response header and a corresponding report-to directive; reporting requires an endpoint and should be treated as attacker-controlled input. Sanitize and rate-limit report handling. Reporting support and details vary, so also exercise the application in browser developer tools. Once legitimate dependencies are covered, change the policy to the enforcing Content-Security-Policy header, then keep monitoring after release. MDN recommends report-only as a way to test a policy before enforcement. See the MDN reference.
Understand the CSP directives in the example
default-src 'self'is the fallback for resource types without a more specific directive.object-src 'none'blocks plugin-style embedded objects.base-uri 'self'limits which origins can set the document’s base URL.frame-ancestors 'none'prevents other sites from embedding the document.
This is not a drop-in policy for every application. A site using a separate asset host, inline bootstrapping code, workers, web sockets, or third-party integrations will need additional specific directives such as script-src, style-src, img-src, font-src, connect-src, worker-src, or frame-src. Write down why each source is needed. A policy can contain multiple directives and policies; adding a second policy does not relax a restrictive first one.
4. Configure headers at the server or application edge
Here is an Nginx example for a site that has completed HTTPS rollout, does not need framing, and has validated the sample CSP. Replace the CSP with the policy discovered for your app. Put the directives in the HTTPS server block. In Nginx, always makes add_header apply to error responses as well; test redirects and responses generated by upstreams because configuration inheritance and proxy behavior can affect the final result.
server {
listen 443 ssl;
server_name app.example.com;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
add_header X-Frame-Options "DENY" always;
location / {
proxy_pass http://application;
}
}
For the observation phase, replace the enforcing CSP line with this report-only header; do not send an enforcing policy that blocks resources while you are still auditing:
add_header Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
If using application middleware or a CDN, set the equivalent response headers in that system and make sure the setting runs for the response paths in scope. Do not blindly copy Nginx syntax into another server. Prevent the app and edge from emitting conflicting values. Never emit an empty header and assume it protects the response: a browser may ignore it.
5. Verify the actual responses
Inspect headers from the public URL, not only the application’s internal response. curl -I requests headers with a HEAD request, which some applications handle differently from GET. Use -D - with a GET when you need to inspect the headers from the normal response. The following examples are runnable after replacing the sample host.
cURL
curl -sS -D - -o /dev/null https://app.example.com/
curl -sS -D - -o /dev/null https://app.example.com/api/status
curl -sS -D - -o /dev/null https://app.example.com/missing-page
curl -sS -L -D - -o /dev/null http://app.example.com/
With -L, cURL prints headers for each hop. Review the HTTPS destination response as well as the initial redirect. Test representative static assets, authenticated routes, and errors too. Do not paste secrets or session cookies into shared logs when checking authenticated paths.
Python
import requests
urls = [
"https://app.example.com/",
"https://app.example.com/api/status",
"https://app.example.com/missing-page",
]
for url in urls:
response = requests.get(url, timeout=20, allow_redirects=True)
print(f"{url} -> {response.status_code}")
for name in (
"Strict-Transport-Security",
"X-Content-Type-Options",
"Referrer-Policy",
"Permissions-Policy",
"Content-Security-Policy",
"Content-Security-Policy-Report-Only",
):
print(f" {name}: {response.headers.get(name, '')}")
Node.js
const urls = [
'https://app.example.com/',
'https://app.example.com/api/status',
'https://app.example.com/missing-page',
];
for (const url of urls) {
const response = await fetch(url, { redirect: 'follow' });
console.log(url, '->', response.status);
for (const name of [
'strict-transport-security',
'x-content-type-options',
'referrer-policy',
'permissions-policy',
'content-security-policy',
'content-security-policy-report-only',
]) {
console.log(` ${name}: ${response.headers.get(name) ?? ''}`);
}
}
These scripts check presence, not correctness. Compare values to the intended policy, inspect browser console reports, and verify behavior in a browser. Automated scanners can help find missing or weak values, but a score does not establish that a CSP permits the right dependencies or that the application is secure.
6. Use a validation checklist before enforcement
- Fetch a successful HTML page, redirect, error, API response, static file, and authenticated page where applicable.
- Confirm every intended header is present, non-empty, and has the expected value at the public endpoint.
- Check that each response has the right
Content-Type; resolve MIME mismatches before relying onnosniff. - Review CSP report-only violations while exercising login, account management, checkout, uploads, embedded content, and other important flows.
- Attempt to frame a protected page from an unauthorized origin and confirm the browser blocks it.
- Check whether referrer data sent to less-trusted origins could expose sensitive paths or query strings.
- Try browser features the Permissions Policy disables; verify required features still work in approved top-level pages or frames.
- Recheck certificate, HTTPS redirect, HSTS max-age, and subdomain readiness before widening HSTS scope.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. It cannot inspect response headers or replace the checks above; a screenshot can help you review whether a changed policy visibly broke page rendering. If you need a screenshot during that visual check, the call is one request. See the ScreenshotNeo docs.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Learn more at ScreenshotNeo, then sign up for 1,000 free screenshots a month with no card.
7. Troubleshoot common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Header is missing on an error or redirect. | The middleware or server directive only covers selected successful responses, or a proxy generated the response. | Configure the responsible layer for those status paths, then re-fetch the public URL and inspect every redirect hop. |
| Header appears twice or the browser still blocks content. | Origin and edge both set CSP. Multiple CSP policies are enforced together; an additional policy cannot loosen an earlier one. | Choose one policy owner, remove duplicates or reconcile them, and inspect the complete raw response. |
| Page scripts, styles, fonts, or API calls stop working. | An enforcing CSP omitted a real dependency or blocks inline code. | Return to report-only during investigation, classify violations, add narrowly scoped sources or adopt nonce/hash patterns, then retest all flows. |
| HSTS does not seem to work. | The header was served only over HTTP, the browser has not yet learned it, or the inspected host differs from the policy’s scope. | Serve the header over HTTPS, verify the host and redirect path, and allow a browser request to learn the policy. Do not expect HSTS to repair invalid TLS. |
Files break after enabling nosniff. |
The server declares an incorrect or missing MIME type. | Fix each resource’s Content-Type at its serving layer; keep nosniff and verify the correct type. |
| Report-only violations appear, but no reports reach the server. | The policy has no reporting endpoint, the report-to name does not match, or browser/reporting support differs. |
Configure Reporting-Endpoints and the matching CSP directive, test endpoint handling, and use the browser console as a supplementary signal. |
| Security scan reports weak or absent headers despite configuration. | The scanner reached a different hostname, response variant, CDN path, or status response; an empty or malformed value can also be ignored. | Reproduce the exact URL and method, inspect raw headers for that response, then correct policy ownership or value. |
| Legitimate partner embedding is blocked. | frame-ancestors 'none' or X-Frame-Options: DENY disallows all framing. |
Document the product requirement and allow only intended origins with frame-ancestors; align compatibility headers and verify in a browser. |
8. Performance, reliability, and cost considerations
Headers add a small amount of response metadata, but the larger engineering cost is policy maintenance and breakage risk, especially with CSP. Avoid repeating large policies on responses that do not need them where your architecture permits, but do not omit protection from relevant document responses by accident. Keep policy configuration version-controlled and review changes alongside application dependencies.
Reliability comes from setting headers at a consistent layer, covering failure paths, testing through the same CDN and proxy that serve users, and monitoring CSP reports after rollout. Reports can include attacker-controlled URLs or data; validate, limit, and protect that telemetry. A policy update can disrupt scripts or integrations, so stage it, monitor real user flows, and have a rollback path. Header controls do not replace secure implementation practices or TLS operations.
There is no per-request browser cost inherent in adding these response headers. Practical costs are developer time, monitoring and report storage, and the possible impact of a misconfigured enforcement policy. Budget those operational costs against the application’s deployment model rather than treating a scanner result as a substitute for review.
9. Common mistakes to avoid
- Copying a CSP from another site without inventorying this app’s dependencies.
- Using
'unsafe-inline'or broad wildcards merely to silence violations. - Adding HSTS subdomain coverage before every covered hostname supports HTTPS.
- Assuming
X-Frame-Optionsreplaces CSPframe-ancestorsfor modern framing control. - Sending an empty or misspelled header and counting it as protection.
- Enabling legacy
X-XSS-Protectionas a substitute for CSP; OWASP warns it may introduce vulnerabilities and recommends CSP instead. - Assuming headers replace output encoding, sanitization, authentication, authorization, or dependency hygiene.
FAQ
Do security headers stop XSS by themselves?
No. CSP can restrict script and resource behavior, which can reduce the impact of some injection flaws, but it does not replace safe output handling, sanitization, or secure templating.
Should every application use exactly the same CSP?
No. A policy must reflect the application’s actual resource sources, embedding needs, and inline-code strategy. Start with a restrictive candidate and refine from observed violations.
Can I configure these headers in HTML meta tags?
Some CSP behavior can be expressed in a meta element, but important controls such as frame-ancestors cannot be relied on there. Configure security policies as response headers at the serving layer.
Does a successful header scan prove the app is secure?
No. It can find response configuration issues, but it does not establish correct application authorization, input handling, or safe dependency behavior.
For the reference semantics, see MDN’s pages for HSTS, nosniff, Referrer-Policy, and Permissions-Policy.


