Boost Your Web Security: Essential HTTP Header Configurations You’re Missing
This guide shows operations engineers how to dramatically improve web application protection by configuring often‑overlooked HTTP security headers—CSP, X‑Frame‑Options, HSTS, Referrer‑Policy, Permissions‑Policy, and more—through practical Nginx/Apache/Node.js examples, verification scripts, and automation tips.
Why HTTP Security Headers Matter
In a decade of operations work the author has seen countless breaches caused by missing HTTP security headers, such as a major e‑commerce site that suffered XSS attacks because it lacked a Content‑Security‑Policy (CSP). Proper header configuration acts as an invisible shield that prevents XSS, click‑jacking, data leakage, and man‑in‑the‑middle attacks at virtually no cost.
Core Headers and Practical Configurations
1. Content‑Security‑Policy (CSP)
CSP controls which resources a page may load and execute. A strict production‑ready example for Nginx:
# Strict mode – recommended for production
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.yourdomain.com;
media-src 'none';
object-src 'none';
frame-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
" always;
# Report‑only mode for testing
add_header Content-Security-Policy-Report-Only "
default-src 'self';
report-uri /csp-report-endpoint;
" always;Apache equivalent:
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; \
script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; \
style-src 'self' 'unsafe-inline'; \
img-src 'self' data: https:; \
font-src 'self' data:; \
connect-src 'self'; \
frame-ancestors 'none';"
</IfModule>Practical tips:
Start with Report‑Only mode to collect violation reports.
Gradually tighten the policy from permissive to strict.
Replace unsafe-inline with nonces or hashes for better security.
2. X‑Frame‑Options
Prevents click‑jacking by controlling whether a page can be embedded in an iframe.
# Deny all framing
add_header X-Frame-Options "DENY" always;
# Allow same‑origin framing only
add_header X-Frame-Options "SAMEORIGIN" always;Node.js (Express) example using helmet:
const helmet = require('helmet');
const express = require('express');
const app = express();
app.use(helmet.frameguard({ action: 'deny' }));
// Or set manually
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
next();
});
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'");
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
next();
});3. Strict‑Transport‑Security (HSTS)
Forces browsers to use HTTPS, mitigating protocol‑downgrade attacks.
# Nginx full HSTS configuration
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;Gradual rollout strategy:
# Phase 1 – 5 minutes
add_header Strict-Transport-Security "max-age=300" always;
# Phase 2 – 1 day
add_header Strict-Transport-Security "max-age=86400" always;
# Phase 3 – include sub‑domains (1 week)
add_header Strict-Transport-Security "max-age=604800; includeSubDomains" always;
# Phase 4 – production (1 year + preload)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;4. X‑Content‑Type‑Options
Stops browsers from MIME‑sniffing responses.
# Nginx
add_header X-Content-Type-Options "nosniff" always;
# Ensure correct Content‑Type for JS and CSS
location ~* \.(js)$ {
add_header Content-Type "application/javascript" always;
add_header X-Content-Type-Options "nosniff" always;
}
location ~* \.(css)$ {
add_header Content-Type "text/css" always;
add_header X-Content-Type-Options "nosniff" always;
}5. Referrer‑Policy
Controls how much referrer information is sent with requests.
# Recommended: send full URL for same‑origin, only origin for cross‑origin
add_header Referrer-Policy "strict-origin-when-cross-origin" always;6. Permissions‑Policy
Fine‑grained control over browser APIs and features (successor to Feature‑Policy).
add_header Permissions-Policy "
camera=(),
microphone=(),
geolocation=(self),
payment=(),
usb=(),
magnetometer=(),
gyroscope=(),
accelerometer=(self),
ambient-light-sensor=(),
autoplay=(self),
encrypted-media=(self),
picture-in-picture=(),
sync-xhr=(self),
document-domain=(),
publickey-credentials-get=(self)"
always;Real‑World Case Study
A startup’s web app was audited before launch and found to lack several headers. After applying the full Nginx template below, the security score jumped from “F” to “A+”.
server {
listen 443 ssl http2;
server_name secure.example.com;
# SSL configuration
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# CSP variables
set $csp_default "default-src 'self'";
set $csp_script "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net";
set $csp_style "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com";
set $csp_img "img-src 'self' data: https:";
set $csp_font "font-src 'self' https://fonts.gstatic.com";
set $csp_connect "connect-src 'self' wss://ws.example.com";
set $csp_frame "frame-ancestors 'none'";
add_header Content-Security-Policy "$csp_default; $csp_script; $csp_style; $csp_img; $csp_font; $csp_connect; $csp_frame" always;
add_header Permissions-Policy "geolocation=(self), camera=(), microphone=()" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}Monitoring & Verification
After deployment, verify headers with the following methods:
1. Curl script
#!/bin/bash
# Security header check script
URL="https://your-website.com"
echo "Checking $URL security headers..."
headers=$(curl -sI "$URL")
check_header() {
header_name=$1
if echo "$headers" | grep -qi "$header_name"; then
echo "✓ $header_name: Configured"
else
echo "✗ $header_name: Missing"
fi
}
for h in "Strict-Transport-Security" "X-Frame-Options" "X-Content-Type-Options" "Content-Security-Policy" "Referrer-Policy" "Permissions-Policy"; do
check_header "$h"
done2. Online scanners
SecurityHeaders.com – detailed scoring.
Mozilla Observatory – comprehensive web security analysis.
SSL Labs – HTTPS configuration testing.
3. Browser DevTools
Open the Network tab.
Refresh the page.
Select the main document request.
Inspect the Response Headers section.
Common Issues & Solutions
Issue 1: CSP blocks third‑party resources
Symptoms: console shows many CSP violation reports.
# Switch to Report‑Only to collect data
add_header Content-Security-Policy-Report-Only "
default-src 'self';
report-uri /csp-violations;
" always;
# Log violations
location /csp-violations {
access_log /var/log/nginx/csp-violations.log;
}Issue 2: HSTS breaks local development
Symptoms: local server forces HTTPS.
# Conditional HSTS based on hostname
map $host $hsts_header {
default "";
"~*\.production\.com$" "max-age=31536000; includeSubDomains";
}
add_header Strict-Transport-Security $hsts_header always;Issue 3: X‑Frame‑Options conflicts with legitimate iframes
# Prefer CSP frame‑ancestors, keep X‑Frame‑Options as fallback
add_header Content-Security-Policy "frame-ancestors 'self' https://trusted-domain.com" always;
add_header X-Frame-Options "SAMEORIGIN" always;Performance Optimisation
Headers add negligible overhead, but you can reduce processing by using Nginx maps.
# Map URIs to CSP policies to avoid repeated string literals
map $uri $csp_policy {
default "default-src 'self'";
"~*/admin" "default-src 'self'; script-src 'self' 'unsafe-eval'";
"~*/api" "default-src 'none'; frame-ancestors 'none'";
}
server {
add_header Content-Security-Policy $csp_policy always;
}Conditional header setting based on content type:
map $sent_http_content_type $x_frame_options {
"text/html" "SAMEORIGIN";
default "";
}
add_header X-Frame-Options $x_frame_options always;Automation
Docker‑based dynamic configuration
# Dockerfile (nginx:alpine base)
FROM nginx:alpine
RUN apk add --no-cache gettext
COPY nginx.conf.template /etc/nginx/templates/
COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"] #!/bin/sh
# docker-entrypoint.sh
envsubst '${CSP_POLICY} ${HSTS_MAX_AGE}' < /etc/nginx/templates/nginx.conf.template > /etc/nginx/nginx.conf
nginx -g "daemon off;"CI/CD security‑header check (GitLab example)
# .gitlab-ci.yml
security_headers_check:
stage: test
script:
- curl -sI https://$CI_ENVIRONMENT_URL > headers.txt
- |
required_headers=(
"Strict-Transport-Security"
"X-Frame-Options"
"X-Content-Type-Options"
"Content-Security-Policy"
)
for header in "${required_headers[@]}"; do
if ! grep -qi "$header" headers.txt; then
echo "Missing security header: $header"
exit 1
fi
done
- echo "All security headers are properly configured!"Future Directions
New headers are being standardized to further isolate origins and protect against side‑channel attacks.
Cross‑Origin‑Opener‑Policy (COOP)
add_header Cross-Origin-Opener-Policy "same-origin" always;Cross‑Origin‑Resource‑Policy (CORP)
add_header Cross-Origin-Resource-Policy "same-origin" always;Cross‑Origin‑Embedder‑Policy (COEP)
add_header Cross-Origin-Embedder-Policy "require-corp" always;Checklist
Strict-Transport-Security (mandatory for HTTPS sites)
X-Content-Type-Options: nosniff
X-Frame-Options or CSP frame‑ancestors
Content‑Security‑Policy (at least default‑src)
Referrer-Policy
Permissions-Policy
X‑XSS-Protection (optional, legacy)
Regularly audit, monitor, and update these headers to keep the security posture strong while balancing performance and functionality.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
