Set up Node.js application security with Helmet and rate limiting

Intermediate 25 min Apr 05, 2026 153 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Secure your Node.js Express applications against common vulnerabilities with Helmet.js middleware and implement rate limiting to prevent abuse and DDoS attacks.

Prerequisites

  • Root or sudo access
  • Basic knowledge of Node.js and Express
  • Understanding of HTTP headers

What this solves

Modern web applications face constant security threats from malicious actors attempting to exploit common vulnerabilities. Node.js applications, while powerful and flexible, require proper security hardening to protect against attacks like XSS, clickjacking, and brute force attempts. This tutorial shows you how to implement enterprise-grade security using Helmet.js middleware for HTTP header protection and express-rate-limit for traffic throttling.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you have the latest security patches and package definitions.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Node.js and npm

Install the latest LTS version of Node.js along with npm package manager for dependency management.

curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm install -g npm@latest
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo dnf install -y nodejs
sudo npm install -g npm@latest

Create project directory

Create a dedicated directory for your secure Node.js application with proper ownership and permissions.

mkdir -p ~/secure-nodejs-app
cd ~/secure-nodejs-app
npm init -y

Install Express framework

Install Express.js as the foundation for your web application along with essential development dependencies.

npm install express
npm install --save-dev nodemon

Install security packages

Install Helmet.js for HTTP header security and express-rate-limit for request throttling protection.

npm install helmet express-rate-limit express-slow-down

Create basic Express application

Create a foundational Express server that will serve as the base for implementing security middleware.

const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const slowDown = require('express-slow-down');

const app = express();
const PORT = process.env.PORT || 3000;

// Basic middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// Basic routes (will add security later)
app.get('/', (req, res) => {
    res.json({ message: 'Secure Node.js application', status: 'running' });
});

app.get('/api/data', (req, res) => {
    res.json({ data: 'Sample API endpoint', timestamp: new Date().toISOString() });
});

app.listen(PORT, () => {
    console.log(Server running on port ${PORT});
});

Configure Helmet.js security middleware

Implement comprehensive Helmet configuration

Configure Helmet with production-ready security headers to protect against common web vulnerabilities. Add this configuration after your basic middleware setup.

// Helmet security configuration
app.use(helmet({
    contentSecurityPolicy: {
        directives: {
            defaultSrc: ["'self'"],
            scriptSrc: ["'self'", "'unsafe-inline'"],
            styleSrc: ["'self'", "'unsafe-inline'"],
            imgSrc: ["'self'", "data:", "https:"],
            connectSrc: ["'self'"],
            fontSrc: ["'self'"],
            objectSrc: ["'none'"],
            mediaSrc: ["'self'"],
            frameSrc: ["'none'"]
        }
    },
    crossOriginEmbedderPolicy: true,
    crossOriginOpenerPolicy: true,
    crossOriginResourcePolicy: { policy: "cross-origin" },
    dnsPrefetchControl: true,
    frameguard: { action: 'deny' },
    hidePoweredBy: true,
    hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true
    },
    ieNoOpen: true,
    noSniff: true,
    originAgentCluster: true,
    permittedCrossDomainPolicies: false,
    referrerPolicy: { policy: "no-referrer" },
    xssFilter: true
}));

Configure custom security headers

Add additional custom security headers for enhanced protection against emerging threats and compliance requirements.

// Custom security headers
app.use((req, res, next) => {
    res.setHeader('X-Frame-Options', 'DENY');
    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
    res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
    res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
    res.setHeader('Pragma', 'no-cache');
    res.setHeader('Expires', '0');
    next();
});

Implement rate limiting protection

Configure global rate limiting

Set up global rate limiting to protect your entire application from abuse and potential DDoS attacks.

// Global rate limiting
const globalLimiter = rateLimit({
    windowMs: 15  60  1000, // 15 minutes
    max: 100, // Limit each IP to 100 requests per windowMs
    message: {
        error: 'Too many requests from this IP',
        retryAfter: Math.round(15  60  1000 / 1000)
    },
    standardHeaders: true,
    legacyHeaders: false,
    handler: (req, res) => {
        res.status(429).json({
            error: 'Rate limit exceeded',
            retryAfter: Math.round(req.rateLimit.resetTime - Date.now() / 1000)
        });
    }
});

app.use(globalLimiter);

Configure API-specific rate limiting

Create stricter rate limits for sensitive API endpoints that require additional protection from automated attacks.

// API-specific rate limiting
const apiLimiter = rateLimit({
    windowMs: 15  60  1000, // 15 minutes
    max: 50, // Stricter limit for API endpoints
    message: {
        error: 'API rate limit exceeded',
        retryAfter: Math.round(15  60  1000 / 1000)
    },
    standardHeaders: true,
    legacyHeaders: false
});

// Slow down middleware for progressive delays
const speedLimiter = slowDown({
    windowMs: 15  60  1000, // 15 minutes
    delayAfter: 10, // Allow 10 requests per windowMs without delay
    delayMs: 500, // Add 500ms delay per request after delayAfter
    maxDelayMs: 20000 // Maximum delay of 20 seconds
});

// Apply to API routes
app.use('/api', apiLimiter);
app.use('/api', speedLimiter);

Create authentication rate limiting

Implement aggressive rate limiting for authentication endpoints to prevent brute force attacks on user credentials.

// Authentication rate limiting
const authLimiter = rateLimit({
    windowMs: 15  60  1000, // 15 minutes
    max: 5, // Limit each IP to 5 login attempts per windowMs
    skipSuccessfulRequests: true, // Don't count successful requests
    message: {
        error: 'Too many authentication attempts',
        retryAfter: Math.round(15  60  1000 / 1000)
    }
});

