Security headers in one line: why Helmet matters
Most teams delay security hardening until a vulnerability report lands in their inbox. Helmet in NestJS: a practical guide for developers exists precisely to prevent that scramble. By wrapping your Express or Fastify adapter with a single middleware, you automatically set dozens of HTTP security headers that defend against common attacks—clickjacking, MIME sniffing, XSS, and more—without writing custom header logic.
In production e-commerce or SaaS environments, we've seen teams lose hours tuning headers after launch. A few lines added to main.ts during initial setup saves that pain and gives you a solid security baseline from day one.
Getting started: app.use(helmet()) in main.ts
Helmet is the Express ecosystem standard for HTTP header security. NestJS integrates it seamlessly because NestJS wraps Express by default. Installation takes one command:
npm install helmetThen import and apply it in your bootstrap function:
import { NestFactory } from '@nestjs/core';
import helmet from 'helmet';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(helmet());
await app.listen(3000);
}
bootstrap();That single line activates default protections:
- Strict-Transport-Security: Forces HTTPS for the next 30 days (if your origin is HTTPS).
- X-Content-Type-Options: Prevents browsers from sniffing MIME types, stopping certain polyglot attacks.
- X-Frame-Options: Blocks framing of your app in iframes unless you explicitly allow it.
- X-XSS-Protection: Legacy header; modern browsers ignore it, but older clients still benefit.
- Referrer-Policy: Limits what referer information leaks to third-party sites.
- Permissions-Policy: Controls which browser features (camera, microphone, geolocation) scripts may access.
- Content-Security-Policy (CSP): Restricts where resources (scripts, styles, images) may load from.
In a real project, we deployed this to a ride-hailing backend serving both web and mobile clients. Within minutes, security scanners showed dramatic improvements without a single user-facing code change.
Configure Content Security Policy for HTML-serving applications
If your NestJS app serves Server-Side Rendered (SSR) HTML or has inline scripts, the default CSP is too strict. Helmet's default CSP blocks all inline scripts and styles, which breaks many applications.
Here's how to customize it for a typical SaaS dashboard:
import { NestFactory } from '@nestjs/core';
import helmet from 'helmet';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://trusted-cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
fontSrc: ["'self'", "https://fonts.googleapis.com"],
frameSrc: ["'none'"],
},
},
}));
await app.listen(3000);
}
bootstrap();Breaking down this configuration:
defaultSrc: Fallback policy. Only load from the same origin.scriptSrc: Scripts from your origin or a specific trusted CDN. Remove'unsafe-inline'unless absolutely necessary.styleSrc:'unsafe-inline'is sometimes unavoidable for CSS-in-JS libraries; we prefer bundled CSS.imgSrc: Allow your origin, data URIs (for small inlined images), and HTTPS sources.connectSrc: Only fetch/XHR/WebSocket to your origin or API domain.frameSrc: Block embedding of iframes entirely, or list specific origins if you embed payment forms or widgets.
In a travel booking system we worked on, we initially set scriptSrc: ["'self'"] but forgot our analytics script. The browser silently blocked it, causing confusion until we checked the CSP violations report. Build a logging endpoint for CSP violations and monitor it in production:
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
},
reportUri: '/api/csp-report',
},
}));Then in your controller, log and act on violations to catch misconfigurations early.
Using @fastify/helmet for the Fastify adapter
If you're using NestJS with Fastify instead of Express, you need @fastify/helmet instead of the generic helmet package:
npm install @fastify/helmetRegister it as a plugin in your Fastify setup:
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import helmet from '@fastify/helmet';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(
AppModule,
new FastifyAdapter(),
);
await app.register(helmet);
await app.listen(3000);
}
bootstrap();The API is nearly identical. Customization works the same way:
await app.register(helmet, {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
},
},
});We chose Fastify for a high-throughput CRM backend handling 50k requests per minute. Helmet integration was effortless, and performance overhead was negligible.
Check your results with securityheaders.com
After deploying Helmet, verify that headers are actually being set and graded by external auditors. securityheaders.com is the standard tool:
- Navigate to
https://securityheaders.comin your browser. - Enter your production domain.
- Wait for the scan to complete.
- Review the report, which grades you on letter scale (A to F).
A typical Helmet-enabled site scores A or A+. The report shows exactly which headers are present and their values. You'll see:
- Missing headers (sometimes expected, e.g., if you don't use CSP reporting).
- Weak configurations (e.g., CSP using too many
'unsafe-inline'directives). - Suggestions for improvement.
In a previous e-commerce project, the security team insisted on X-Frame-Options: DENY, which broke an embedded payment iframe. The securityheaders report made it obvious we needed ALLOW-FROM or to reconsider architecture. The report is also useful for stakeholders who want proof of security posture.
Browser DevTools also show headers:
// Inspect in Chrome DevTools > Network > select any request > Response Headers
strict-transport-security: max-age=15552000; includeSubDomains
x-content-type-options: nosniff
x-frame-options: DENY
x-xss-protection: 1; mode=block
referrer-policy: no-referrer
permissions-policy: camera=(), microphone=(), geolocation=()Common mistakes and how to avoid them
Forgetting to apply Helmet before other middleware: Always call app.use(helmet()) early in your bootstrap, before route handlers or custom middleware that might conflict. Helmet should be one of the first lines after NestFactory.create().
Over-permissive CSP: A CSP that allows 'unsafe-inline' for scripts negates much of the protection. We often see teams disable CSP entirely because "it breaks things." Instead, invest an hour auditing your dependencies. Bundled CSS and externally-sourced scripts are the better path.
Not testing in staging: Helm defaults can break authentication redirects, third-party embeds, or analytics. Always deploy to a staging environment first and run your integration test suite. Check CSP violation logs.
Ignoring HSTS for non-HTTPS origins: If your app doesn't yet run on HTTPS, Helmet's HSTS header doesn't apply anyway. But once you enable HTTPS, that header persists in browsers for 30 days by default. Plan for this; don't flip HTTP off and on repeatedly in staging.
Hardcoding CSP without environment variables: Your staging domain, API endpoint, and CDN differ from production. Use environment variables or a config file so CSP directives match your deployment:
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
process.env.SCRIPT_SRC_DOMAIN || 'https://cdn.example.com',
],
connectSrc: ["'self'", process.env.API_BASE_URL || 'https://api.example.com'],
},
},
}));Key takeaways
- Install
helmet(or@fastify/helmetfor Fastify) and callapp.use(helmet())in your main.ts bootstrap function to enable HTTP security headers automatically. - For HTML-serving apps, customize the CSP directives to allow your legitimate scripts, styles, and API endpoints; avoid
'unsafe-inline'for scripts where possible. - Test your configuration in staging first, monitoring CSP violation logs to catch misconfigurations.
- Use securityheaders.com to verify your headers are set correctly and receive a security grade for your domain.
- Review common mistakes—forgetting to apply Helmet early, over-permissive policies, and hardcoded domain values—before deploying to production.






Comments
Loading comments…