{"id":23,"date":"2026-06-06T21:48:50","date_gmt":"2026-06-06T21:48:50","guid":{"rendered":"https:\/\/softwareproduction.eu\/wordpress\/?p=23"},"modified":"2026-06-06T23:45:23","modified_gmt":"2026-06-06T23:45:23","slug":"memory-stability-and-pm2-running-a-long-lived-node-js-server","status":"publish","type":"post","link":"https:\/\/softwareproduction.eu\/?p=23","title":{"rendered":"Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>Seventeenth in a series about migrating from legacy architectures to a modern Nuxt 4 stack.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Inconvenient Truth About Node.js Servers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js is optimized for event-driven I\/O, not for long-lived servers that render thousands of pages per hour. Over time, the V8 heap grows and objects such as GraphQL responses, Vue server renderer allocations, cached strings, and Apollo Client instances accumulate. Without intervention, a production process will eventually consume all available memory and get killed by the container orchestrator.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is not a bug to eliminate so much as a reality to manage. The real question is not <em>whether<\/em> memory will approach its limit, but <em>how gracefully<\/em> the system will handle it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">PM2 Cluster Mode: Zero-Downtime Worker Management<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In a large enterprise application, instead of a single Node.js process, PM2 typically runs N worker processes \u2014 often 2\u20133 per container. Each worker handles requests independently, which provides two critical benefits:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Fault isolation<\/strong> \u2014 if one worker crashes or becomes unresponsive, the others keep serving requests  <\/li>\n<li><strong>Rolling restarts<\/strong> \u2014 when a worker approaches its memory limit, PM2 restarts it while the other workers continue handling traffic<\/li>\n<\/ol>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n    subgraph C[\"Container (2 vCPU, 4 GiB RAM)\"]\n        direction TB\n        M[PM2 Master Process]\n\n        subgraph W1[Worker 1]\n            direction TB\n            H1[V8 Heap\\n~1.5 GiB\\nmax-old-space-size=1536]\n            R1[Handles requests\\nindependently]\n        end\n\n        subgraph W2[Worker 2]\n            direction TB\n            H2[V8 Heap\\n~1.5 GiB\\nmax-old-space-size=1536]\n            R2[Handles requests\\nindependently]\n        end\n    end\n\n    M --- W1\n    M --- W2<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When Worker 1 approaches 1,536 MB of heap usage, PM2 restarts it. Worker 2 handles traffic during the restart, which typically takes 2\u20133 seconds for V8 to compile the Nuxt application. For that worker, downtime lasts a few seconds. For the overall application, it is effectively zero.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">V8 Heap Cap: Trading Throughput for Predictability<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">By default, V8 uses a dynamic heap limit that grows based on available system memory. In containerized environments, that behavior is risky \u2014 V8 can grow beyond the container\u2019s memory allocation and trigger an OOM kill.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Setting an explicit heap limit forces more aggressive garbage collection:<\/p>\n\n\n\n<pre><code class=\"language-bash\">NODE_OPTIONS=--max-old-space-size=1536\n\nEffect:\n  Without cap:  GC runs infrequently \u2192 heap grows to 3+ GiB \u2192 OOM kill\n  With cap:     GC runs at ~1.2 GiB \u2192 heap stays under 1.5 GiB \u2192 stable<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n    A[Start] --&gt; B[No explicit V8 heap cap]\n    B --&gt; C[\"Heap grows with available memory\\n&amp;gt; 3 GiB in container\"]\n    C --&gt; D[Container OOM kill]\n\n    A --&gt; E[Set --max-old-space-size=1536]\n    E --&gt; F[GC runs around 1.2 GiB]\n    F --&gt; G[\"Heap stays &amp;lt;= 1.5 GiB\"]\n    G --&gt; H[Process stable\\nSlightly lower peak throughput]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The trade-off is straightforward: more frequent GC pauses of 2\u20135 ms each reduce peak throughput by about 5%. But the process never gets OOM-killed, which is a far better outcome in production.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Memory Is the Scaling Bottleneck<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Load testing for a typical Nuxt SSR frontend in a large SaaS or e-commerce platform reveals something counterintuitive: the Nuxt SSR application is often <strong>I\/O-bound, not CPU-bound<\/strong>.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n    subgraph RU[Resource Usage Under Load]\n        CPU[CPU peak ~12%]\n        MEM[Memory peak ~60%]\n        BOT[Bottleneck: Memory, not CPU]\n    end\n\n    CPU --&gt; BOT\n    MEM --&gt; BOT<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">SSR mostly waits for backend responses (for example, GraphQL or REST APIs) and renders HTML \u2014 I\/O work that barely touches the CPU. But each in-flight request still holds response objects, VNode trees, and serialization buffers in memory. Under load, dozens of concurrent requests holding a few hundred kilobytes each add up quickly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This means:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Over-provisioning CPU wastes money<\/strong> \u2014 you pay for compute that sits idle  <\/li>\n<li><strong>Under-provisioning memory crashes the server<\/strong> \u2014 V8 heap exhaustion triggers cascading failures  <\/li>\n<li><strong>A good starting ratio is roughly 1 vCPU : 2 GiB RAM<\/strong> for SSR workloads<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Right-Sizing Experiment<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In a representative production-like environment, you can right-size Node.js SSR containers by running load tests with different resource configurations:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Configuration<\/th><th>Result<\/th><\/tr><\/thead><tbody><tr><td>4 vCPU \/ 8 GiB<\/td><td>Stable but over-provisioned<\/td><\/tr><tr><td>2 vCPU \/ 4 GiB<\/td><td>Stable and efficient \u2713<\/td><\/tr><tr><td>1 vCPU \/ 2 GiB<\/td><td><strong>Cascading failures<\/strong><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">At 1 vCPU \/ 2 GiB, workers ran at 1,791 MB out of 2,048 MB \u2014 V8 was at its ceiling. Health probes timed out because the event loop was blocked by GC. The orchestrator restarted replicas, but cold-starting Nuxt takes several seconds because V8 must compile the application. During that window, the remaining replicas were overloaded, which caused <em>them<\/em> to fail health checks. The cascade continued until manual intervention.<\/p>\n\n\n\n<pre class=\"mermaid\">sequenceDiagram\n    participant R1 as Replica 1\n    participant R2 as Replica 2\n    participant R3 as Replica 3\n    participant O as Orchestrator\n\n    Note over R1: Memory 1791\/2048 MB&lt;br\/&gt;GC stalls&lt;br\/&gt;Health probe timeout\n    O-&gt;&gt;R1: Mark unhealthy\n    O-&gt;&gt;R1: Restart replica\n    Note over R1: Cold start (~3s)&lt;br\/&gt;No traffic handling\n\n    Note over R2: Now handling 2\u00d7 traffic&lt;br\/&gt;Memory spike\n    O-&gt;&gt;R2: Health probe timeout\n    O-&gt;&gt;R2: Restart replica\n\n    Note over R3: Now handling 3\u00d7 traffic&lt;br\/&gt;Immediate failure\n    O-&gt;&gt;R3: Restart replica\n\n    Note over R1,R3: All replicas restarting&lt;br\/&gt;Zero capacity for ~10 seconds<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In practice, the minimum viable per-replica compute for V8 startup plus Nuxt SSR in such an environment is about 2 vCPU \/ 4 GiB. Going below that introduces a cascading failure risk that replica count alone cannot absorb.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Minimum Replicas: Preventing Cold-Start Cascades<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Even with correctly sized replicas, starting from too few creates problems under load. The orchestrator can launch new replicas, but each one needs time to start, compile, and begin accepting requests.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, with 2 replicas scaling to 15, the first traffic burst hits only 2 instances. They overload while new replicas spin up. By the time those are ready, the original 2 may already have failed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix is to <strong>set <code>minReplicas<\/code> high enough to handle average production traffic without scaling out<\/strong>. In a typical large-scale web application, values might look like this:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Service<\/th><th>minReplicas<\/th><th>maxReplicas<\/th><th>Reasoning<\/th><\/tr><\/thead><tbody><tr><td>SSR SPA<\/td><td>5<\/td><td>20<\/td><td>Handles page rendering (heaviest)<\/td><\/tr><tr><td>API<\/td><td>3<\/td><td>20<\/td><td>Handles business logic (lighter)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n    TRAF[Average production traffic] --&gt;|First burst| R5[5 pre-warmed SSR SPA replicas]\n    R5 --&gt; CAP[\"Within capacity&lt;br\/&gt;No scale-out needed\"]\n\n    TRAF --&gt;|Genuine spike| SO[Autoscaler triggers scale-out]\n    SO --&gt; N[New replicas starting\\nNuxt compile + V8 startup]\n    R5 --&gt; BUF[Existing 5 replicas buffer traffic]\n    N --&gt; READY[New replicas ready\\nTraffic distributed]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">At 5 pre-warmed SPA replicas, normal production traffic stays within capacity and does not trigger scaling. Scale-out only activates for genuine spikes, and the existing 5 replicas buffer traffic while new ones start.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Health Monitoring<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The application exposes a health endpoint that returns per-worker metrics, enabling the orchestrator and internal tools to see exactly what PM2 workers are doing:<\/p>\n\n\n\n<pre><code class=\"language-http\">GET \/api\/health\/pm2\nResponse:\n{\n  \"workers\": [\n    {\n      \"id\": 0,\n      \"cpu\": 8.2,\n      \"memory\": 1234567890,\n      \"restarts\": 3,\n      \"uptime\": 86400000,\n      \"status\": \"online\"\n    },\n    {\n      \"id\": 1,\n      \"cpu\": 5.1,\n      \"memory\": 987654321,\n      \"restarts\": 1,\n      \"uptime\": 72000000,\n      \"status\": \"online\"\n    }\n  ]\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The endpoint is protected by an internal API guard \u2014 it returns 404 for any caller that is not a health probe or internal service with the correct authorization header. External callers cannot even discover that it exists.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Validated Configuration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">After extensive load testing in a realistic production scenario, a configuration like the following has proven to pass all thresholds:<\/p>\n\n\n\n<pre><code class=\"language-text\">Per Container:\n  CPU:     2 vCPU\n  Memory:  4 GiB\n  PM2:     2 workers per container\n  V8:      --max-old-space-size=1536 per worker\n\nScaling:\n  SPA: min 5, max 20 replicas\n  API: min 3, max 20 replicas\n\nResult at 6\u00d7 production load:\n  Median response time: 165 ms\n  Error rate: 0.82%\n  CPU peak: 12% of allocation\n  Memory peak: 60% of allocation<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n    subgraph PC[Per Container]\n        CPU[CPU: 2 vCPU]\n        MEM[Memory: 4 GiB]\n        PM2W[PM2: 2 workers per container]\n        V8[V8: --max-old-space-size=1536 per worker]\n    end\n\n    subgraph SC[Scaling]\n        SPA[SPA: min 5, max 20 replicas]\n        API[API: min 3, max 20 replicas]\n    end\n\n    subgraph RES[Result at 6\u00d7 production load]\n        RT[Median response time: 165 ms]\n        ER[Error rate: 0.82%]\n        CPUU[CPU peak: 12% of allocation]\n        MEMU[Memory peak: 60% of allocation]\n    end\n\n    PC --&gt; SC --&gt; RES<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Lessons Learned<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Node.js is not a &#8220;fire and forget&#8221; runtime<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Unlike compiled languages with deterministic memory management, Node.js requires active memory management for long-lived processes. V8 heap caps, PM2 restarts, and minimum replica sizing are not optimizations \u2014 they are necessities.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Size for memory, not CPU<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">SSR workloads are I\/O-bound. The CPU spends most of its time waiting for backend responses. Provision memory generously and CPU conservatively. A 1:2 vCPU:GiB ratio is a solid starting point.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Cold starts are the hidden enemy of auto-scaling<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Auto-scaling sounds effortless until you realize new replicas take several seconds to become productive. During that window, existing replicas have to absorb the load. If they cannot, cascading failures follow. Adequate <code>minReplicas<\/code> removes that risk.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Load test the validated configuration, not the ideal one<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">It is tempting to load test with generous resources and right-size later. But right-sizing can expose failure modes that do not exist at larger sizes. Always load test the <strong>production configuration<\/strong>, not a more generous version.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What&#8217;s Next<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Article 11<\/strong>: <em>Multi-Environment Infrastructure \u2014 Azure Container Apps and the Configuration System<\/em> \u2014 Managing three environments with generated configuration.  <\/li>\n<li><strong>Article 12<\/strong>: <em>Security in a Nuxt SSR App \u2014 CSRF, Azure AD, CSP, and More<\/em> \u2014 The security layers that protect a server-rendered application.  <\/li>\n<li><strong>Article 13<\/strong>: <em>Observability and Distributed Tracing \u2014 Application Insights End-to-End<\/em> \u2014 How every request is traced from the reverse proxy through the application to the backend.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Munir Husseini is a software architect specializing in full-stack TypeScript, .NET, and cloud-native architectures.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Seventeenth in a series about migrating from legacy architectures to a modern Nuxt 4 stack. The Inconvenient Truth About Node.js Servers Node.js is optimized for event-driven I\/O, not for long-lived servers that render thousands of pages per hour. Over time, the V8 heap grows and objects such as GraphQL responses, Vue server renderer allocations, cached [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":242,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-23","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-advanced-web-app-with-nuxt-and-net"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server - Scalable Web Production<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/softwareproduction.eu\/?p=23\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server - Scalable Web Production\" \/>\n<meta property=\"og:description\" content=\"Seventeenth in a series about migrating from legacy architectures to a modern Nuxt 4 stack. The Inconvenient Truth About Node.js Servers Node.js is optimized for event-driven I\/O, not for long-lived servers that render thousands of pages per hour. Over time, the V8 heap grows and objects such as GraphQL responses, Vue server renderer allocations, cached [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/softwareproduction.eu\/?p=23\" \/>\n<meta property=\"og:site_name\" content=\"Scalable Web Production\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-06T21:48:50+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-06T23:45:23+00:00\" \/>\n<meta name=\"author\" content=\"Munir Husseini\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Munir Husseini\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23\"},\"author\":{\"name\":\"Munir Husseini\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\"},\"headline\":\"Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server\",\"datePublished\":\"2026-06-06T21:48:50+00:00\",\"dateModified\":\"2026-06-06T23:45:23+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23\"},\"wordCount\":1047,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/17-memory-stability-pm2.jpg\",\"articleSection\":[\"Advanced Web App with Nuxt and .NET\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=23#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23\",\"name\":\"Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server - Scalable Web Production\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/17-memory-stability-pm2.jpg\",\"datePublished\":\"2026-06-06T21:48:50+00:00\",\"dateModified\":\"2026-06-06T23:45:23+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=23\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23#primaryimage\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/17-memory-stability-pm2.jpg\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/17-memory-stability-pm2.jpg\",\"width\":1880,\"height\":1253},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=23#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/softwareproduction.eu\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"name\":\"Softwareproduction\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/softwareproduction.eu\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\",\"name\":\"Munir Husseini\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"width\":32,\"height\":32,\"caption\":\"Munir Husseini\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\",\"name\":\"Munir Husseini\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"caption\":\"Munir Husseini\"},\"sameAs\":[\"https:\\\/\\\/softwareproduction.eu\\\/\"],\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server - Scalable Web Production","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/softwareproduction.eu\/?p=23","og_locale":"en_US","og_type":"article","og_title":"Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server - Scalable Web Production","og_description":"Seventeenth in a series about migrating from legacy architectures to a modern Nuxt 4 stack. The Inconvenient Truth About Node.js Servers Node.js is optimized for event-driven I\/O, not for long-lived servers that render thousands of pages per hour. Over time, the V8 heap grows and objects such as GraphQL responses, Vue server renderer allocations, cached [&hellip;]","og_url":"https:\/\/softwareproduction.eu\/?p=23","og_site_name":"Scalable Web Production","article_published_time":"2026-06-06T21:48:50+00:00","article_modified_time":"2026-06-06T23:45:23+00:00","author":"Munir Husseini","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Munir Husseini","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/softwareproduction.eu\/?p=23#article","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/?p=23"},"author":{"name":"Munir Husseini","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f"},"headline":"Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server","datePublished":"2026-06-06T21:48:50+00:00","dateModified":"2026-06-06T23:45:23+00:00","mainEntityOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=23"},"wordCount":1047,"commentCount":0,"publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=23#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/17-memory-stability-pm2.jpg","articleSection":["Advanced Web App with Nuxt and .NET"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/softwareproduction.eu\/?p=23#respond"]}]},{"@type":"WebPage","@id":"https:\/\/softwareproduction.eu\/?p=23","url":"https:\/\/softwareproduction.eu\/?p=23","name":"Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server - Scalable Web Production","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/#website"},"primaryImageOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=23#primaryimage"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=23#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/17-memory-stability-pm2.jpg","datePublished":"2026-06-06T21:48:50+00:00","dateModified":"2026-06-06T23:45:23+00:00","breadcrumb":{"@id":"https:\/\/softwareproduction.eu\/?p=23#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/softwareproduction.eu\/?p=23"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/?p=23#primaryimage","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/17-memory-stability-pm2.jpg","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/17-memory-stability-pm2.jpg","width":1880,"height":1253},{"@type":"BreadcrumbList","@id":"https:\/\/softwareproduction.eu\/?p=23#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/softwareproduction.eu\/"},{"@type":"ListItem","position":2,"name":"Memory, Stability, and PM2 \u2014 Running a Long-Lived Node.js Server"}]},{"@type":"WebSite","@id":"https:\/\/softwareproduction.eu\/#website","url":"https:\/\/softwareproduction.eu\/","name":"Softwareproduction","description":"","publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/softwareproduction.eu\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/softwareproduction.eu\/#organization","name":"Munir Husseini","url":"https:\/\/softwareproduction.eu\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","width":32,"height":32,"caption":"Munir Husseini"},"image":{"@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f","name":"Munir Husseini","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","caption":"Munir Husseini"},"sameAs":["https:\/\/softwareproduction.eu\/"],"url":"https:\/\/softwareproduction.eu\/?author=1"}]}},"jetpack_featured_media_url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/17-memory-stability-pm2.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/23","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=23"}],"version-history":[{"count":10,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/23\/revisions"}],"predecessor-version":[{"id":243,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/23\/revisions\/243"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/media\/242"}],"wp:attachment":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=23"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=23"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=23"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}