TwinsCloud Private Limited - Premium Cloud Solutions & Training Logo
Back to Blog

Defending Node.js APIs Against DDoS and High-Concurrency Surges

Distributed Denial of Service (DDoS) attacks and sudden viral traffic surges represent one of the most punishing stress tests for web infrastructure. Because Node.js operates on an asynchronous, single-threaded event loop, a poorly defended API can be brought to its knees not just by massive volumetric multi-gigabit botnets, but by subtle, low-bandwidth Layer 7 application-layer attacks designed to exhaust database connections and freeze the JavaScript runtime.

In this technical security guide, the cloud architects at TwinsCloud walk through a battle-tested, four-tier defense architecture: edge mitigation, reverse-proxy connection throttling, Redis sliding-window rate limiting, and Node.js runtime self-defense.

1. Understanding the Threat: Layer 3/4 vs. Layer 7 Attacks

Defending an API requires understanding the exact attack vector:

  • Layer 3/4 (Network/Transport Floods): SYN floods, UDP amplification, and ICMP reflection designed to saturate network bandwidth and overwhelm network router state tables. These must be mitigated upstream at the DNS and edge layer.
  • Layer 7 (Application Floods & Semantic Attacks): Legitimate-looking HTTP GET/POST requests targeting expensive endpoints (e.g., complex full-text search, report generation, or password hashing). A single attacker sending 200 requests/second to an unindexed search endpoint can exhaust backend database connections and starve legitimate users.
  • Slowloris / Slow POST Attacks: Attackers open hundreds of connections and transmit HTTP headers excruciatingly slowly (1 byte every 15 seconds), holding server sockets open indefinitely until the web server exhausts its connection pool.

2. Tier 1: Edge Mitigation with Cloudflare WAF

