HTTP Security Headers Every Web Application Needs: Node.js, Express, and Nginx
Set the essential HTTP security headers in Node.js, Express, and Nginx — Strict-Transport-Security, X-Frame-Options, CORP, COOP, and more with practical config examples.
Security headers are one of the fastest, highest-leverage improvements you can make to a web application's security posture. They require no changes to application logic, take minutes to configure, and address a meaningful set of browser-side attacks. Yet they are routinely missing from production deployments.
This guide covers the headers that matter, what each one actually does, and how to set them in Express and Nginx.
Why HTTP Security Headers Matter
Modern browsers enforce security policies that applications can opt into via HTTP response headers. Without these headers, browsers fall back to permissive defaults — defaults designed for compatibility with the web of twenty years ago, not for the security requirements of a modern application.
A quick scan with securityheaders.com or Mozilla Observatory will tell you where your current application stands. Most production applications have significant gaps.
The Essential HTTP Security Headers
Strict-Transport-Security (HSTS)
HSTS tells browsers to only communicate with your site over HTTPS, even if the user types http:// in the address bar. Without it, a user on an untrusted network can be the subject of an SSL stripping attack — the attacker downgrades the connection to HTTP before it reaches your server.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
max-age: how long (in seconds) the browser remembers this policy. Two years (63072000) is standard.includeSubDomains: applies the policy to all subdomains. Only include this if all your subdomains serve HTTPS correctly.preload: requests inclusion in browser HSTS preload lists, so the browser enforces HTTPS before ever making a first connection. Submit to hstspreload.org separately.
In Express (via Helmet):
const helmet = require('helmet');
app.use(helmet.hsts({
maxAge: 63072000,
includeSubDomains: true,
preload: true
}));In Nginx:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;X-Frame-Options
Prevents your page from being embedded in an iframe by other origins. Clickjacking attacks work by overlaying a transparent iframe of your application over a malicious page and tricking users into clicking interface elements they cannot see.
X-Frame-Options: DENY
Use DENY unless your application has a legitimate need to be framed by specific trusted origins, in which case SAMEORIGIN allows framing from your own origin only. For fine-grained control, use the CSP frame-ancestors directive instead — it supersedes X-Frame-Options in browsers that support CSP.
X-Content-Type-Options
Prevents browsers from MIME-sniffing a response away from the declared content type. Without it, a browser might interpret a text file as executable JavaScript if the content looks like a script.
X-Content-Type-Options: nosniff
This is a one-liner with no configuration needed. It should be on every application.
Referrer-Policy
Controls how much information is included in the Referer header when users navigate away from your site. The default behavior sends the full URL to external sites, which can leak sensitive path information (user IDs in URLs, tokens in query parameters, internal paths).
Referrer-Policy: strict-origin-when-cross-origin
This sends the full URL for same-origin requests and only the origin (no path) for cross-origin requests. For applications that handle particularly sensitive URLs, no-referrer sends nothing at all.
Permissions-Policy (formerly Feature-Policy)
Restricts which browser features and APIs your application and any embedded third-party content can use.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
This disables the camera, microphone, geolocation, and payment APIs. Adjust to permit the features your application actually uses. Restricting these prevents a compromised third-party script from silently activating device capabilities.
Cross-Origin-Opener-Policy (COOP)
Prevents other origins from getting a handle to your window object, which is a prerequisite for certain cross-origin attacks including Spectre side-channel attacks.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy (CORP)
Prevents other origins from reading resources loaded by your server, protecting against cross-origin information leaks.
Cross-Origin-Resource-Policy: same-origin
Use same-site if your resources need to be loadable by other applications on the same site, or cross-origin only for resources explicitly intended to be public (fonts, public images served from a CDN).
Cross-Origin-Embedder-Policy (COEP)
Required to enable certain powerful browser features (like SharedArrayBuffer) and works in conjunction with COOP to provide cross-origin isolation.
Cross-Origin-Embedder-Policy: require-corp
Note that this header breaks embedding of third-party resources that do not set CORP themselves (such as many ad networks and analytics scripts). Evaluate carefully before enabling.
Configuring Security Headers in Express
The Helmet middleware for Express sets most of these headers with sensible defaults in a single line:
const helmet = require('helmet');
app.use(helmet());Helmet's defaults are a reasonable starting point. Review and extend them based on your application's specific requirements — particularly CSP and Permissions-Policy, which require application-specific configuration.
Configuring Security Headers in Nginx
server {
# Existing server config...
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
}The always flag ensures headers are sent on error responses as well as successful ones.
Verifying Your Headers
After deployment, verify your headers using:
- Mozilla Observatory (observatory.mozilla.org): grades your headers and explains each gap
- securityheaders.com: quick header check with letter grade
- curl:
curl -I https://yourapp.comshows response headers directly
Re-run header checks after significant deployments, particularly those that add or change third-party integrations, since new features may require header policy updates.
What Headers Do Not Cover
Security headers address browser-enforced policies. They do not address server-side vulnerabilities, injection flaws, authentication weaknesses, or misconfigured access control. They are one layer of a defense-in-depth approach, not a substitute for secure application code.
Building a web application and want a team that ships with a complete security baseline? Start a build.