What security headers do, and what they do not
A security header is a response header that tells the browser to be stricter with your page than it would be by default: use only HTTPS, stop guessing file types, stay out of other sites' frames, share less in the Referer, keep the camera off, and run only the scripts you approved.
They are defence in depth. None of them fixes a vulnerable application: SQL injection or a broken login is just as broken with a perfect header set. What they do is limit the damage of the bugs a browser can contain, such as cross-site scripting, clickjacking, protocol downgrade and data leaking through referrers, for every visitor, at the cost of a few hundred bytes.
They also work only in browsers. An API client, a crawler or an attacker's script ignores all of them, which is why a WAF and the application code itself remain the first line.
Strict-Transport-Security (HSTS)
Strict-Transport-Security tells the browser to use HTTPS for your hostname and to remember that for max-age seconds, so even a typed http:// address never leaves the device in plain text. It is the header with the biggest effect for the least work, provided every URL on the site already works over HTTPS.
A safe start is max-age=31536000: one year, this hostname only. includeSubDomains and preload reach further and are hard to undo. The HSTS guide explains what each of them commits you to, how to verify the header and how to roll it back.
On CDN.com.tr, HSTS is an account preset in Delivery Rules, so there is no header line to write for it. It sends exactly max-age=31536000 on HTTPS responses and nothing on plain HTTP.
X-Content-Type-Options: nosniff
Browsers used to guess a file's type from its content when the Content-Type header looked wrong. That guessing, called MIME sniffing, let an uploaded file that looked like a script run as one. X-Content-Type-Options: nosniff switches it off: a stylesheet must arrive as text/css and a script with a JavaScript type, or the browser refuses it.
nosniff is the only value, and the header is safe on every response, HTML or not. Check one thing first: that your server labels files correctly. A script served as text/plain stops loading the moment the header goes on, which is exactly the behaviour you asked for.
X-Frame-Options and frame-ancestors: who may frame your pages
Clickjacking loads your page inside an invisible frame on another site and lines up one of its buttons with one of yours, so a visitor clicks "Delete account" while believing they clicked "Play". The defence is telling the browser who may put your page in a frame.
X-Frame-Options has two useful values: DENY for nobody and SAMEORIGIN for pages on your own origin. The old ALLOW-FROM value is ignored by current browsers. To allow a list of sites, use the CSP directive frame-ancestors, for example frame-ancestors 'self' https://partner.example.
When a response carries both, browsers that understand frame-ancestors follow it and ignore X-Frame-Options. Sending both is common and harmless, since the older header covers older browsers, as long as the two say the same thing. One limit: frame-ancestors works only as an HTTP header, never in a <meta> tag.
Referrer-Policy: what leaves with each click
When a visitor follows a link, or your page loads an image, script or font from another site, the browser can send your page's address in the Referer header. Full URLs leak more than people expect: search terms, order numbers, the token in a password-reset link.
strict-origin-when-cross-origin is the sensible default: the full URL within your own site, only the origin (https://example.com/) to other sites, and nothing when an HTTPS page links to plain HTTP. Current browsers already apply this, or something stricter, when a page says nothing; setting it explicitly keeps the behaviour fixed whatever a browser's default becomes. For an application with sensitive URLs, no-referrer sends nothing at all, at the cost of referral data in other sites' analytics.
Permissions-Policy: switch off what you do not use
Permissions-Policy decides which powerful browser features your page and the frames inside it may use: camera, microphone, geolocation, payment, USB and more. An empty list () turns a feature off for everyone, (self) allows only your own origin, and you can name the origins of embeds that genuinely need one.
A company site or a shop rarely needs any of these, so camera=(), microphone=(), geolocation=() is a reasonable start; add more as you review what you embed. The gain is containment: a compromised third-party script or an ad frame cannot ask the visitor for their camera. Chromium-based browsers enforce the header, so treat it as hardening on top of the other controls, not a replacement for them.
Content-Security-Policy: powerful, and worth a plan
A Content Security Policy tells the browser where scripts, styles, images, fonts, connections and frames may come from, and whether inline code may run. Done well, it turns most cross-site scripting bugs from "an attacker runs code in your users' sessions" into "the browser blocked a script and reported it".
It is also the header that breaks pages. Inline <script> blocks, onclick attributes, a tag manager that injects more scripts, an analytics endpoint nobody remembered: each one must be allowed or rewritten. So roll it out in two steps. First send the policy as Content-Security-Policy-Report-Only: nothing is blocked, and every violation appears in the console and, with a report-to or report-uri endpoint, in your reports. Fix or allow what the reports show, then send the same policy as Content-Security-Policy.
The policy below is strict but readable. For inline scripts you cannot remove, generate a random nonce for every response and put it in both the policy and the <script> tag. With 'strict-dynamic', scripts loaded by a trusted script are trusted too, which makes tag managers workable without listing every domain they touch. Avoid 'unsafe-inline' for scripts: it switches off most of the protection the policy exists for.
A strict starting policy, in report-only mode
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests
Safe defaults to copy
Most sites can start with this set and only adjust the CSP sources and the permissions list. Order does not matter; what matters is that each header appears once.
X-XSS-Protection is missing on purpose. It controlled a filter that current browsers have removed, and in old browsers the filter itself could be abused; send nothing or X-XSS-Protection: 0. Expect-CT is obsolete for the same kind of reason and can go too.
Response headers for an HTML page
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
Setting them on CDN.com.tr
HSTS has its own preset, described above. The other single-value headers go in a delivery rule: in Delivery Rules, edit the rule that serves your pages, usually /, and add one per line to Custom Headers as Header-Name value (on the tabbed rules page: Headers → Add response headers). A value with spaces goes in double quotes.
A full Content-Security-Policy cannot go there. Its directives are separated by semicolons, and the panel and the edge refuse ; in a header line, because a semicolon would end the edge's own configuration directive. A single directive has no semicolon, so frame-ancestors alone works at the edge. The complete policy belongs at your origin, in the web server or the application, where it can also carry a per-request nonce. An HTML <meta http-equiv="Content-Security-Policy"> tag works too, except for frame-ancestors, report-uri and sandbox, which browsers ignore in a meta tag.
Headers added by a rule apply to successful and redirect responses (2xx and 3xx), cache hits included, as soon as the rule is published; a header that must also appear on error pages belongs at the origin. Headers your origin sends are stored with each cached copy, so after you change them, purge the CDN cache for the affected pages. If your origin already sends one of these headers, select it under Hide Headers in the same rule, or visitors receive two copies.
Custom Headers on the rule that serves your pages
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy "camera=(), microphone=(), geolocation=()"
Content-Security-Policy "frame-ancestors 'self'"
How to test
Start with curl, which shows exactly what the server sends. Check a page, a static file and a page that does not exist: headers set in one place often miss the other two.
Then open the browser's developer tools. The Network tab shows the headers as the browser received them, and the Console reports every CSP violation and every refused frame, with the directive that caused it. Online scanners such as Mozilla's HTTP Observatory grade the whole set and explain each finding, which makes a useful second opinion.
Finally, test behaviour, not just presence: embed the page in an iframe on another origin and confirm the browser refuses it, and watch the Console for a few days with the report-only policy before you enforce it.
curl -sI https://www.example.com/ | grep -i -E \
'strict-transport|x-content-type|x-frame|referrer-policy|permissions-policy|content-security'
# a static file and a missing page, too
curl -sI https://www.example.com/assets/app.css | grep -i x-content-type
curl -sI https://www.example.com/no-such-page | grep -i -E 'x-frame|content-security'
Security headers FAQ
Which security headers should every website send?
HSTS, once the whole site works over HTTPS; X-Content-Type-Options: nosniff; X-Frame-Options: SAMEORIGIN or a CSP frame-ancestors; Referrer-Policy: strict-origin-when-cross-origin; and a Permissions-Policy that turns off features you do not use. Add a Content-Security-Policy after a report-only trial.
Is X-Frame-Options deprecated?
Superseded rather than removed. CSP frame-ancestors does the same job with more control, and browsers that support it ignore X-Frame-Options when both are present. Sending SAMEORIGIN or DENY alongside it still protects older browsers; only ALLOW-FROM is dead.
Should I still send X-XSS-Protection?
No. The filter it controlled has been removed from current browsers, and in old ones it could be abused against the page. Leave it out or send 0, and rely on a Content-Security-Policy instead.
Can I set Content-Security-Policy through CDN.com.tr?
A single directive, yes: for example frame-ancestors 'self' in a delivery rule's Custom Headers. A full policy needs semicolons between its directives, which edge rules do not accept, so set it in your web server or application, or in a meta tag for everything except frame-ancestors, report-uri and sandbox.
Do security headers affect SEO or page speed?
Not measurably. They add a few hundred bytes, and HTTP/2 and HTTP/3 compress repeated headers. They are not a documented ranking factor. The one SEO risk is a CSP that blocks your own scripts and breaks how the page renders, which the report-only step catches.
Do images, CSS and JavaScript files need these headers too?
nosniff and HSTS are useful on every response. The rest, CSP, frame protection, Referrer-Policy and Permissions-Policy, act on documents, so HTML pages are what matters. Sending them on everything does no harm.