Never allow untrusted, malicious traffic to reach your origin servers. Placing Cloudflare or AWS CloudFront with AWS WAF in front of your domain stops 95% of attacks before they consume a single cycle of backend compute:

  • Cloudflare Under Attack Mode: Automatically challenges suspicious automated visitors with non-intrusive JavaScript computational puzzles.
  • Custom Edge Rate Limiting Rules: Throttle requests to sensitive endpoints (e.g., /api/auth/* or /api/search) at the edge based on IP, ASN, or country code.
  • Origin IP Shielding: Ensure your origin server's direct IP address is never publicly exposed. Configure Nginx and cloud firewalls to reject all incoming traffic that does not originate from verified Cloudflare IP address blocks.

3. Tier 2: Reverse Proxy Hardening with Nginx

Before requests reach Node.js, an Nginx reverse proxy acts as an indispensable shock absorber. Here is a production-hardened Nginx configuration specifically tuned to mitigate Slowloris and connection flooding:

# Define shared memory zones for rate and connection limiting
limit_req_zone $binary_remote_addr zone=api_rate_limit:20m rate=30r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:20m;

server {
    listen 443 ssl http2;
    server_name api.twinscloud.com;

    # Protect against Slowloris: Enforce aggressive timeouts
    client_body_timeout 10s;
    client_header_timeout 10s;
    keepalive_timeout 15s;
    send_timeout 10s;

    # Limit payload sizes to prevent buffer overflow attacks
    client_max_body_size 2M;
    client_body_buffer_size 128k;

    # Apply concurrent connection limit per IP
    limit_conn conn_limit 20;

    location /api/ {
        # Allow brief bursts up to 20 requests with nodelay
        limit_req zone=api_rate_limit burst=20 nodelay;

        proxy_pass http://localhost: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;
    }
}

4. Tier 3: Redis Sliding-Window Rate Limiting in Node.js

Naive rate limiters rely on fixed time windows (e.g., reset counter every 60 seconds). This allows attackers to exploit the "boundary burst" vulnerability: sending 100 requests at 11:59:59 and another 100 at 12:00:01, effectively executing 200 requests within a two-second interval.

A Sliding Window Counter implemented via Redis Sorted Sets (ZSET) tracks requests with microsecond precision, preventing burst exploits:

// middleware/rateLimiter.ts
import Redis from "ioredis";
import { Request, Response, NextFunction } from "express";

const redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379");

export async function slidingWindowLimiter(
  req: Request,
  res: Response,
  next: NextFunction
) {
  const ip = req.ip || req.headers["x-forwarded-for"] || "anonymous";
  const key = `rate_limit:${ip}`;
  const now = Date.now();
  const windowMs = 60 * 1000; // 1 minute window
  const maxRequests = 60; // Max 60 requests per minute

  try {
    const multi = redis.multi();
    // Remove timestamps older than the current window
    multi.zremrangebyscore(key, 0, now - windowMs);
    // Add current request timestamp
    multi.zadd(key, now, now.toString());
    // Count active requests within current window
    multi.zcard(key);
    // Set expiry to automatically clean up keys
    multi.expire(key, 60);

    const results = await multi.exec();
    const requestCount = results ? (results[2][1] as number) : 0;

    res.setHeader("X-RateLimit-Limit", maxRequests);
    res.setHeader("X-RateLimit-Remaining", Math.max(0, maxRequests - requestCount));

    if (requestCount > maxRequests) {
      return res.status(429).json({
        error: "Too Many Requests",
        message: "You have exceeded your request quota. Please retry in 60 seconds.",
      });
    }

    next();
  } catch (error) {
    console.error("Rate limiter failure, failing open:", error);
    next(); // Fail open so redis glitches do not bring down production
  }
}

5. Tier 4: Node.js Event Loop Self-Defense

Even with rate limiters active, an unexpected wave of complex requests can starve the Node.js event loop. When event loop lag spikes, the server stops accepting new connections and drops active TCP sockets.

Monitor event loop latency with libraries like toobusy-js. If the loop latency exceeds 70ms, your server should immediately respond with 503 Service Unavailable, rejecting non-essential requests before server memory exhausts:

import toobusy from "toobusy-js";

// Set maximum acceptable event loop lag in milliseconds
toobusy.maxLag(70);

app.use((req, res, next) => {
  if (toobusy()) {
    res.status(503).json({
      error: "Server Overloaded",
      message: "Our servers are experiencing extreme traffic. Please try again shortly.",
    });
  } else {
    next();
  }
});

6. Circuit Breakers for Downstream Dependencies

When high traffic inundates your API, third-party payment gateways, SMS providers, or backend databases often fail first. Without protection, your Node.js processes will hang waiting for socket timeouts, cascading failure throughout your system.

Wrap downstream calls with circuit breaker patterns using libraries like Opossum. When failure rates exceed 50%, the circuit breaker trips open, instantly returning cached responses or fallback errors without tying up Node.js thread resources.

7. Active Attack Incident Response Playbook

If your application comes under an active Layer 7 DDoS attack, execute these four immediate actions:

  1. Activate Cloudflare "Under Attack Mode": This forces incoming HTTP traffic through cryptographic challenges, instantly filtering out unmanaged headless bots.
  2. Inspect Top Offending IPs & User-Agents in Nginx: Execute awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -20 to find offending IPs and block them at the cloud firewall level.
  3. Temporarily Cache Dynamic Endpoints: Configure Nginx micro-caching (caching GET responses for just 3 to 5 seconds). A 3-second cache turns 10,000 backend database queries into a single database hit.
  4. Scale ECS / Container Replicas: Trigger manual horizontal container scaling to absorb legitimate traffic spikes while filtering rules propagate.

8. Frequently Asked Questions (FAQs)

Q1: Can software rate limiters in Node.js replace a Cloudflare WAF during a 100 Gbps attack?

No. A 100 Gbps attack will saturate network interfaces and network switch buffers long before packets ever reach your operating system kernel or Node.js runtime. Volumetric network floods must be absorbed by global edge anycast networks like Cloudflare or AWS Shield.

Q2: What is the primary difference between fixed-window and sliding-window rate limiting?

Fixed-window rate limiters reset counters at rigid intervals, allowing attackers to double their permitted requests across the boundary window. Sliding-window rate limiters maintain a rolling timeline of timestamps, accurately enforcing rate limits regardless of when requests arrive.

Q3: What makes single-threaded Node.js applications uniquely susceptible to Slowloris attacks?

If Node.js is exposed directly to the internet without a reverse proxy like Nginx, thousands of slow-writing clients can tie up all available Node.js file descriptors and socket buffers, preventing the server from accepting new legitimate connections.

Q4: How do we prevent rate limiters from blocking entire corporate offices sharing a single NAT IP?

For authenticated users, rate limit based on a cryptographically verified JWT user_id or API key rather than raw IP addresses. For unauthenticated public endpoints, assign higher burst thresholds or implement CAPTCHA challenges before enforcing hard IP bans.

Q5: What standard HTTP response headers should a secured API return when rate-limiting?

Compliant APIs should return standard IETF rate-limiting headers: X-RateLimit-Limit (total quota), X-RateLimit-Remaining (remaining quota), and Retry-After (seconds until quota resets).


Concerned about your application's security posture and resilience against traffic surges? Contact TwinsCloud for a comprehensive Cloud Security Audit or book a technical strategy call via our Consultation Page.