Security

Add security headers in Nginx and Apache: CSP, HSTS, X-Frame-Options and more

Which HTTP security headers matter today, what each one blocks, and copy-ready Nginx and Apache configs, including a safe way to roll out Content-Security-Policy.

IntermediateLinux VPS3 min read

Security headers are HTTP response headers that tell browsers to enable built-in protections: force HTTPS, block clickjacking, stop MIME sniffing and limit where scripts can load from. They cost nothing in performance and are set once in the web server. This guide covers the headers worth sending and how to add them in Nginx and Apache.

Before you start

The security headers that matter

HeaderProtects againstRecommended value
Strict-Transport-SecurityDowngrade to HTTP, cookie theftmax-age=31536000; includeSubDomains
Content-Security-PolicyXSS, injected scripts, clickjackingSite-specific (see below)
X-Content-Type-OptionsMIME sniffingnosniff
X-Frame-OptionsClickjacking (legacy browsers)SAMEORIGIN
Referrer-PolicyLeaking full URLs to other sitesstrict-origin-when-cross-origin
Permissions-PolicyUnwanted camera, mic, location usecamera=(), microphone=(), geolocation=()

Skip X-XSS-Protection: modern browsers removed the filter it controlled. Set it to 0 or leave it out.

Add security headers in Nginx

Put them in the HTTPS server block, or in a snippet you include:

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;

Warning: Nginx add_header directives inherit only if the inner block has none. A location with its own add_header (for example for caching) drops all server-level headers. Include the snippet in that location too.

The always parameter sends the headers on error responses as well.

Add security headers in Apache

Enable mod_headers (sudo a2enmod headers), then add to the <VirtualHost *:443> block:

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=()"

Roll out Content-Security-Policy safely

CSP is the most powerful header and the easiest to break a site with, because it blocks any script, style, font or frame not on its allow list.

  1. Start in report-only mode so nothing is blocked:
    add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data: https:; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'" always;
  2. Browse the whole site with the browser console open. Each violation names the blocked source.
  3. Add legitimate sources (analytics, fonts, payment widgets) to the right directive.
  4. When the console is clean, rename the header to Content-Security-Policy.

frame-ancestors 'self' is the modern replacement for X-Frame-Options; sending both is fine.

Check the result

curl -sI https://example.com/ | grep -iE 'strict|content-security|x-frame|x-content|referrer|permissions'

Test a 404 URL too, to confirm the headers appear on errors.

Troubleshooting

  • Headers missing on some pages: an Nginx location with its own add_header, or PHP/the app sending its own headers. Check per URL with curl.
  • Headers sent twice: both the web server and the app (or a plugin) set them. Keep one source.
  • Site broken after CSP: switch back to Report-Only and add the missing sources.

FAQ: security headers

Should I add HSTS preload?

Only when every subdomain serves HTTPS permanently. Preload is hard to undo. See the HSTS header guide.

Do security headers improve SEO?

Not directly, but HTTPS enforcement and a safe site protect rankings and visitors.

Where do headers go behind a reverse proxy?

Set them on the server that talks to browsers, usually the proxy or TLS terminator.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.