Current situation: PHP2Shell. Two critical PHP vulnerabilities (CVSS 9.2) allow malicious code without login. Patches since 21 October 2026.What to do now →

Security headers for websites: setting them correctly

Security headers instruct the browser to protect your visitors better. How to set the most important headers in Nginx and Apache, introduce a Content Security Policy step by step and check the result.

Last updated 6 min readGuides

In short: Security headers are instructions from the web server to the browser that protect visitors from attacks such as cross-site scripting, clickjacking and unencrypted connections. The most important are HSTS, Content Security Policy, X-Content-Type-Options, protection against foreign frames, Referrer-Policy and Permissions-Policy. They are sensible hardening but do not replace updates on the server.

What security headers achieve and what they do not

With every page request, the web server sends headers along with the content. Some of them control security functions of the browser. This can be used to specify, for example, that a page is loaded only over HTTPS or that the browser executes no scripts from foreign domains.

The classification is important for operators: headers protect mainly the visitors, for example against session theft or manipulated forms. They do not close vulnerabilities on the server. A weakness in PHP, in the CMS or in an extension remains, no matter how well the headers are set. In combination with clean code, however, they considerably limit the consequences of many mistakes.

The most important security headers at a glance

Header Purpose Recommendation as a starting point
Strict-Transport-Security (HSTS) forces HTTPS for future visits max-age=31536000; includeSubDomains
Content-Security-Policy (CSP) defines permitted sources for scripts, images, frames individual, first as report-only
X-Content-Type-Options stops the browser from guessing file types nosniff
X-Frame-Options or CSP frame-ancestors prevents embedding in foreign pages (clickjacking) SAMEORIGIN or frame-ancestors 'self'
Referrer-Policy limits which address is passed on to other sites strict-origin-when-cross-origin
Permissions-Policy switches off browser features such as camera or location allow only needed features

You should set HSTS only once all subdomains are reliably reachable over HTTPS. The browser remembers the instruction for the specified time; a mistake then cannot be withdrawn immediately. If necessary, start with a short duration and increase it after successful tests. The additional option preload enters the domain permanently into browser vendors' lists and should be set only after careful review.

Headers are also not the only place where the browser offers protection. Cookies should carry the attributes Secure, HttpOnly and SameSite so that sessions cannot be intercepted over unencrypted connections or by scripts. How to set this up in PHP is described in the guide on sessions.

Setting security headers in Nginx

In Nginx, the headers belong in the server block of the respective website. The addition always ensures that they are also sent on error pages.

server {
    # ...
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'" always;

    server_tokens off;
}

A peculiarity of Nginx: if a location block sets its own add_header directives, the headers from the server block are not inherited there. Therefore also check subpages and static files. Further hardening for this combination is described in Securing Nginx with PHP-FPM.

Setting security headers in Apache

On Apache you need the module mod_headers. The directives belong in the VirtualHost or, if you have no access to it, in the .htaccess.

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
    Header always set Content-Security-Policy-Report-Only "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"
</IfModule>

ServerTokens Prod
ServerSignature Off

ServerTokens and ServerSignature belong in the main configuration. They prevent the server from stating its exact version in responses and error pages. In php.ini you achieve the same for PHP with expose_php = Off.

Introducing the Content Security Policy step by step

The Content Security Policy is the most effective but also the most demanding header. It defines from which sources scripts, stylesheets, images, fonts and frames may be loaded. If a planted script is loaded from a foreign domain, the browser blocks it.

Modern websites often embed analytics tools, payment providers, map services or videos. A policy that is too strict then disables functions. So proceed in stages:

  • Start with Content-Security-Policy-Report-Only first. The browser reports violations in the developer console or to a collection address but blocks nothing.
  • Record all needed sources and approve them specifically instead of using blanket wildcards.
  • Gradually move inline scripts into files or give them nonces, so that 'unsafe-inline' can be dispensed with.
  • Switch to Content-Security-Policy only after a test phase without unexpected reports.
Tip

The CSP complements clean escaping in the code but does not replace it. How to secure output in PHP properly is in the guide Preventing cross-site scripting in PHP.

Checking headers and avoiding typical mistakes

After every change, you should check that the headers actually arrive. This works without additional tools using curl:

# Test the configuration and reload
nginx -t && systemctl reload nginx

# Show the headers of the home page and of a static file
curl -sI https://www.example.de/ | grep -iE 'strict|content-security|x-content|x-frame|referrer|permissions|server'
curl -sI https://www.example.de/favicon.ico | grep -iE 'strict|x-content'

Typical mistakes are headers set twice when the web server and the application send the same headers, an upstream CDN that removes or overwrites headers, and headers that appear only on the home page but not on redirects or error pages. Also check after updates of the CMS or of security extensions whether the headers have changed.

So define one place where the headers are maintained, ideally the web server or the upstream service. Many CMS extensions also offer settings for security headers. That is convenient but easily leads to duplicate or contradictory entries if the server also sets headers. For individual tests, the browser's developer tools are also suitable: in the network tab you see the response headers for each request, and the console shows violations of the Content Security Policy.

How does PHP SOS help?

  • We check the current headers of all your websites, including behind CDN and proxy, and point out gaps and contradictions.
  • We set the headers centrally in the web server as part of server hardening and test the home page, subpages and error pages.
  • We introduce the Content Security Policy in report-only mode, evaluate the reports and switch it to enforcement after agreement.
  • Where code changes are not immediately possible, we complement the protection with WAF rules and virtual patching.

Frequently asked questions

What are security headers?

Security headers are additional information in the web server's response. They tell the browser, for example, to load the page only over HTTPS, not to execute foreign scripts, or not to display the page inside foreign frames. They protect mainly the visitors.

Do security headers protect against PHP2Shell?

No. PHP2Shell is a vulnerability in PHP on the server, while headers work in the visitor's browser. The update to a fixed PHP version and the restart of all workers help against PHP2Shell. Headers are supplementary hardening against other types of attack.

Can a Content Security Policy break my website?

Yes, a policy that is too strict blocks needed scripts, fonts or embedded content. That is why it is introduced first in report-only mode, the reports are evaluated, and only then is it switched to enforcement.

Is X-XSS-Protection still useful?

No. Current browsers no longer evaluate this header, and the recommendation is to omit it or set it to 0. Protection against cross-site scripting comes from clean escaping in the code and a Content Security Policy.

Sources

Immediate contact

Every hour counts. Write to us now.

Briefly describe your system, PHP version and observation. We will get back to you, clarify the situation and work remotely until the incident is closed and documented.

Immediate help