What each header does
| Header | Protects against |
|---|---|
Content-Security-Policy | XSS, code injection |
Strict-Transport-Security | Protocol downgrade attacks |
X-Frame-Options | Clickjacking |
X-Content-Type-Options | MIME sniffing / XSS |
Referrer-Policy | Referrer info leakage |
Permissions-Policy | Unwanted browser API access |
X-Powered-By | Tech stack exposure (should be removed) |
Next.js
// next.config.js
const securityHeaders = [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
]
const nextConfig = {
poweredByHeader: false,
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }]
},
}
module.exports = nextConfig
Astro
For static builds (SSG), headers must be set at the server or CDN level — Astro cannot add HTTP headers to static files. Use the Nginx, Vercel, or Netlify config below.
For SSR (with a Node.js or Cloudflare adapter), add headers in middleware:
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware'
export const onRequest = defineMiddleware(async (context, next) => {
const response = await next()
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('X-Frame-Options', 'SAMEORIGIN')
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
response.headers.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
return response
})
Vue (Nuxt)
Option 1 — via routeRules in nuxt.config.ts (works with SSR and Nitro server):
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
routeRules: {
'/**': {
headers: {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'SAMEORIGIN',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
},
},
},
},
})
Option 2 — via server middleware (more flexible):
// server/middleware/security-headers.ts
export default defineEventHandler((event) => {
setResponseHeaders(event, {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'SAMEORIGIN',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
})
})
Option 3 — nuxt-security module (most complete, includes CSP auto-configuration):
npx nuxi module add security
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-security'],
security: {
headers: {
xFrameOptions: 'SAMEORIGIN',
xContentTypeOptions: 'nosniff',
referrerPolicy: 'strict-origin-when-cross-origin',
strictTransportSecurity: 'max-age=63072000; includeSubDomains',
permissionsPolicy: { camera: [], microphone: [], geolocation: [] },
},
},
})
For static builds (nuxt generate), use the Nginx, Vercel, or Netlify configs below.
Angular
Angular itself does not control HTTP response headers — they are always set at the server or CDN level.
Angular Universal (SSR) with Express:
// server.ts
import express from 'express'
const app = express()
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff')
res.setHeader('X-Frame-Options', 'SAMEORIGIN')
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin')
res.setHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
next()
})
For static Angular builds (without SSR): use the Nginx, Vercel, or Netlify configs below — headers are applied independently of the Angular application code.
Nginx
server {
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 Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
server_tokens off;
}
Apache / PHP / WordPress
Add to .htaccess in the site root:
<IfModule mod_headers.c>
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 Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header unset X-Powered-By
Header always unset X-Powered-By
</IfModule>
Make sure mod_headers is enabled:
a2enmod headers && systemctl restart apache2
WordPress plugin option: if you don't have access to .htaccess or httpd.conf, use the Headers & API Manager plugin to add headers from the admin panel.
Remove X-Powered-By in PHP:
// At the top of index.php or in functions.php (WordPress)
header_remove('X-Powered-By');
Vercel
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}
For Next.js on Vercel, prefer next.config.js — it takes precedence and is easier to maintain.
Netlify
# netlify.toml
[[headers]]
for = "/*"
[headers.values]
X-Content-Type-Options = "nosniff"
X-Frame-Options = "SAMEORIGIN"
Referrer-Policy = "strict-origin-when-cross-origin"
Strict-Transport-Security = "max-age=63072000; includeSubDomains"
Permissions-Policy = "camera=(), microphone=(), geolocation=()"
Tilda
Tilda does not allow setting custom HTTP response headers. Headers are controlled by Tilda's infrastructure.
The only workaround is hosting Tilda export (if available on your plan) behind your own Nginx or Cloudflare, where you can add headers. Without that, this cannot be fixed on Tilda's platform.
CSP — start with report-only mode
Content-Security-Policy is the most powerful header but the most likely to break your app. Start with Report-Only — violations are logged to the console, nothing is blocked:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
Fix all reported violations, then switch to enforcing:
Content-Security-Policy: default-src 'self'; script-src 'self'; ...
Verify
curl -I https://yoursite.com
Or use securityheaders.com for a full graded report. Then run vibeblame again.