Scale and Monitor NestJS in Production

Khimananda Oli 7 min read Programming and Languages
Scale and Monitor NestJS in Production

By Khimananda Oli | Last reviewed: August 2026

NestJS applications often hit performance walls when traffic grows beyond a single instance, making it critical to understand how to scale and monitor NestJS in production before users experience latency or outages. While the framework’s modular architecture supports growth, default configurations rarely handle high concurrency without explicit tuning for statelessness, observability, and resource management. This guide covers the exact infrastructure patterns and instrumentation I use to keep NestJS services reliable under load, drawing from real-world deployments on Kubernetes and cloud-native stacks.

How Do You Architect NestJS for Horizontal Scaling?

Scaling NestJS horizontally requires treating each application instance as ephemeral and interchangeable. A common mistake is storing session data or file uploads locally within the container filesystem, which breaks immediately when a load balancer routes requests across multiple pods. For teams managing backend infrastructure, especially those transitioning from monolithic PHP setups like Laravel on Ubuntu VPS, this shift to statelessness is the foundational step for cloud-native reliability.

Ingress / LBRound RobinNestJS Pod AStateless WorkerNestJS Pod BStateless WorkerNestJS Pod CStateless WorkerRedis ClusterSessions / CachePostgreSQLPrimary DataBullMQ QueueAsync Jobs
Stateless NestJS scaling architecture showing load distribution across pods with externalized state in Redis and PostgreSQL

Externalize All Stateful Components

You must move sessions, caches, and job queues out of the application process. Use Redis or Memcached for session storage via the @nestjs/throttler or express-session with a compatible store. For background tasks, never rely on in-memory arrays; implement BullMQ or RabbitMQ so that if Pod A crashes mid-job, Pod B can pick up the retry without data loss. This separation allows you to scale API workers independently from queue processors.

Configure Graceful Shutdowns

Kubernetes kills pods with a SIGTERM signal. If your NestJS app doesn't handle this, active requests drop during deployments or scaling events. Enable graceful shutdown in your main.ts:

const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
await app.listen(3000);

This ensures the HTTP server stops accepting new connections while finishing existing ones before the process exits. Combine this with a preStop hook in your Kubernetes deployment to add a small sleep (e.g., 5 seconds), allowing the service mesh or ingress controller to update endpoints before traffic hits the terminating pod.

How Do You Configure Kubernetes HPA for NestJS?

CPU-based autoscaling is often too slow for Node.js APIs because the event loop can saturate long before CPU hits 80%. To properly scale and monitor NestJS in production, configure Horizontal Pod Autoscaler (HPA) using custom metrics exposed via Prometheus Adapter. This reacts to actual application load rather than generic system resources.

Expose Custom Metrics for Autoscaling

Install @willsoto/nestjs-prometheus to expose HTTP request duration and throughput. Define a ServiceMonitor and ensure Prometheus scrapes your pods. Then, configure the Prometheus Adapter to map http_requests_total to a custom metric http_requests_per_second.

Define the HPA Manifest

Create an HPA that targets requests per second instead of CPU:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nestjs-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nestjs-api
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "50"

This configuration scales up when average RPS exceeds 50 per pod. Always set minReplicas to at least 2 for high availability. Test scaling behavior with load testing tools like k6 before relying on it in production.

How Do You Instrument NestJS with OpenTelemetry?

Logs tell you what happened; traces tell you where it broke. Distributed tracing is non-negotiable for microservices. I recommend following the standardized approach detailed in OpenTelemetry: The Observability Standard to avoid vendor lock-in while gaining deep visibility into request flows across services.

NestJS AppOTel SDKAuto-InstrumentOTel CollectorBatch & ExportSidecar / AgentJaeger / TempoDistributed TracesPrometheusMetrics BackendLoki / ELKStructured Logs
OpenTelemetry instrumentation flow showing trace data moving from NestJS through the collector to observability backends

Initialize the SDK Correctly

Create a dedicated tracing.ts file imported at the very top of main.ts before any other modules. This ensures all libraries are patched before initialization:

