{"id":82977,"date":"2026-05-14T16:51:31","date_gmt":"2026-05-14T20:51:31","guid":{"rendered":"https:\/\/www.inmotionhosting.com\/blog\/?p=82977"},"modified":"2026-05-15T16:48:33","modified_gmt":"2026-05-15T20:48:33","slug":"nodejs-performance-optimization-production-vps-hosting","status":"publish","type":"post","link":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/","title":{"rendered":"Node.js Performance Optimization for Production VPS Hosting"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"538\" src=\"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting-1024x538.png\" alt=\"Node.js Performance Optimization for Production VPS Hosting - Hero Image\" class=\"wp-image-82978\" srcset=\"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting-1024x538.png 1024w, https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting-300x158.png 300w, https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting-768x403.png 768w, https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting.png 1200w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n<div class=\"wp-block-post-excerpt\"><p class=\"wp-block-post-excerpt__excerpt\">Node.js performance problems in production almost always come from the same short list: a single-process app pinned to one CPU core, missing caching layers, an event loop blocked by synchronous work, and a process manager that crashes silently at 2 a.m. <\/p><\/div>\n\n\n<p>This article covers the tuning that actually moves p95 latency and concurrent request capacity on a production VPS, with the configuration patterns that hold up at real traffic.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why Node.js Performance Tuning Matters on a Production VPS<\/h2>\n\n\n\n<p>A VPS gives you the root access, dedicated vCPU allocation, and persistent process control that shared hosting cannot, which is exactly what a <a href=\"https:\/\/www.inmotionhosting.com\/blog\/node-js-hosting-guide\/\">long-running Node.js process<\/a> needs. The default node server.js setup runs your application as a single process on a single thread, so a 4-vCPU VPS running an untuned app uses roughly 25% of the hardware you are paying for. Tuning closes that gap.<\/p>\n\n\n\n<p>The other reason to tune at the VPS layer is that Node.js is single-threaded for application code by design. The runtime uses <a href=\"https:\/\/nodejs.org\/en\/learn\/asynchronous-work\/event-loop-timers-and-nexttick\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">an event loop and a libuv thread pool to handle I\/O<\/a>, but any CPU-bound work you write still blocks every request on that worker. Production tuning is mostly about getting CPU-bound work off the event loop. It also involves putting cheaper layers in front of Node so the runtime only handles what it must.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What Are the Most Common Node.js Performance Bottlenecks?<\/h2>\n\n\n\n<p>Real production issues cluster around a handful of root causes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Single-process deployments. <\/strong>One Node process cannot use more than one CPU core for application code, so a multi-core VPS sits idle under load.<\/li>\n\n\n\n<li><strong>Blocked event loop. <\/strong>Synchronous file reads, JSON.parse on large payloads, bcrypt hashing on the main thread, or unbounded regex stall every concurrent request.<\/li>\n\n\n\n<li><strong>Memory leaks from retained references. <\/strong>Long-lived closures, growing in-memory caches without eviction, and event listeners attached without cleanup quietly push heap usage past the default 1.5 GB ceiling.<\/li>\n\n\n\n<li><strong>No HTTP-level caching. <\/strong>Every request hits application code, even for responses that change once an hour.<\/li>\n\n\n\n<li><strong>Direct exposure to the internet. <\/strong>Running node on port 80 or 443 without Nginx in front leaves TLS termination, static file serving, and slow-client buffering to your application.<\/li>\n\n\n\n<li><strong>Database round trips on the hot path. <\/strong>Missing indexes and N+1 queries show up as Node performance problems even though the actual time is spent waiting on the database.<\/li>\n<\/ul>\n\n\n\n<p>Knowing which one you have requires measurement, not guessing. Start by checking event loop lag and heap usage before changing anything.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Do You Set the Right Node.js Cluster and Worker Count?<\/h2>\n\n\n\n<p>The cluster pattern runs one Node process per CPU core, with a master process distributing connections to workers. The <a href=\"https:\/\/nodejs.org\/api\/cluster.html\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Node.js cluster module is built into the runtime<\/a> and is the foundation that PM2 and most process managers use under the hood.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1478\" height=\"1000\" src=\"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/node-js-vps-architecture-diagram.png\" alt=\"Node.js VPS architecture diagram\" class=\"wp-image-83003\" srcset=\"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/node-js-vps-architecture-diagram.png 1478w, https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/node-js-vps-architecture-diagram-300x203.png 300w, https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/node-js-vps-architecture-diagram-1024x693.png 1024w, https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/node-js-vps-architecture-diagram-768x520.png 768w\" sizes=\"auto, (max-width: 1478px) 100vw, 1478px\" \/><\/figure>\n\n\n\n<p>The general rule:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>CPU-bound or balanced workloads: <\/strong>workers = number of vCPUs. On a 4-vCPU VPS, run 4 workers.<\/li>\n\n\n\n<li><strong>I\/O-heavy workloads: <\/strong>workers = vCPUs is still the right starting point. Adding more rarely helps, because the bottleneck is the database or external API, not Node.<\/li>\n\n\n\n<li><strong>Memory-constrained VPS plans: <\/strong>workers = floor(available RAM \/ per-worker heap). If each worker holds 400MB of heap and you have 2GB free after the OS, four workers is the ceiling regardless of core count.<\/li>\n<\/ul>\n\n\n\n<p>With PM2 you set this declaratively:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#e1e4e8;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>pm2 start app.js -i max --name api<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark\" style=\"background-color: #24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F97583\">pm2<\/span><span style=\"color: #E1E4E8\"> start app.js -i<\/span><span style=\"color: #79B8FF\"> max<\/span><span style=\"color: #E1E4E8\"> --name api<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>The <code>-i max<\/code> flag spawns one worker per available core. Use a specific number, such as <code>-i 4<\/code>, when you want to leave headroom for a database or cache process on the same VPS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What PM2 and Process Manager Settings Improve Stability?<\/h2>\n\n\n\n<p>PM2 is the most common production process manager for Node, and the defaults are not the configuration you want at scale. A production-ready <code>ecosystem.config.js<\/code> looks closer to this:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#e1e4e8;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>module.exports = {\n  apps: &#91;{\n    name: 'api',\n    script: '.\/server.js',\n    instances: 'max',\n    exec_mode: 'cluster',\n    max_memory_restart: '500M',\n    node_args: '--max-old-space-size=460',\n    env_production: {\n      NODE_ENV: 'production',\n      PORT: 3000\n    },\n    error_file: '\/var\/log\/pm2\/api-err.log',\n    out_file: '\/var\/log\/pm2\/api-out.log',\n    merge_logs: true,\n    time: true\n  }&#93;\n};<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark\" style=\"background-color: #24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #E1E4E8\">module.<\/span><span style=\"color: #F97583\">exports<\/span><span style=\"color: #E1E4E8\"> = {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">  apps: &#91;{<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    name: <\/span><span style=\"color: #F97583\">&#39;api&#39;<\/span><span style=\"color: #E1E4E8\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    script: <\/span><span style=\"color: #9ECBFF\">&#39;.\/server.js&#39;<\/span><span style=\"color: #E1E4E8\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    instances: <\/span><span style=\"color: #9ECBFF\">&#39;max&#39;<\/span><span style=\"color: #E1E4E8\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    exec_mode: <\/span><span style=\"color: #9ECBFF\">&#39;cluster&#39;<\/span><span style=\"color: #E1E4E8\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    max_memory_restart: <\/span><span style=\"color: #9ECBFF\">&#39;500M&#39;<\/span><span style=\"color: #E1E4E8\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    node_args: <\/span><span style=\"color: #9ECBFF\">&#39;--max-old-space-size=460&#39;<\/span><span style=\"color: #E1E4E8\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    env_production: {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">      NODE_ENV: <\/span><span style=\"color: #9ECBFF\">&#39;production&#39;<\/span><span style=\"color: #E1E4E8\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">      PORT: 3000<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    },<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    error_file: <\/span><span style=\"color: #9ECBFF\">&#39;\/var\/log\/pm2\/api-err.log&#39;<\/span><span style=\"color: #E1E4E8\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    out_file: <\/span><span style=\"color: #9ECBFF\">&#39;\/var\/log\/pm2\/api-out.log&#39;<\/span><span style=\"color: #E1E4E8\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    merge_logs: true,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">    time: true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">  }&#93;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">};<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>A few details that matter in production:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>max_memory_restart<\/code> triggers a graceful restart before a worker hits the V8 heap limit and gets killed by the OS OOM killer. Set it 5 to 10% below <code>--max-old-space-size<\/code>.<\/li>\n\n\n\n<li><code>exec_mode: cluster<\/code> is what actually enables load balancing across workers. Fork mode runs independent processes without shared port binding.<\/li>\n\n\n\n<li><strong>Log rotation <\/strong>is not on by default. Install <code>pm2-logrotate<\/code> and set <code>pm2 set<\/code> <code>pm2-logrotate:max_size 50M<\/code> and <code>pm2 set pm2-logrotate:retain 14<\/code> so logs do not fill the disk during a traffic spike.<\/li>\n\n\n\n<li><strong>Startup persistence. <\/strong>Run <code>pm2 startup systemd<\/code> and <code>pm2 save<\/code> so workers come back automatically after a reboot or kernel update.<\/li>\n<\/ul>\n\n\n\n<p>For zero-downtime reloads on deploys, use <code>pm2 reload api<\/code> rather than <code>restart<\/code>. Reload swaps workers one at a time while keeping the cluster online.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Should You Configure Nginx as a Reverse Proxy for Node.js?<\/h2>\n\n\n\n<p>Putting Nginx in front of Node is the single most impactful change for most production deployments. Nginx handles TLS termination, static asset delivery, gzip and Brotli compression, request buffering for slow clients, and HTTP\/2 multiplexing, freeing Node to do only the work your application code requires.<\/p>\n\n\n\n<p>A minimal production server block:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#e1e4e8;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>uupstream node_api {\n\u00a0\u00a0\u00a0\u00a0server 127.0.0.1:3000;\n\u00a0\u00a0\u00a0\u00a0keepalive 64;\n}\n\u00a0\nserver {\n\u00a0\u00a0\u00a0\u00a0listen 443 ssl http2;\n\u00a0\u00a0\u00a0\u00a0server_name api.example.com;\n\u00a0\n\u00a0\u00a0\u00a0\u00a0ssl_certificate \u00a0 \u00a0 \/etc\/letsencrypt\/live\/api.example.com\/fullchain.pem;\n\u00a0\u00a0\u00a0\u00a0ssl_certificate_key \/etc\/letsencrypt\/live\/api.example.com\/privkey.pem;\n\u00a0\n\u00a0\u00a0\u00a0\u00a0gzip on;\n\u00a0\u00a0\u00a0\u00a0gzip_types application\/json text\/css application\/javascript;\n\u00a0\n\u00a0\u00a0\u00a0\u00a0location \/static\/ {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0alias \/var\/www\/api\/public\/;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0expires 30d;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0add_header Cache-Control \"public, immutable\";\n\u00a0\u00a0\u00a0\u00a0}\n\u00a0\n\u00a0\u00a0\u00a0\u00a0location \/ {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0proxy_pass http:\/\/node_api;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0proxy_http_version 1.1;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0proxy_set_header Connection \"\";\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0proxy_set_header Host $host;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0proxy_set_header X-Real-IP $remote_addr;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0proxy_set_header X-Forwarded-Proto $scheme;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0proxy_read_timeout 60s;\n\u00a0\u00a0\u00a0\u00a0}\n}<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark\" style=\"background-color: #24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F97583\">uupstream<\/span><span style=\"color: #E1E4E8\"> node_api {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">server<\/span><span style=\"color: #E1E4E8\"> 127.0.0.1:3000;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0keepalive <\/span><span style=\"color: #E1E4E8\">64;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F97583\">server<\/span><span style=\"color: #E1E4E8\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0listen <\/span><span style=\"color: #E1E4E8\">443 ssl http2;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0server_name <\/span><span style=\"color: #E1E4E8\">api.example.com;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0ssl_certificate <\/span><span style=\"color: #E1E4E8\">\u00a0 \u00a0 \/etc\/letsencrypt\/live\/api.example.com\/fullchain.pem;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0ssl_certificate_key <\/span><span style=\"color: #E1E4E8\">\/etc\/letsencrypt\/live\/api.example.com\/privkey.pem;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0gzip <\/span><span style=\"color: #E1E4E8\">on;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0gzip_types <\/span><span style=\"color: #E1E4E8\">application\/json text\/css application\/javascript;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">location<\/span><span style=\"color: #E1E4E8\"> <\/span><span style=\"color: #B392F0\">\/static\/ <\/span><span style=\"color: #E1E4E8\">{<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0alias <\/span><span style=\"color: #E1E4E8\">\/var\/www\/api\/public\/;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0expires <\/span><span style=\"color: #E1E4E8\">30d;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0add_header <\/span><span style=\"color: #E1E4E8\">Cache-Control <\/span><span style=\"color: #9ECBFF\">&quot;public, immutable&quot;<\/span><span style=\"color: #E1E4E8\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">location<\/span><span style=\"color: #E1E4E8\"> <\/span><span style=\"color: #B392F0\">\/ <\/span><span style=\"color: #E1E4E8\">{<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0proxy_pass <\/span><span style=\"color: #E1E4E8\">http:\/\/node_api;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0proxy_http_version <\/span><span style=\"color: #E1E4E8\">1.1;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0proxy_set_header <\/span><span style=\"color: #E1E4E8\">Connection <\/span><span style=\"color: #9ECBFF\">&quot;&quot;<\/span><span style=\"color: #E1E4E8\">;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0proxy_set_header <\/span><span style=\"color: #E1E4E8\">Host $host;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0proxy_set_header <\/span><span style=\"color: #E1E4E8\">X-Real-IP $remote_addr;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0proxy_set_header <\/span><span style=\"color: #E1E4E8\">X-Forwarded-For $proxy_add_x_forwarded_for;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0proxy_set_header <\/span><span style=\"color: #E1E4E8\">X-Forwarded-Proto $scheme;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span><span style=\"color: #F97583\">\u00a0proxy_read_timeout <\/span><span style=\"color: #E1E4E8\">60s;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">\u00a0\u00a0\u00a0\u00a0}<\/span><\/span>\n<span class=\"line\"><span style=\"color: #E1E4E8\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>Two details developers miss most often: setting <code>proxy_http_version 1.1<\/code> plus the empty <code>Connection <\/code>header enables connection reuse from the <a href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html#keepalive\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">upstream keepalive pool<\/a>, which dramatically reduces TCP handshake overhead under load. Serving <code>\/static\/<\/code> directly from Nginx with long <code>Cache-Control<\/code> headers also pulls thousands of requests per minute off your Node workers for files they should never have been touching.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What Memory and Garbage Collection Flags Should You Tune?<\/h2>\n\n\n\n<p>Node uses V8 under the hood, and V8&#8217;s default old-generation heap size is roughly 1.5GB on 64-bit systems regardless of how much RAM the VPS actually has. On a 4GB VPS running four workers, that default leaves about 10GB of theoretical heap capacity you cannot use because each worker caps itself.<\/p>\n\n\n\n<p>The flag to set is <code>--max-old-space-size<\/code>, expressed in megabytes:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span role=\"button\" tabindex=\"0\" style=\"color:#e1e4e8;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>node --max-old-space-size=460 server.js<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki github-dark\" style=\"background-color: #24292e\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F97583\">node<\/span><span style=\"color: #E1E4E8\"> --max-old-space-size=460 server.js<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>Sizing guidance:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Reserve roughly 25% of total RAM for the OS, Nginx, and any database or cache running on the same VPS.<\/strong><\/li>\n\n\n\n<li><strong>Divide the rest by your worker count, then subtract 10% for V8 overhead. <\/strong>On a 2GB VPS with 4 workers, that math lands around 460MB per worker.<\/li>\n\n\n\n<li><strong>Match <\/strong><code>max_memory_restart<\/code><strong> in PM2 to this value or slightly below. <\/strong>A worker restarted by PM2 is recoverable; one killed by the kernel OOM killer is not.<\/li>\n<\/ul>\n\n\n\n<p>For very high-throughput services, additional flags worth testing include <code>--max-semi-space-size<\/code> to give the young generation more room (reducing minor GC frequency on services that allocate aggressively) and <code>--no-compilation-cache<\/code> if you are seeing memory pressure from cached compiled code in short-lived workers. Test changes under load before committing them to production.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Do You Profile a Slow Node.js Application?<\/h2>\n\n\n\n<p>Most performance work fails because the engineer optimized the wrong thing. Profile first, then change code:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>node --inspect server.js<\/code> with Chrome DevTools gives you a flame graph of CPU time and a heap snapshot tool for finding retained objects. The DevTools Performance tab is the fastest path to identifying a blocked event loop.<\/li>\n\n\n\n<li><code>clinic doctor<\/code> (<a href=\"https:\/\/clinicjs.org\/doctor\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">clinicjs.org<\/a>) runs your app under load and produces a diagnosis. It is especially good at flagging event loop delay and excessive GC pressure before you dig deeper.<\/li>\n\n\n\n<li><code>autocannon<\/code> is the load generator most Node developers reach for. A baseline benchmark before any tuning gives you the comparison point you need to know if your changes helped or hurt.<\/li>\n\n\n\n<li><strong>Event loop lag monitoring in production <\/strong>belongs in your APM or a simple <code>perf_hooks.monitorEventLoopDelay()<\/code> exporter to Prometheus. Lag above 50 ms under steady load is a signal that something synchronous is blocking workers.<\/li>\n<\/ul>\n\n\n\n<p>If a single endpoint is slow, time the database query separately from the handler. The Node profiler will point at <code>await pool.query(...)<\/code> as the slow line, but the work is happening in PostgreSQL or MySQL, not in your code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Which Caching Layers Make the Biggest Difference?<\/h2>\n\n\n\n<p>Caching is the highest-ROI optimization most teams skip. Three layers matter for Node.js production workloads:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Application-level caching with Redis. <\/strong>Move session storage, rate-limit counters, and frequently accessed query results out of the database into Redis on the same VPS or a private network neighbor. A round trip to local Redis is sub-millisecond; the same query against PostgreSQL on cold cache might be 20 to 80 ms.<\/li>\n\n\n\n<li><strong>HTTP response caching at Nginx. <\/strong>For endpoints that return identical responses for the same URL, <code>proxy_cache<\/code> in Nginx can serve thousands of requests per second from disk without ever touching Node. Even a 10-second cache window on a popular endpoint cuts upstream load dramatically.<\/li>\n\n\n\n<li><strong>CDN in front of your VPS. <\/strong><a href=\"https:\/\/www.inmotionhosting.com\/blog\/top-cdn-providers\/\" type=\"post\" id=\"79009\">Cloudflare, Bunny<\/a>, or any reverse proxy CDN absorbs static asset traffic, terminates TLS at the edge, and shields the origin from bot traffic. For globally distributed users, the latency improvement is usually larger than any application-level tuning.<\/li>\n<\/ul>\n\n\n\n<p>The order to add them is the order listed. Redis first because it changes how your application is structured. Nginx caching second because it requires no code changes, and a CDN third because it benefits even an untuned app.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Do You Secure a Production Node.js VPS?<\/h2>\n\n\n\n<p>Performance and security overlap more than developers expect, because an exposed application is one botnet scan away from being unavailable. Baseline hardening for a Node.js VPS:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Run Node as a non-root user. <\/strong>Use <code>setcap 'cap_net_bind_service=+ep' $(which node)<\/code> if you need to bind to ports below 1024 without root, or terminate at Nginx and let Node listen on 3000.<\/li>\n\n\n\n<li><strong>Configure a host firewall. <\/strong>UFW on Ubuntu or <code>firewalld<\/code> on AlmaLinux locks the server down to only the ports you intentionally expose, typically 22, 80, and 443.<\/li>\n\n\n\n<li><strong>Keep dependencies patched. <\/strong><code>npm audit<\/code> in CI and Dependabot or Renovate on the repository catch CVEs in transitive dependencies before they reach production.<\/li>\n\n\n\n<li><strong>Set HTTP security headers. <\/strong><a href=\"https:\/\/helmetjs.github.io\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Helmet<\/a> is the standard Express middleware for headers like <code>Strict-Transport-Security<\/code>, <code>Content-Security-Policy<\/code>, and <code>X-Frame-Options<\/code>. Misconfigured headers are one of the more common findings in security audits.<\/li>\n\n\n\n<li><strong>Rotate secrets and use environment variables. <\/strong>Never commit .env files. Tools like Doppler, Vault, or even systemd <code>EnvironmentFile=<\/code> directives keep credentials out of the repository.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">When Should You Scale Beyond a Single VPS?<\/h2>\n\n\n\n<p>A well-tuned Node.js application on a 4 to 8 vCPU VPS with Nginx and Redis can comfortably serve millions of requests per day. Scaling horizontally usually becomes necessary for one of three reasons:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Sustained CPU usage above 70% across all workers, <\/strong>even after profiling and caching changes, indicates you have outgrown the box.<\/li>\n\n\n\n<li><strong>Tight uptime SLAs <\/strong>that cannot tolerate a single-host failure require at least two application VPS instances behind a load balancer.<\/li>\n\n\n\n<li><strong>Stateful resource separation <\/strong>becomes worth the operational cost when your database, cache, and application workloads start competing for the same disk I\/O or RAM on a shared VPS.<\/li>\n<\/ul>\n\n\n\n<p>InMotion&#8217;s <a href=\"https:\/\/www.inmotionhosting.com\/cloud-vps\">Cloud VPS plans<\/a> and <a href=\"https:\/\/www.inmotionhosting.com\/vps-hosting\">Managed VPS plans<\/a> both ship with full root access, dedicated vCPU allocation, and Linux distributions including AlmaLinux 9, Ubuntu 22.04 LTS, and Debian 12, which cover the runtime requirements for any current Node.js LTS release. The 99.99% uptime SLA and 24\/7 access to the APS team matter most at the point where your application has stopped being a side project and started carrying revenue.<\/p>\n\n\n\n<p>If you are running a production Node.js application on shared hosting or on a VPS that has not been tuned past the defaults, the changes in this article will likely cut p95 latency in half. They can also double sustainable request throughput before you spend another dollar on infrastructure. Start with PM2 cluster mode and Nginx in front, profile what is left, and add caching where the data supports it.<\/p>\n\n\n\n<p>Practical Node.js performance tuning for production VPS hosting, including clustering, PM2, Nginx reverse proxy, memory flags, and caching layers.<\/p>\n\n\n\n<p><strong>Ready to Run Node.js in Production?<\/strong> InMotion&#8217;s <a href=\"https:\/\/www.inmotionhosting.com\/vps-hosting\">Managed VPS plans<\/a> give you root access, dedicated vCPU allocation, and a choice of AlmaLinux 9, Ubuntu 22.04 LTS, or Debian 12. Backed by 24\/7 human support and a 99.99% uptime SLA.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><em><em>Node.js performance problems in production almost always come from the same short list: a single-process app pinned to one CPU core, missing caching layers, an event loop blocked by synchronous work, and a process manager that crashes silently at 2 a.m. <\/em><\/em><\/p>\n","protected":false},"author":116,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[319],"tags":[],"class_list":["post-82977","post","type-post","status-publish","format-standard","hentry","category-vps"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Node.js Performance Optimization on VPS | InMotion Hosting<\/title>\n<meta name=\"description\" content=\"Practical Node.js performance tuning for production VPS hosting, including clustering, PM2, Nginx reverse proxy, memory flags, and caching layers.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Node.js Performance Optimization on VPS | InMotion Hosting\" \/>\n<meta property=\"og:description\" content=\"Practical Node.js performance tuning for production VPS hosting, including clustering, PM2, Nginx reverse proxy, memory flags, and caching layers.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/\" \/>\n<meta property=\"og:site_name\" content=\"InMotion Hosting Blog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/inmotionhosting\" \/>\n<meta property=\"article:published_time\" content=\"2026-05-14T20:51:31+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-05-15T20:48:33+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Sam Page\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@inmotionhosting\" \/>\n<meta name=\"twitter:site\" content=\"@inmotionhosting\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Sam Page\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Node.js Performance Optimization on VPS | InMotion Hosting","description":"Practical Node.js performance tuning for production VPS hosting, including clustering, PM2, Nginx reverse proxy, memory flags, and caching layers.","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:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/","og_locale":"en_US","og_type":"article","og_title":"Node.js Performance Optimization on VPS | InMotion Hosting","og_description":"Practical Node.js performance tuning for production VPS hosting, including clustering, PM2, Nginx reverse proxy, memory flags, and caching layers.","og_url":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/","og_site_name":"InMotion Hosting Blog","article_publisher":"https:\/\/www.facebook.com\/inmotionhosting","article_published_time":"2026-05-14T20:51:31+00:00","article_modified_time":"2026-05-15T20:48:33+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting.png","type":"image\/png"}],"author":"Sam Page","twitter_card":"summary_large_image","twitter_creator":"@inmotionhosting","twitter_site":"@inmotionhosting","twitter_misc":{"Written by":"Sam Page","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"TechArticle","@id":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/#article","isPartOf":{"@id":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/"},"author":{"name":"Sam Page","@id":"https:\/\/www.inmotionhosting.com\/blog\/#\/schema\/person\/b459c4b748083c4f8431d5312e795796"},"headline":"Node.js Performance Optimization for Production VPS Hosting","datePublished":"2026-05-14T20:51:31+00:00","dateModified":"2026-05-15T20:48:33+00:00","mainEntityOfPage":{"@id":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/"},"wordCount":1824,"commentCount":0,"publisher":{"@id":"https:\/\/www.inmotionhosting.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting-1024x538.png","articleSection":["VPS Hosting Articles"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/","url":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/","name":"Node.js Performance Optimization on VPS | InMotion Hosting","isPartOf":{"@id":"https:\/\/www.inmotionhosting.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/#primaryimage"},"image":{"@id":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/#primaryimage"},"thumbnailUrl":"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting-1024x538.png","datePublished":"2026-05-14T20:51:31+00:00","dateModified":"2026-05-15T20:48:33+00:00","description":"Practical Node.js performance tuning for production VPS hosting, including clustering, PM2, Nginx reverse proxy, memory flags, and caching layers.","breadcrumb":{"@id":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/#primaryimage","url":"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting.png","contentUrl":"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2026\/05\/Node.js-Performance-Optimization-for-Production-VPS-Hosting.png","width":1200,"height":630},{"@type":"BreadcrumbList","@id":"https:\/\/www.inmotionhosting.com\/blog\/nodejs-performance-optimization-production-vps-hosting\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.inmotionhosting.com\/blog\/"},{"@type":"ListItem","position":2,"name":"VPS Hosting Articles","item":"https:\/\/www.inmotionhosting.com\/blog\/vps\/"},{"@type":"ListItem","position":3,"name":"Node.js Performance Optimization for Production VPS Hosting"}]},{"@type":"WebSite","@id":"https:\/\/www.inmotionhosting.com\/blog\/#website","url":"https:\/\/www.inmotionhosting.com\/blog\/","name":"InMotion Hosting Blog","description":"Web Hosting Strategy, Trends and Security","publisher":{"@id":"https:\/\/www.inmotionhosting.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.inmotionhosting.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.inmotionhosting.com\/blog\/#organization","name":"InMotion Hosting","url":"https:\/\/www.inmotionhosting.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.inmotionhosting.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2019\/11\/imh-logo-all-colors-big.jpg","contentUrl":"https:\/\/www.inmotionhosting.com\/blog\/wp-content\/uploads\/2019\/11\/imh-logo-all-colors-big.jpg","width":1630,"height":430,"caption":"InMotion Hosting"},"image":{"@id":"https:\/\/www.inmotionhosting.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/inmotionhosting","https:\/\/x.com\/inmotionhosting"]},{"@type":"Person","@id":"https:\/\/www.inmotionhosting.com\/blog\/#\/schema\/person\/b459c4b748083c4f8431d5312e795796","name":"Sam Page","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/35c230f33cd7aacf52f0f53bc02230a2ee7840b5b221af549d491ab98f65a363?s=96&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/35c230f33cd7aacf52f0f53bc02230a2ee7840b5b221af549d491ab98f65a363?s=96&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/35c230f33cd7aacf52f0f53bc02230a2ee7840b5b221af549d491ab98f65a363?s=96&r=g","caption":"Sam Page"},"url":"https:\/\/www.inmotionhosting.com\/blog\/author\/samp\/"}]}},"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"primary_category":{"id":319,"name":"VPS Hosting Articles","slug":"vps","link":"https:\/\/www.inmotionhosting.com\/blog\/vps\/"},"_links":{"self":[{"href":"https:\/\/www.inmotionhosting.com\/blog\/wp-json\/wp\/v2\/posts\/82977","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.inmotionhosting.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.inmotionhosting.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.inmotionhosting.com\/blog\/wp-json\/wp\/v2\/users\/116"}],"replies":[{"embeddable":true,"href":"https:\/\/www.inmotionhosting.com\/blog\/wp-json\/wp\/v2\/comments?post=82977"}],"version-history":[{"count":7,"href":"https:\/\/www.inmotionhosting.com\/blog\/wp-json\/wp\/v2\/posts\/82977\/revisions"}],"predecessor-version":[{"id":83004,"href":"https:\/\/www.inmotionhosting.com\/blog\/wp-json\/wp\/v2\/posts\/82977\/revisions\/83004"}],"wp:attachment":[{"href":"https:\/\/www.inmotionhosting.com\/blog\/wp-json\/wp\/v2\/media?parent=82977"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.inmotionhosting.com\/blog\/wp-json\/wp\/v2\/categories?post=82977"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.inmotionhosting.com\/blog\/wp-json\/wp\/v2\/tags?post=82977"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}