Security Headers for SaaS Apps — What Changes When Users Log In
Updated April 2026
Reading this? Verify your fix live. Scan your SaaS headers → HeadersFixer
A static marketing site needs three security headers. A SaaS app needs different headers on each type of page — public marketing, authenticated app, and API endpoints all have different security requirements.
Header strategy by route type
| Header | Marketing pages | App (authenticated) | API endpoints |
|---|---|---|---|
| HSTS | ✅ max-age=31536000 | ✅ same | ✅ same |
| X-Frame-Options | SAMEORIGIN | DENY (no embedding) | N/A |
| CSP | Permissive (analytics, chat) | Stricter (nonces) | default-src 'none' |
| Cache-Control | public, max-age=3600 | private, no-store | private, no-store |
| Referrer-Policy | strict-origin-when-cross-origin | same-origin | no-referrer |
Never cache authenticated responses
# Any response containing user-specific data Cache-Control: private, no-store # private = CDN does not cache (browser only) # no-store = browser does not write to disk cache # Together = the response exists only in memory during the request
API endpoint headers
# REST API — minimal and correct Content-Type: application/json Cache-Control: private, no-store X-Content-Type-Options: nosniff # Add CORS if needed (see CORS guides) Access-Control-Allow-Origin: https://yourapp.com Access-Control-Allow-Credentials: true # No CSP needed on JSON API responses — no HTML to execute scripts
CSP for authenticated app routes
# App pages load fewer third-party scripts than marketing pages
# Marketing: GA, Intercom, Hotjar, LinkedIn Pixel...
# App: usually just your own JS, maybe Stripe for billing
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{nonce}' https://js.stripe.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.stripe.com https://api.yourapp.com; frame-src https://js.stripe.com; frame-ancestors 'none'; object-src 'none';
Referrer-Policy tightening for app routes
# Marketing pages — can share referrer with third-party analytics Referrer-Policy: strict-origin-when-cross-origin # App pages — users are logged in, do not leak app URLs to third parties Referrer-Policy: same-origin # API endpoints — do not send referrer at all Referrer-Policy: no-referrer
Multi-tenant SaaS — subdomain isolation
# Each customer subdomain: customer1.yourapp.com # Risk: XSS on one subdomain should not affect others # Prevent cookie sharing between subdomains: # Set cookies with domain= set to the exact subdomain, not the parent domain Set-Cookie: session=abc; Domain=customer1.yourapp.com; HttpOnly; Secure; SameSite=Lax # NOT: Domain=.yourapp.com (which shares with all subdomains)
Nginx split config by location
server { # Marketing — public caching allowed location / { add_header Cache-Control "public, max-age=3600" always; add_header X-Frame-Options "SAMEORIGIN" always; } # Authenticated app — no caching location /app/ { add_header Cache-Control "private, no-store" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "same-origin" always; } # API — strict no-cache, no iframing context location /api/ { add_header Cache-Control "private, no-store" always; add_header X-Content-Type-Options "nosniff" always; }
} Scan your SaaS headers → HeadersFixer