import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter(),
  instrumentations: [
    new HttpInstrumentation(),
    new ExpressInstrumentation(),
  ],
});

sdk.start();
process.on('SIGTERM', () => sdk.shutdown());

This setup automatically captures incoming HTTP requests, database calls, and outgoing fetch/axios requests. Correlation IDs propagate through headers, linking logs to traces seamlessly.

Which Metrics Matter Most for NestJS Reliability?

Collecting everything creates noise. Focus on the signals that actually predict user pain. Understanding the four golden signals of monitoring helps filter relevant data from vanity metrics. For NestJS specifically, these four indicators provide the highest signal-to-noise ratio:

Metric NameTypeWhy It MattersAlert Threshold Example
http_request_duration_secondsHistogramDetects latency degradation before timeouts occurp95 > 500ms for 5m
http_requests_total{status=~"5.."}CounterTracks server error rate directly impacting usersRate > 1% for 2m
nodejs_eventloop_lag_secondsGaugeReveals blocking code starving the async queuep99 > 100ms
bullmq_job_duration_secondsHistogramMonitors background task health separate from APIp95 > 30s

Avoid Common Metric Pitfalls

Never use gauges for request counts—they reset on restart and lose data. Always use counters for cumulative values. Label cardinality matters too: avoid high-cardinality labels like user_id or request_path with dynamic segments. Normalize paths to templates (e.g., /users/:id) to prevent memory exhaustion in Prometheus. Refer to Prometheus metrics monitoring fundamentals for proper naming conventions and retention strategies.

How Do You Optimize NestJS Container Resources?

Node.js memory management differs significantly from compiled languages. Setting Kubernetes limits too low causes OOMKills; setting them too high wastes money. Profile your application under realistic load first using clinic.js or built-in V8 inspector to establish baselines.

Right-Size Memory Requests and Limits

Set requests to your observed p95 memory usage plus 20% buffer. Set limits to 1.5x–2x requests to allow garbage collection spikes without eviction. For a typical NestJS API handling 100 RPS:

resources:
  requests:
    memory: "512Mi"
    cpu: "250m"
  limits:
    memory: "1Gi"
    cpu: "1000m"

Enable the --max-old-space-size flag in your Dockerfile to match container limits, preventing Node from attempting allocations beyond available memory:

CMD ["node", "--max-old-space-size=900", "dist/main.js"]

Tune the Event Loop

Monitor event loop lag as a leading indicator of saturation. If lag consistently exceeds 50ms, adding more pods won't help—you have synchronous code blocking execution. Use async_hooks or profiling to identify bottlenecks. Consider offloading heavy computation to worker threads or separate microservices written in Rust/Go if JavaScript becomes the constraint.

Healthy Event LoopConsistent Low LatencyNon-blocking I/O OperationsEvent Loop Lag < 10msBlocked Event LoopSYNC BLOCKSpike Latency & TimeoutsCPU-bound Tasks Blocking QueueEvent Loop Lag > 500msKey Takeaway: Scaling Won't Fix Blocking CodeProfile with clinic.js or --prof before adding replicasOffload heavy work to Worker Threads or sidecarsMonitor nodejs_eventloop_lag_seconds as primary health signalSet alerts on p99 lag, not just CPU/memory utilization
Comparison of healthy versus blocked NestJS event loop behavior highlighting why scaling alone cannot fix synchronous bottlenecks

Conclusion

To successfully scale and monitor NestJS in production, treat observability and scalability as inseparable concerns rather than afterthoughts. Start by enforcing statelessness and graceful shutdowns, then implement custom-metric-driven autoscaling that responds to actual application behavior. Instrument comprehensively with OpenTelemetry from day one, focusing on the four golden signals filtered through NestJS-specific lenses like event loop lag and queue depth. These practices transform fragile prototypes into resilient platforms capable of handling serious traffic.

If your team needs hands-on support implementing these patterns, auditing existing infrastructure, or designing compliance-ready observability stacks, reach out to discuss your specific requirements. Production reliability isn't accidental—it's engineered deliberately.

Frequently Asked Questions