// Sample authentication endpoint with rate limiting
app.post('/auth/login', authLimiter, (req, res) => {
    // Simulate authentication logic
    const { username, password } = req.body;
    
    if (!username || !password) {
        return res.status(400).json({ error: 'Username and password required' });
    }
    
    // In production, implement proper authentication
    res.json({ message: 'Authentication endpoint (implement your auth logic here)' });
});

Advanced security configurations

Configure request validation

Add request validation and sanitization to prevent malicious input from reaching your application logic.

// Request size limiting and validation
app.use(express.json({ 
    limit: '10mb',
    verify: (req, res, buf, encoding) => {
        // Prevent malformed JSON attacks
        try {
            JSON.parse(buf);
        } catch (e) {
            res.status(400).json({ error: 'Invalid JSON format' });
            throw new Error('Invalid JSON');
        }
    }
}));

// Input sanitization middleware
app.use((req, res, next) => {
    if (req.body) {
        for (let key in req.body) {
            if (typeof req.body[key] === 'string') {
                req.body[key] = req.body[key].trim();
            }
        }
    }
    next();
});

Add security monitoring

Implement basic security monitoring to log and track potential security incidents and rate limit violations.

// Security monitoring and logging
const securityLog = (req, message, level = 'info') => {
    const logEntry = {
        timestamp: new Date().toISOString(),
        ip: req.ip || req.connection.remoteAddress,
        userAgent: req.get('User-Agent'),
        url: req.originalUrl,
        method: req.method,
        message: message,
        level: level
    };
    
    console.log([SECURITY-${level.toUpperCase()}], JSON.stringify(logEntry));
};

// Security event middleware
app.use((req, res, next) => {
    // Log suspicious patterns
    const suspiciousPatterns = ['/admin', '/.env', '/wp-admin', '/phpmyadmin'];
    if (suspiciousPatterns.some(pattern => req.path.includes(pattern))) {
        securityLog(req, Suspicious path access: ${req.path}, 'warning');
    }
    
    next();
});

Update package.json scripts

Add convenient npm scripts for development and production deployment with proper environment handling.

{
  "name": "secure-nodejs-app",
  "version": "1.0.0",
  "description": "Secure Node.js application with Helmet and rate limiting",
  "main": "app.js",
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "express": "^4.18.2",
    "helmet": "^7.1.0",
    "express-rate-limit": "^7.1.5",
    "express-slow-down": "^2.0.1"
  },
  "devDependencies": {
    "nodemon": "^3.0.2"
  }
}

Production deployment configuration

Create systemd service

Configure a systemd service for production deployment with proper user isolation and security settings.

sudo useradd --system --create-home --shell /bin/false nodeapp
sudo chown -R nodeapp:nodeapp ~/secure-nodejs-app
[Unit]
Description=Secure Node.js Application
After=network.target

[Service]
Type=simple
User=nodeapp
Group=nodeapp
WorkingDirectory=/home/nodeapp/secure-nodejs-app
ExecStart=/usr/bin/node app.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3000

Security settings

NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/home/nodeapp/secure-nodejs-app [Install] WantedBy=multi-user.target

Configure reverse proxy with Nginx

Set up Nginx as a reverse proxy to handle SSL termination and additional security features. This setup works well with our Nginx reverse proxy with SSL tutorial.

server {
    listen 80;
    server_name example.com;
    
    # Security headers (additional to Helmet)
    add_header X-Real-IP $remote_addr;
    add_header X-Forwarded-For $proxy_add_x_forwarded_for;
    add_header X-Forwarded-Proto $scheme;
    
    # Rate limiting at Nginx level
    limit_req_zone $binary_remote_addr zone=app:10m rate=10r/s;
    limit_req zone=app burst=20 nodelay;
    
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 60s;
        proxy_connect_timeout 60s;
    }
}

Enable and start services

Enable the systemd service and configure Nginx to start your secure application automatically on system boot.

sudo systemctl enable secure-nodejs-app
sudo systemctl start secure-nodejs-app
sudo ln -s /etc/nginx/sites-available/secure-nodejs-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Verify your setup

Test your security configuration to ensure all middleware is working correctly and providing proper protection.

# Check service status
sudo systemctl status secure-nodejs-app

Test basic functionality

curl -I http://localhost:3000

Test rate limiting (run multiple times quickly)

for i in {1..10}; do curl -s http://localhost:3000/api/data; done

Check security headers

curl -I http://localhost:3000 | grep -E "X-|Content-Security-Policy|Strict-Transport-Security"

Monitor logs

sudo journalctl -u secure-nodejs-app -f
Note: The security headers should include X-Content-Type-Options, X-Frame-Options, and Content-Security-Policy. Rate limiting should trigger after exceeding the configured thresholds.

Common issues

SymptomCauseFix
Application won't startPort already in useCheck with sudo netstat -tlnp | grep :3000 and kill conflicting process
Rate limiting not workingMiddleware order incorrectEnsure rate limiting middleware is applied before route handlers
CSP blocking resourcesToo restrictive Content Security PolicyAdjust CSP directives to allow necessary resources
502 Bad Gateway with NginxNode.js service not runningsudo systemctl start secure-nodejs-app
Headers not appearingNginx overriding headersCheck Nginx configuration for conflicting header directives
High memory usageRate limiter storing too many IPsReduce windowMs or implement Redis store for rate limiting
Security Warning: Never disable security features to fix functionality issues. Instead, configure them properly to balance security and usability.

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.