
Table of Contents
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.
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.
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 Name | Type | Why It Matters | Alert Threshold Example |
|---|---|---|---|
http_request_duration_seconds | Histogram | Detects latency degradation before timeouts occur | p95 > 500ms for 5m |
http_requests_total{status=~"5.."} | Counter | Tracks server error rate directly impacting users | Rate > 1% for 2m |
nodejs_eventloop_lag_seconds | Gauge | Reveals blocking code starving the async queue | p99 > 100ms |
bullmq_job_duration_seconds | Histogram | Monitors background task health separate from API | p95 > 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.
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.