PM2 remains the standard for Node.js clustering. Use ecosystem.config.cjs with exec_mode set to cluster and instances matching CPU cores. It handles auto-restarts, log rotation, and zero-downtime reloads natively without external container orchestration overhead for single-server deployments.

Yes, use Redis or NATS as a transport layer. Configure ClientProxy with TCP or gRPC for inter-service communication behind a load balancer. Ensure stateless design and externalize session storage to allow multiple instances to handle requests independently without sticky sessions.

Event loop lag, heap usage, HTTP latency percentiles, and error rates are critical. Track these via Prometheus client libraries. High event loop delay indicates blocking code, while rising heap usage suggests memory leaks requiring immediate profiling before out-of-memory crashes occur in production environments.

Yes, install @opentelemetry/auto-instrumentations-node. It automatically traces HTTP, database, and messaging calls without manual decorator boilerplate. Export traces to Jaeger or Grafana Tempo for distributed tracing across microservices, providing end-to-end visibility into request flows and bottleneck identification.

Allocate 512MB to 1GB minimum per instance. Node.js V8 heap defaults to 4GB on 64-bit systems but resize based on actual workload. Monitor RSS versus heap used; consistent growth above 70% utilization requires increasing limits or optimizing garbage collection pressure.

Absolutely. Use native K8s Services and Ingress controllers for routing. Implement readiness probes checking /health endpoints and liveness probes verifying event loop responsiveness. Horizontal Pod Autoscaler can scale replicas based on custom Prometheus metrics like request queue depth or CPU saturation thresholds.

Synchronous file I/O, heavy JSON parsing, or unoptimized database queries block the main thread. Offload CPU-intensive tasks to worker threads using nest-worker-threads module. Profile with clinic.js bubbleprof to identify blocking functions and refactor them into asynchronous operations or separate processes.

Use @nestjs/terminus package. Expose /health endpoint checking database connectivity, Redis availability, and disk space. Return 503 status when dependencies fail so load balancers stop routing traffic. Combine with Kubernetes probe timeouts to prevent cascading failures during partial infrastructure outages.

BullMQ suits background jobs needing priority, retries, and rate limiting within Node ecosystems. RabbitMQ excels at complex routing patterns and polyglot architectures. Choose BullMQ for simpler task processing with Redis backing; select RabbitMQ when requiring message persistence, pub/sub fanout, or integration with non-Node services.

Under 200ms typically. Use webpack or esbuild bundling, lazy-load modules, and minimize dependency count. Pre-warm functions via scheduled invocations if latency matters. Consider AWS Lambda SnapStart or Cloudflare Workers for near-instant initialization without code changes.

Structured JSON logs with correlation IDs. Use pino-http middleware for fast, low-overhead logging. Include trace_id, span_id, user_id, and request_path fields. Ship to Loki or Elasticsearch for filtering. Avoid string concatenation; structured data enables automated alerting and dashboard aggregation across distributed systems.

Centralize authentication via JWT validation middleware or API gateway. Store secrets in HashiCorp Vault or AWS Secrets Manager, never in environment variables directly. Enable TLS termination at ingress level. Rate limit per IP and user token using Redis-backed throttlers to prevent abuse across all scaled instances equally.

Common causes include unclosed database connections, event listener accumulation, or global cache bloat. Use heap snapshots via Chrome DevTools or memlab to compare baseline versus loaded states. Implement connection pooling with max limits and add cleanup hooks in module onModuleDestroy lifecycle methods.

Yes. Import nodejs-dashboard or nestjs-prometheus prebuilt dashboards. Create panels for request rate, error percentage, p99 latency, and GC pause duration. Set alerts on SLO violations. Correlate metric spikes with deployment timestamps to quickly identify regressions introduced by new releases or configuration changes.

Calculate as (core_count * 2) + effective_spindle_count for PostgreSQL. For cloud databases, start with 20-50 connections per instance depending on query complexity. Monitor active versus idle connections; excessive waiting indicates undersized pools while high idle counts waste resources. Adjust dynamically using pg-pool configuration options.