{"id":9791,"date":"2026-05-18T07:56:27","date_gmt":"2026-05-18T07:56:27","guid":{"rendered":"https:\/\/cloudminister.com\/blog\/?p=9791"},"modified":"2026-05-21T06:44:02","modified_gmt":"2026-05-21T06:44:02","slug":"deploy-your-node-js-app-on-linux","status":"publish","type":"post","link":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/","title":{"rendered":"How to Deploy Node.js on Linux: Complete Production Guide 2026"},"content":{"rendered":"<p>Node.js powers over 6.3 million websites worldwide and remains the go-to runtime for building scalable APIs, real-time applications, and microservices in 2026. With Node.js v22 LTS (codename &#8220;Iron&#8221;) now the current long-term support release, and Ubuntu 24.04 LTS (Noble Numbat) as the dominant Linux server OS, deploying Node.js has never been more streamlined \u2014 but only when you follow the right steps.<\/p>\n<p>Skipping a process manager, running your app without a reverse proxy, or exposing your Node.js port directly to the internet are mistakes that can bring a production server down within hours. This guide walks you through every step correctly \u2014 from server setup to <a href=\"https:\/\/cloudminister.com\/devops-services\/\">CI\/CD automation<\/a> \u2014 so your deployment is secure, stable, and ready to scale.<\/p>\n<p>By the end of this guide, you will have a fully working Node.js application running on a Linux server with PM2 process management, Nginx reverse proxy, Let&#8217;s Encrypt SSL, UFW firewall protection, and an automated GitHub Actions deployment pipeline.<\/p>\n<h2><strong>Before You Begin \u2014 Prerequisites<\/strong><\/h2>\n<p>Make sure you have the following ready before starting:<\/p>\n<p>\u2022 A Linux server running Ubuntu 24.04 LTS (recommended) or 22.04 LTS minimum<br \/>\u2022 SSH access to the server with sudo privileges<br \/>\u2022 A domain name pointed to your server&#8217;s public IP address (needed for SSL)<br \/>\u2022 Your Node.js application code \u2014 either locally or hosted on a GitHub repository<br \/>\u2022 Basic familiarity with the Linux terminal and command line<\/p>\n<h3>What you will build by the end of this guide:<\/h3>\n<p>A production-grade Node.js deployment with PM2 cluster mode, Nginx reverse proxy, HTTPS via Let&#8217;s Encrypt, UFW firewall, Fail2ban brute-force protection, and GitHub Actions CI\/CD.<\/p>\n<p>\u00a0<\/p>\n<h2><strong>1. Prepare Your Node.js Application for Production<\/strong><\/h2>\n<p>Before touching your server, your application code must be production-ready. Here is what to check and set up.<\/p>\n<p>Getting Your Code Ready<\/p>\n<p>Go through your codebase and:<br \/>\u2022 Remove unused npm packages from package.json<br \/>\u2022 Optimise database queries and add indexes where needed<br \/>\u2022 Implement proper error handling \u2014 unhandled exceptions crash PM2 workers<br \/>\u2022 Make sure all sensitive values (API keys, database URLs, secrets) are in environment variables, never hardcoded<\/p>\n<p>Setting Up Environment Variables with dotenv<\/p>\n<p>Environment variables keep sensitive data out of your code and out of version control.<\/p>\n<h3>Step 1 \u2014 Install the dotenv package:<\/h3>\n<p>npm install dotenv<\/p>\n<h3>Step 2 \u2014 Create a .env file in your project root:<\/h3>\n<p>PORT=3000<br \/>DB_URL=mongodb:\/\/localhost:27017\/mydb<br \/>SECRET_KEY=your_secret_key_here<br \/>NODE_ENV=production<\/p>\n<h3>Step 3 \u2014 Load it at the very top of your main file (server.js or app.js):<\/h3>\n<p>require(&#8216;dotenv&#8217;).config();<br \/>const port = process.env.PORT || 3000;<br \/>const dbUrl = process.env.DB_URL;<\/p>\n<h3>Step 4 \u2014 Add .env to your .gitignore file so it never gets pushed to GitHub:<\/h3>\n<p>echo &#8220;.env&#8221; &gt;&gt; .gitignore<\/p>\n<p>Configuring package.json Scripts<\/p>\n<p>Make sure your package.json has the correct scripts:<\/p>\n<p>&#8220;scripts&#8221;: {<br \/>&#8220;start&#8221;: &#8220;node server.js&#8221;,<br \/>&#8220;dev&#8221;: &#8220;nodemon server.js&#8221;,<br \/>&#8220;prod&#8221;: &#8220;NODE_ENV=production node server.js&#8221;,<br \/>&#8220;test&#8221;: &#8220;jest &#8211;coverage&#8221;<br \/>}<\/p>\n<p>To install only production dependencies on the server (excludes devDependencies), use:<\/p>\n<p>npm ci &#8211;omit=dev<\/p>\n<p>Note: &#8220;npm install &#8211;production&#8221; is deprecated as of npm v9+. Always use &#8220;npm ci &#8211;omit=dev&#8221; on production servers.<\/p>\n<p>Creating a PM2 Ecosystem File<\/p>\n<p>Instead of starting your app with a simple command, create an ecosystem.config.js file in your project root. This is the correct PM2 configuration method in 2026:<\/p>\n<p>module.exports = {<br \/>apps: [{<br \/>name: &#8216;myapp&#8217;,<br \/>script: &#8216;server.js&#8217;,<br \/>instances: &#8216;max&#8217;,<br \/>exec_mode: &#8216;cluster&#8217;,<br \/>env: {<br \/>NODE_ENV: &#8216;development&#8217;<br \/>},<br \/>env_production: {<br \/>NODE_ENV: &#8216;production&#8217;,<br \/>PORT: 3000<br \/>},<br \/>error_file: &#8216;.\/logs\/err.log&#8217;,<br \/>out_file: &#8216;.\/logs\/out.log&#8217;,<br \/>log_date_format: &#8216;YYYY-MM-DD HH:mm:ss&#8217;<br \/>}]<br \/>};<\/p>\n<p>The &#8220;instances: max&#8221; setting tells PM2 to use all available CPU cores, and &#8220;exec_mode: cluster&#8221; enables Node.js cluster mode \u2014 which means your app handles far more traffic than a single-process setup.<\/p>\n<h2><strong>2. Setting Up a Linux Server<\/strong><\/h2>\n<h3><span style=\"font-weight: 400;\">Choosing a Cloud Provider<\/span><\/h3>\n<p>Selecting the right cloud provider affects your app&#8217;s speed, cost, and reliability. Here are the top options in 2026:<\/p>\n<h3><strong>1. CloudMinister<\/strong><\/h3>\n<p>CloudMinister is an affordable cloud hosting provider offering stable, Node.js-optimised Linux VPS servers for developers and businesses in India and globally. It provides VPS hosting, dedicated servers, and cloud infrastructure with a focus on performance, security, and 24\/7 managed support. <a href=\"https:\/\/cloudminister.com\">CloudMinister<\/a> is an excellent choice for startups and enterprises who want a cost-effective, fully supported environment for running Node.js applications without the complexity of hyperscaler platforms.<\/p>\n<h3>2. Amazon Web Services (AWS)<\/h3>\n<p>AWS EC2 is the world&#8217;s leading cloud compute platform, offering hundreds of instance types, global availability zones, auto-scaling groups, and deep integration with other AWS services like RDS, S3, and CloudFront. It suits any project size, from a small REST API to an enterprise microservices architecture.<\/p>\n<h3>3. DigitalOcean<\/h3>\n<p>DigitalOcean Droplets are developer-friendly virtual machines that balance simplicity and performance. With SSD storage, one-click deployments, a clean dashboard, and transparent flat-rate pricing, DigitalOcean is a top choice for individual developers, startups, and small teams deploying Node.js applications.<\/p>\n<h3>4. Akamai Cloud (formerly Linode)<\/h3>\n<p>Akamai Cloud \u2014 acquired and rebranded from Linode in 2023 \u2014 offers high-performance compute instances with transparent, predictable pricing and no surprise bills. In 2026, it provides Dedicated CPU instances, GPU servers, and managed Kubernetes. It is well-suited for developers who want bare-metal performance without cloud vendor lock-in.<\/p>\n<p>Recommended OS: Ubuntu 24.04 LTS<\/p>\n<p>Select Ubuntu 24.04 LTS (Noble Numbat) as your server operating system. It is the current LTS release, supported until April 2029, and the recommended choice for all Node.js production deployments in 2026.<\/p>\n<p>Do not use CentOS \u2014 it reached End of Life in December 2021 and no longer receives security updates. If you need a RHEL-based system, use Rocky Linux 9 or AlmaLinux 9 instead.<\/p>\n<p>Connecting to Your Server via SSH<\/p>\n<p>Once your instance is running, connect using SSH:<\/p>\n<p>ssh your-username@your-server-ip<\/p>\n<p>Always use SSH key authentication instead of password authentication. To generate an SSH key pair on your local machine (if you have not already done so):<\/p>\n<p>ssh-keygen -t ed25519 -C &#8220;your_email@example.com&#8221;<\/p>\n<p>Then copy your public key to the server:<\/p>\n<p>ssh-copy-id your-username@your-server-ip<\/p>\n<p>Hardening SSH Access (Do This Immediately After First Login)<\/p>\n<p>Open the SSH configuration file:<\/p>\n<p>sudo nano \/etc\/ssh\/sshd_config<\/p>\n<p>Set these values:<\/p>\n<p>PermitRootLogin no<br \/>PasswordAuthentication no<br \/>PubkeyAuthentication yes<\/p>\n<p>Save the file, then restart SSH:<\/p>\n<p>sudo systemctl restart sshd<\/p>\n<p>This ensures no one can brute-force their way into your server using a password.<\/p>\n<h2><strong>3. Installing Node.js and Required Dependencies<\/strong><\/h2>\n<p>Updating the System<\/p>\n<p>Always update your package list before installing anything:<\/p>\n<p>sudo apt update &amp;&amp; sudo apt upgrade -y<\/p>\n<p>Node.js Version Reference for 2026<\/p>\n<p>Before installing, understand which version to use:<\/p>\n<table data-path-to-node=\"1\">\n<thead>\n<tr>\n<td><strong>Version<\/strong><\/td>\n<td><strong>Status in 2026<\/strong><\/td>\n<td><strong>Recommendation<\/strong><\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><span data-path-to-node=\"1,1,0,0\">v22.x LTS (Iron)<\/span><\/td>\n<td><span data-path-to-node=\"1,1,1,0\">Active LTS \u2014 supported until April 2027<\/span><\/td>\n<td><span data-path-to-node=\"1,1,2,0\"><b data-path-to-node=\"1,1,2,0\" data-index-in-node=\"0\">USE THIS<\/b><\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,2,0,0\">v20.x LTS (Iron)<\/span><\/td>\n<td><span data-path-to-node=\"1,2,1,0\">Maintenance LTS \u2014 ends mid-2026<\/span><\/td>\n<td><span data-path-to-node=\"1,2,2,0\">Acceptable<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,3,0,0\">v18.x (Hydrogen)<\/span><\/td>\n<td><span data-path-to-node=\"1,3,1,0\">End of Life \u2014 April 2025<\/span><\/td>\n<td><span data-path-to-node=\"1,3,2,0\">Do NOT use<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,4,0,0\">v16.x (Gallium)<\/span><\/td>\n<td><span data-path-to-node=\"1,4,1,0\">End of Life \u2014 September 2023<\/span><\/td>\n<td><span data-path-to-node=\"1,4,2,0\">Do NOT use<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Installing Node.js via NVM (Recommended Method)<\/p>\n<p>NVM (Node Version Manager) is the industry-standard way to install Node.js on a Linux server in 2026. It lets you install and switch between multiple Node.js versions without system-level conflicts.<\/p>\n<h3>Step 1 \u2014 Install NVM:<\/h3>\n<p>curl -o- https:\/\/raw.githubusercontent.com\/nvm-sh\/nvm\/v0.39.7\/install.sh | bash<\/p>\n<h3>Step 2 \u2014 Reload your shell:<\/h3>\n<p>source ~\/.bashrc<\/p>\n<h3>Step 3 \u2014 Install Node.js v22 LTS:<\/h3>\n<p>nvm install 22<br \/>nvm use 22<br \/>nvm alias default 22<\/p>\n<h3>Step 4 \u2014 Verify the installation:<\/h3>\n<p>node -v<br \/>npm -v<\/p>\n<p>You should see v22.x.x and npm 10.x.x.<\/p>\n<p>Alternative: Install Node.js via NodeSource (System-Wide)<\/p>\n<p>If you need Node.js installed system-wide (for all users), use the official NodeSource binary:<\/p>\n<p>curl -fsSL https:\/\/deb.nodesource.com\/setup_22.x | sudo -E bash &#8211;<br \/>sudo apt-get install -y nodejs<br \/>node -v &amp;&amp; npm -v<\/p>\n<p>Installing PM2<\/p>\n<p>PM2 is the process manager that keeps your Node.js app running in the background, restarts it on crashes, and manages logs.<\/p>\n<p>If you installed Node.js with NVM, do NOT use sudo:<\/p>\n<p>npm install -g pm2<br \/>pm2 &#8211;version<\/p>\n<p>If you installed Node.js system-wide via NodeSource or apt, use sudo:<\/p>\n<p>sudo npm install -g pm2<\/p>\n<p>Installing Nginx<\/p>\n<p>sudo apt install nginx -y<br \/>sudo systemctl enable nginx<br \/>sudo systemctl start nginx<\/p>\n<h2><strong>4. Deploying the Node.js Application<\/strong><\/h2>\n<p>Setting Up the Application Directory<\/p>\n<p>Create a dedicated directory for your application:<\/p>\n<p>sudo mkdir -p \/var\/www\/myapp<br \/>sudo chown $USER:$USER \/var\/www\/myapp<\/p>\n<p>Recommended production directory structure:<\/p>\n<p>\/var\/www\/myapp\/<br \/>\u251c\u2500\u2500 server.js<br \/>\u251c\u2500\u2500 ecosystem.config.js<br \/>\u251c\u2500\u2500 package.json<br \/>\u251c\u2500\u2500 package-lock.json<br \/>\u251c\u2500\u2500 .env<br \/>\u251c\u2500\u2500 node_modules\/<br \/>\u2514\u2500\u2500 logs\/<br \/>\u251c\u2500\u2500 err.log<br \/>\u2514\u2500\u2500 out.log<\/p>\n<p>Transferring Your Application Files<\/p>\n<p>Recommended method \u2014 Git (industry standard in 2026):<\/p>\n<p>cd \/var\/www\/myapp<br \/>git clone https:\/\/github.com\/your-username\/your-repo.git .<\/p>\n<p>For private repositories, use a GitHub fine-grained personal access token:<\/p>\n<p>git clone https:\/\/oauth2:YOUR_GITHUB_TOKEN@github.com\/your-username\/your-repo.git .<\/p>\n<p>Alternative method \u2014 SCP (for quick one-time transfers):<\/p>\n<p>scp -r \/local\/path\/to\/app your-username@your-server-ip:\/var\/www\/myapp<\/p>\n<p>Installing Dependencies on the Server<\/p>\n<p>cd \/var\/www\/myapp<br \/>npm ci &#8211;omit=dev<\/p>\n<p>Starting the App with PM2<\/p>\n<p>Use the ecosystem.config.js file created in Section 1:<\/p>\n<p>pm2 start ecosystem.config.js &#8211;env production<\/p>\n<p>Or start directly without the ecosystem file:<\/p>\n<p>pm2 start server.js &#8211;name &#8220;myapp&#8221;<\/p>\n<p>Check that your app is running:<\/p>\n<p>pm2 status<br \/>pm2 logs myapp<\/p>\n<p>Making PM2 Start Automatically on Server Reboot<\/p>\n<p>pm2 startup systemd<\/p>\n<p>PM2 will output a command \u2014 copy it exactly and run it. Then save the process list:<\/p>\n<p>pm2 save<\/p>\n<p>From this point, your app will automatically restart whenever the server reboots.<\/p>\n<h2><span style=\"font-weight: 400;\">5. Configuring a Reverse Proxy with Nginx<\/span><\/h2>\n<h3>Why You Need a Reverse Proxy<\/h3>\n<p>A reverse proxy sits between the internet and your Node.js application. It is not optional in production \u2014 it is essential. Here is what Nginx does for you:<\/p>\n<p>Enhanced Security: Your Node.js app never directly faces the internet. Nginx acts as the shield, hiding your backend from direct attacks such as DDoS, request flooding, and direct port scanning.<\/p>\n<p>SSL Termination: Nginx handles HTTPS encryption and decryption so your Node.js app does not have to \u2014 improving performance and simplifying your code.<\/p>\n<p>Load Balancing: Nginx can distribute traffic across multiple Node.js processes or servers, ensuring no single instance is overwhelmed.<\/p>\n<p>Gzip Compression: Nginx compresses responses before sending them to the browser, reducing bandwidth usage and improving page load speed significantly.<\/p>\n<p>Static File Serving: Nginx serves CSS, JS, image files, and other static assets directly \u2014 much faster than routing them through Node.js.<\/p>\n<p>Access Control: You can set rate limits, IP whitelists, and authentication rules at the Nginx level, before requests even reach your app.<\/p>\n<p>Installing and Configuring Nginx<\/p>\n<p>Create a new Nginx configuration file for your application:<\/p>\n<p>sudo nano \/etc\/nginx\/sites-available\/myapp<\/p>\n<p>Paste the following complete production-ready configuration:<\/p>\n<p>server {<br \/>listen 80;<br \/>server_name yourdomain.com www.yourdomain.com;<\/p>\n<p># Security headers<br \/>add_header X-Frame-Options &#8220;SAMEORIGIN&#8221; always;<br \/>add_header X-Content-Type-Options &#8220;nosniff&#8221; always;<br \/>add_header X-XSS-Protection &#8220;1; mode=block&#8221; always;<br \/>add_header Referrer-Policy &#8220;no-referrer-when-downgrade&#8221; always;<\/p>\n<p># Gzip compression<br \/>gzip on;<br \/>gzip_types text\/plain application\/json application\/javascript text\/css;<\/p>\n<p>location \/ {<br \/>proxy_pass http:\/\/localhost:3000;<br \/>proxy_http_version 1.1;<br \/>proxy_set_header Upgrade $http_upgrade;<br \/>proxy_set_header Connection &#8216;upgrade&#8217;;<br \/>proxy_set_header Host $host;<br \/>proxy_set_header X-Real-IP $remote_addr;<br \/>proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br \/>proxy_set_header X-Forwarded-Proto $scheme;<br \/>proxy_cache_bypass $http_upgrade;<br \/>proxy_read_timeout 86400;<br \/>proxy_connect_timeout 60s;<br \/>}<\/p>\n<p># Serve static files directly via Nginx (much faster)<br \/>location \/static\/ {<br \/>alias \/var\/www\/myapp\/public\/;<br \/>expires 30d;<br \/>add_header Cache-Control &#8220;public, immutable&#8221;;<br \/>}<br \/>}<\/p>\n<p>Replace yourdomain.com with your actual domain name.<\/p>\n<p>Enable the configuration by creating a symbolic link:<\/p>\n<p>sudo ln -s \/etc\/nginx\/sites-available\/myapp \/etc\/nginx\/sites-enabled\/<\/p>\n<p>Always test the configuration before reloading \u2014 a single typo will take your site offline:<\/p>\n<p>sudo nginx -t<\/p>\n<p>If you see &#8220;syntax is ok&#8221; and &#8220;test is successful&#8221;, reload Nginx:<\/p>\n<p>sudo systemctl reload nginx<\/p>\n<p>Use &#8220;reload&#8221; instead of &#8220;restart&#8221; \u2014 reload applies changes with zero downtime. Restart briefly stops and starts Nginx, causing a momentary outage.<\/p>\n<h2><b>6. Securing Your Deployment<\/b><\/h2>\n<h3>Setting Up a Firewall with UFW<\/h3>\n<p>UFW (Uncomplicated Firewall) controls which ports are open on your server. Correct setup is critical.<\/p>\n<p>Set the default policies first \u2014 deny all incoming traffic, allow all outgoing:<\/p>\n<p>sudo ufw default deny incoming<br \/>sudo ufw default allow outgoing<\/p>\n<p>Allow SSH access (do this before enabling UFW, or you will lock yourself out):<\/p>\n<p>sudo ufw allow OpenSSH<\/p>\n<p>Allow HTTP and HTTPS through Nginx:<\/p>\n<p>sudo ufw allow &#8216;Nginx Full&#8217;<\/p>\n<p>Block direct access to your Node.js port \u2014 users must only reach your app through Nginx, never directly:<\/p>\n<p>sudo ufw deny 3000\/tcp<\/p>\n<p>Enable UFW and check the rules:<\/p>\n<p>sudo ufw enable<br \/>sudo ufw status verbose<\/p>\n<h3>Securing with SSL \u2014 Let&#8217;s Encrypt and Certbot<\/h3>\n<p>Install Certbot using the official snap method (recommended in 2026 \u2014 the apt version is outdated):<\/p>\n<p>sudo snap install &#8211;classic certbot<br \/>sudo ln -s \/snap\/bin\/certbot \/usr\/bin\/certbot<\/p>\n<p>Obtain your SSL certificate for both your domain and www subdomain:<\/p>\n<p>sudo certbot &#8211;nginx -d yourdomain.com -d www.yourdomain.com<\/p>\n<p>Certbot automatically configures Nginx for HTTPS and sets up a systemd timer for auto-renewal. Verify the renewal timer is active:<\/p>\n<p>sudo systemctl status snap.certbot.renew.timer<\/p>\n<p>Test that auto-renewal works correctly:<\/p>\n<p>sudo certbot renew &#8211;dry-run<\/p>\n<p>After SSL is installed, check your security grade at ssllabs.com\/ssltest \u2014 you should achieve an A or A+ rating.<\/p>\n<h3>Blocking Brute-Force Attacks with Fail2ban<\/h3>\n<p>Fail2ban monitors your server logs and automatically bans IP addresses that repeatedly fail SSH login attempts. It is essential for any internet-facing server.<\/p>\n<p>Install Fail2ban:<\/p>\n<p>sudo apt install fail2ban -y<\/p>\n<p>Create a local configuration file:<\/p>\n<p>sudo cp \/etc\/fail2ban\/jail.conf \/etc\/fail2ban\/jail.local<br \/>sudo nano \/etc\/fail2ban\/jail.local<\/p>\n<p>Find the [sshd] section and set these values:<\/p>\n<p>[sshd]<br \/>enabled = true<br \/>port = ssh<br \/>maxretry = 3<br \/>bantime = 3600<br \/>findtime = 600<\/p>\n<p>This bans any IP that fails SSH login 3 times within 10 minutes, for 1 hour.<\/p>\n<p><strong>Enable and start Fail2ban:<\/strong><\/p>\n<p>sudo systemctl enable fail2ban<br \/>sudo systemctl start fail2ban<br \/>sudo fail2ban-client status sshd<\/p>\n<h3>Securing Your Node.js Application Code<\/h3>\n<p>Install Helmet.js \u2014 a security middleware that automatically sets 11 HTTP security headers in your Express.js app:<\/p>\n<p>npm install helmet<br \/>npm install express-rate-limit<\/p>\n<p>Add these to the top of your server.js or app.js, before any routes:<\/p>\n<p>const express = require(&#8216;express&#8217;);<br \/>const helmet = require(&#8216;helmet&#8217;);<br \/>const rateLimit = require(&#8216;express-rate-limit&#8217;);<\/p>\n<p>const app = express();<\/p>\n<p>\/\/ Apply security headers<br \/>app.use(helmet());<\/p>\n<p>\/\/ Tell Express to trust the Nginx proxy<br \/>app.set(&#8216;trust proxy&#8217;, 1);<\/p>\n<p>\/\/ Rate limiting \u2014 max 100 requests per 15 minutes per IP<br \/>const limiter = rateLimit({<br \/>windowMs: 15 * 60 * 1000,<br \/>max: 100<br \/>});<br \/>app.use(&#8216;\/api\/&#8217;, limiter);<\/p>\n<h3>Security Best Practices Checklist<\/h3>\n<p>\u2022 Store all secrets and credentials in .env \u2014 never hardcode them<br \/>\u2022 Add .env to .gitignore \u2014 never commit it to version control<br \/>\u2022 Run your app as a non-root user (PM2 handles this by default)<br \/>\u2022 Keep system packages updated: sudo apt update &amp;&amp; sudo apt upgrade -y<br \/>\u2022 Review PM2 logs regularly for errors and unusual activity<\/p>\n<h2><span style=\"font-weight: 400;\">7. Automating Deployment with CI\/CD<\/span><\/h2>\n<h3>Introduction to CI\/CD Pipelines<\/h3>\n<p>A CI\/CD pipeline (Continuous Integration \/ Continuous Deployment) automates the process of testing and deploying your code every time you push a change. Instead of manually SSHing into your server, pulling code, and restarting PM2, a pipeline does it all for you in under 60 seconds \u2014 and only deploys if your tests pass.<\/p>\n<h3>How CI\/CD Works<\/h3>\n<p><strong>Continuous Integration (CI)<\/strong>: Every time a developer pushes code to the repository, the pipeline automatically installs dependencies and runs the test suite. If any test fails, deployment is blocked. Bugs are caught before they reach production.<\/p>\n<p><strong>Continuous Deployment (CD)<\/strong>: If all tests pass, the pipeline SSHs into your production server, pulls the latest code, reinstalls dependencies, and reloads the app \u2014 with zero manual steps and zero downtime.<\/p>\n<h3>Why It Matters<\/h3>\n<p>Speed: Deployment that took 15 minutes manually now takes under 60 seconds automatically.<br \/>Quality: Automated tests catch bugs before production. No more &#8220;it worked on my machine.&#8221;<br \/>Reliability: Every deployment follows the exact same steps \u2014 no human error, no forgotten commands.<br \/>Confidence: You can push code 10 times a day without fear.<\/p>\n<p>Using GitHub Actions<\/p>\n<p>In your project repository, create the following file:<\/p>\n<p>Path: .github\/workflows\/deploy.yml<\/p>\n<p>name: Deploy Node.js to Linux Server<\/p>\n<p>on:<br \/>push:<br \/>branches: [main]<\/p>\n<p>jobs:<br \/>test:<br \/>runs-on: ubuntu-latest<br \/>steps:<br \/>&#8211; uses: actions\/checkout@v4<\/p>\n<p>&#8211; name: Setup Node.js v22<br \/>uses: actions\/setup-node@v4<br \/>with:<br \/>node-version: &#8217;22&#8217;<br \/>cache: &#8216;npm&#8217;<\/p>\n<p>&#8211; name: Install dependencies<br \/>run: npm ci<\/p>\n<p>&#8211; name: Run tests<br \/>run: npm test<\/p>\n<p>deploy:<br \/>runs-on: ubuntu-latest<br \/>needs: test<br \/>if: github.ref == &#8216;refs\/heads\/main&#8217;<br \/>steps:<br \/>&#8211; name: Deploy via SSH<br \/>uses: appleboy\/ssh-action@v1.0.3<br \/>with:<br \/>host: ${{ secrets.SERVER_HOST }}<br \/>username: ${{ secrets.SERVER_USER }}<br \/>key: ${{ secrets.SSH_PRIVATE_KEY }}<br \/>script: |<br \/>cd \/var\/www\/myapp<br \/>git pull origin main<br \/>npm ci &#8211;omit=dev<br \/>pm2 reload ecosystem.config.js &#8211;env production<br \/>pm2 save<\/p>\n<h3>Setting Up GitHub Secrets<\/h3>\n<p>Your workflow references three secrets. You must add them to GitHub before the pipeline will work.<\/p>\n<p>Go to your GitHub repository \u2192 Settings \u2192 Secrets and Variables \u2192 Actions \u2192 New Repository Secret<\/p>\n<p>Add these three secrets:<\/p>\n<p>Secret name: SERVER_HOST<br \/>Value: Your server&#8217;s IP address (example: 203.0.113.42)<\/p>\n<p>Secret name: SERVER_USER<br \/>Value: Your Linux username (example: ubuntu or deploy)<\/p>\n<p>Secret name: SSH_PRIVATE_KEY<br \/>Value: The full contents of your private key file. To get it, run on your local machine:<br \/>cat ~\/.ssh\/id_ed25519<\/p>\n<p>Copy the entire output (including the &#8212;&#8211;BEGIN and &#8212;&#8211;END lines) and paste it as the secret value.<\/p>\n<p>Once these secrets are set, every push to your main branch will automatically test and deploy your application.<\/p>\n<h2><span style=\"font-weight: 400;\">8. Monitoring and Maintenance<\/span><\/h2>\n<p>PM2 has built-in monitoring tools that give you real-time insight into your application&#8217;s health:<\/p>\n<table data-path-to-node=\"1\">\n<thead>\n<tr>\n<td><strong>Command<\/strong><\/td>\n<td><strong>Description<\/strong><\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><span data-path-to-node=\"1,1,0,0\"><code data-path-to-node=\"1,1,0,0\" data-index-in-node=\"0\">pm2 status<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,1,1,0\">Overview of all running apps<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,2,0,0\"><code data-path-to-node=\"1,2,0,0\" data-index-in-node=\"0\">pm2 monit<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,2,1,0\">Real-time CPU and memory dashboard<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,3,0,0\"><code data-path-to-node=\"1,3,0,0\" data-index-in-node=\"0\">pm2 logs myapp<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,3,1,0\">Live log stream<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,4,0,0\"><code data-path-to-node=\"1,4,0,0\" data-index-in-node=\"0\">pm2 logs myapp --lines 200<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,4,1,0\">View last 200 lines of logs<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,5,0,0\"><code data-path-to-node=\"1,5,0,0\" data-index-in-node=\"0\">pm2 describe myapp<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,5,1,0\">Full process information<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,6,0,0\"><code data-path-to-node=\"1,6,0,0\" data-index-in-node=\"0\">pm2 flush<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,6,1,0\">Clear all log files<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Setting Up Log Rotation<\/h3>\n<p>Without log rotation, PM2 log files will grow indefinitely and eventually fill your server&#8217;s disk, causing a crash. Install PM2&#8217;s official log rotation module:<\/p>\n<p>pm2 install pm2-logrotate<br \/>pm2 set pm2-logrotate:max_size 10M<br \/>pm2 set pm2-logrotate:retain 7<br \/>pm2 set pm2-logrotate:compress true<\/p>\n<p>This keeps a maximum of 7 rotated log files, each up to 10MB, compressed.<\/p>\n<h3>Setting Up Uptime Monitoring and Alerts<\/h3>\n<p>Use these free tools to get alerted if your site goes down:<\/p>\n<p><strong>BetterStack (betterstack.com)<\/strong> \u2014 free tier with 3-minute check intervals, email and SMS alerts, and a status page. This is the most recommended uptime monitor in 2026.<\/p>\n<p><strong>UptimeRobot (uptimerobot.com)<\/strong> \u2014 free with 5-minute check intervals and email alerts. Good for personal projects.<\/p>\n<p>For application error tracking, use Sentry (sentry.io) \u2014 it captures unhandled exceptions in your Node.js app and sends alerts with a full stack trace and context.<\/p>\n<p><strong>Useful Server Health Commands<\/strong><\/p>\n<table data-path-to-node=\"1\">\n<thead>\n<tr>\n<td><strong>Command<\/strong><\/td>\n<td><strong>Description<\/strong><\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><span data-path-to-node=\"1,1,0,0\"><code data-path-to-node=\"1,1,0,0\" data-index-in-node=\"0\">htop<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,1,1,0\">CPU and memory usage<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,2,0,0\"><code data-path-to-node=\"1,2,0,0\" data-index-in-node=\"0\">df -h<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,2,1,0\">Disk space usage<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,3,0,0\"><code data-path-to-node=\"1,3,0,0\" data-index-in-node=\"0\">sudo ss -tlnp<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,3,1,0\">Check which ports are open<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,4,0,0\"><code data-path-to-node=\"1,4,0,0\" data-index-in-node=\"0\">sudo tail -f \/var\/log\/nginx\/error.log<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,4,1,0\">Check Nginx error logs<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,5,0,0\"><code data-path-to-node=\"1,5,0,0\" data-index-in-node=\"0\">journalctl --since \"1 hour ago\"<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,5,1,0\">Check system logs for the last hour<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Scaling Your Application<\/h3>\n<p>Vertical Scaling: Upgrade your server \u2014 add more CPU cores, RAM, or disk space through your cloud provider&#8217;s control panel. PM2 cluster mode will automatically use the new cores.<\/p>\n<p>Horizontal Scaling: Run multiple servers behind a load balancer. Use Nginx upstream blocks to distribute traffic across server instances. This approach allows your application to serve millions of requests while maintaining zero downtime during deployments.<\/p>\n<h3>Zero-Downtime Reloads with PM2<\/h3>\n<p>When deploying new code, always use &#8220;reload&#8221; instead of &#8220;restart&#8221;:<\/p>\n<p>pm2 reload ecosystem.config.js &#8211;env production<\/p>\n<p>PM2 reload restarts each worker process one at a time, so traffic is never interrupted. PM2 restart stops all processes simultaneously, causing a brief outage.<\/p>\n<h2><strong>9. Troubleshooting Common Issues<\/strong><\/h2>\n<p>Even with a correct setup, you will occasionally encounter issues. Here are the most common problems and their exact fixes.<\/p>\n<h3>502 Bad Gateway from Nginx<\/h3>\n<p>This means Nginx is running but your Node.js app is not, or is not listening on the expected port.<\/p>\n<table data-path-to-node=\"1\">\n<thead>\n<tr>\n<td><strong>Command<\/strong><\/td>\n<td><strong>Description<\/strong><\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><span data-path-to-node=\"1,1,0,0\"><code data-path-to-node=\"1,1,0,0\" data-index-in-node=\"0\">pm2 status<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,1,1,0\">Check if the app is running<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,2,0,0\"><code data-path-to-node=\"1,2,0,0\" data-index-in-node=\"0\">pm2 logs myapp<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,2,1,0\">Look for startup errors<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,3,0,0\"><code data-path-to-node=\"1,3,0,0\" data-index-in-node=\"0\">pm2 restart myapp<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,3,1,0\">Restart if it shows &#8220;stopped&#8221; or &#8220;errored&#8221;<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Also check your Nginx config \u2014 make sure &#8220;proxy_pass http:\/\/localhost:3000&#8221; matches the PORT in your .env file.<\/p>\n<h3>Port 3000 Already in Use<\/h3>\n<p>If you see &#8220;Error: listen EADDRINUSE :::3000&#8221;, another process is using your port.<\/p>\n<table data-path-to-node=\"1\">\n<thead>\n<tr>\n<td><strong>Command<\/strong><\/td>\n<td><strong>Description<\/strong><\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><span data-path-to-node=\"1,1,0,0\"><code data-path-to-node=\"1,1,0,0\" data-index-in-node=\"0\">sudo lsof -i :3000<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,1,1,0\">Find what is using the port<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,2,0,0\"><code data-path-to-node=\"1,2,0,0\" data-index-in-node=\"0\">kill -9 [PID]<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,2,1,0\">Kill that process (replace <code data-path-to-node=\"1,2,1,0\" data-index-in-node=\"27\">[PID]<\/code> with the number shown)<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>App Crashes on Reboot<\/h3>\n<p>Your PM2 process list was not saved, or the startup hook was not installed.<\/p>\n<table data-path-to-node=\"1\">\n<thead>\n<tr>\n<td><strong>Scenario \/ Step<\/strong><\/td>\n<td><strong>Command<\/strong><\/td>\n<td><strong>Description<\/strong><\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><span data-path-to-node=\"1,1,0,0\"><b data-path-to-node=\"1,1,0,0\" data-index-in-node=\"0\">Startup Setup<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,1,1,0\"><code data-path-to-node=\"1,1,1,0\" data-index-in-node=\"0\">pm2 startup systemd<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,1,2,0\">Generates a systemd startup command<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,2,0,0\"><b data-path-to-node=\"1,2,0,0\" data-index-in-node=\"0\">Startup Setup<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,2,1,0\"><i data-path-to-node=\"1,2,1,0\" data-index-in-node=\"0\">(Copy and run the output command)<\/i><\/span><\/td>\n<td><span data-path-to-node=\"1,2,2,0\">Configures PM2 to launch on system boot<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,3,0,0\"><b data-path-to-node=\"1,3,0,0\" data-index-in-node=\"0\">Startup Setup<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,3,1,0\"><code data-path-to-node=\"1,3,1,0\" data-index-in-node=\"0\">pm2 save<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,3,2,0\">Saves the current process list for automatic restoration<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,4,0,0\"><b data-path-to-node=\"1,4,0,0\" data-index-in-node=\"0\">Package-Lock Sync Fix<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,4,1,0\"><code data-path-to-node=\"1,4,1,0\" data-index-in-node=\"0\">rm -rf node_modules package-lock.json<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,4,2,0\">Deletes existing dependencies and the out-of-sync lockfile<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,5,0,0\"><b data-path-to-node=\"1,5,0,0\" data-index-in-node=\"0\">Package-Lock Sync Fix<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,5,1,0\"><code data-path-to-node=\"1,5,1,0\" data-index-in-node=\"0\">npm install<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,5,2,0\">Performs a clean install and regenerates the lockfile<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,6,0,0\"><b data-path-to-node=\"1,6,0,0\" data-index-in-node=\"0\">Package-Lock Sync Fix<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,6,1,0\"><code data-path-to-node=\"1,6,1,0\" data-index-in-node=\"0\">git add package-lock.json<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,6,2,0\">Stages the newly generated lockfile<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,7,0,0\"><b data-path-to-node=\"1,7,0,0\" data-index-in-node=\"0\">Package-Lock Sync Fix<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,7,1,0\"><code data-path-to-node=\"1,7,1,0\" data-index-in-node=\"0\">git commit -m \"Regenerate package-lock.json\"<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,7,2,0\">Commits the changes to your repository<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Permission Denied on npm Global Install (EACCES)<\/h3>\n<p>This happens when you use &#8220;sudo npm install -g&#8221; with NVM.<\/p>\n<table data-path-to-node=\"1\">\n<thead>\n<tr>\n<td><strong>Context \/ Action<\/strong><\/td>\n<td><strong>Command<\/strong><\/td>\n<td><strong>Description<\/strong><\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><span data-path-to-node=\"1,1,0,0\"><b data-path-to-node=\"1,1,0,0\" data-index-in-node=\"0\">Fix Warning<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,1,1,0\"><i data-path-to-node=\"1,1,1,0\" data-index-in-node=\"0\">Never use <code data-path-to-node=\"1,1,1,0\" data-index-in-node=\"10\">sudo<\/code> with NVM-installed npm.<\/i><\/span><\/td>\n<td><span data-path-to-node=\"1,1,2,0\">Avoids permission conflicts and security risks.<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,2,0,0\"><b data-path-to-node=\"1,2,0,0\" data-index-in-node=\"0\">Installation<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,2,1,0\"><code data-path-to-node=\"1,2,1,0\" data-index-in-node=\"0\">npm install -g pm2<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,2,2,0\">Correctly installs PM2 globally under your user profile.<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>SSL Certificate Not Found After Certbot<\/h3>\n<table data-path-to-node=\"1\">\n<thead>\n<tr>\n<td><strong>Scenario \/ Step<\/strong><\/td>\n<td><strong>Command<\/strong><\/td>\n<td><strong>Description<\/strong><\/td>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><span data-path-to-node=\"1,1,0,0\"><b data-path-to-node=\"1,1,0,0\" data-index-in-node=\"0\">SSL Setup<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,1,1,0\"><code data-path-to-node=\"1,1,1,0\" data-index-in-node=\"0\">sudo certbot --nginx -d yourdomain.com -d [www.yourdomain.com](https:\/\/www.yourdomain.com)<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,1,2,0\">Obtains and configures SSL certificate for the domains<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,2,0,0\"><b data-path-to-node=\"1,2,0,0\" data-index-in-node=\"0\">SSL Setup<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,2,1,0\"><code data-path-to-node=\"1,2,1,0\" data-index-in-node=\"0\">sudo nginx -t<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,2,2,0\">Tests the Nginx configuration for syntax errors<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,3,0,0\"><b data-path-to-node=\"1,3,0,0\" data-index-in-node=\"0\">SSL Setup<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,3,1,0\"><code data-path-to-node=\"1,3,1,0\" data-index-in-node=\"0\">sudo systemctl reload nginx<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,3,2,0\">Reloads Nginx to apply the new SSL configurations<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,4,0,0\"><b data-path-to-node=\"1,4,0,0\" data-index-in-node=\"0\">Config Fix<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,4,1,0\"><code data-path-to-node=\"1,4,1,0\" data-index-in-node=\"0\">sudo nginx -t<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,4,2,0\">Shows the exact file and line number with the error<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,5,0,0\"><b data-path-to-node=\"1,5,0,0\" data-index-in-node=\"0\">Config Fix<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,5,1,0\"><i data-path-to-node=\"1,5,1,0\" data-index-in-node=\"0\">(Edit <code data-path-to-node=\"1,5,1,0\" data-index-in-node=\"6\">\/etc\/nginx\/sites-available\/myapp<\/code>)<\/i><\/span><\/td>\n<td><span data-path-to-node=\"1,5,2,0\">Manually correct the configuration mistake<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,6,0,0\"><b data-path-to-node=\"1,6,0,0\" data-index-in-node=\"0\">Config Fix<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,6,1,0\"><code data-path-to-node=\"1,6,1,0\" data-index-in-node=\"0\">sudo nginx -t<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,6,2,0\">Test again to ensure all syntax errors are resolved<\/span><\/td>\n<\/tr>\n<tr>\n<td><span data-path-to-node=\"1,7,0,0\"><b data-path-to-node=\"1,7,0,0\" data-index-in-node=\"0\">Config Fix<\/b><\/span><\/td>\n<td><span data-path-to-node=\"1,7,1,0\"><code data-path-to-node=\"1,7,1,0\" data-index-in-node=\"0\">sudo systemctl reload nginx<\/code><\/span><\/td>\n<td><span data-path-to-node=\"1,7,2,0\">Reloads Nginx safely to apply the working configuration<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2><span style=\"font-weight: 400;\">Conclusion<\/span><\/h2>\n<p>Deploying a Node.js application on Linux in 2026 is a structured, repeatable process. When each layer is correctly configured, the result is a production system that is fast, resilient, and secure.<\/p>\n<p>Here is a summary of what you have built through this guide:<\/p>\n<ul>\n<li>Node.js v22 LTS installed via NVM \u2014 the correct 2026 method<\/li>\n<li>Application running under PM2 in cluster mode \u2014 using all available CPU cores<\/li>\n<li>Nginx reverse proxy routing traffic with security headers and gzip compression<\/li>\n<li>HTTPS with a Let&#8217;s Encrypt SSL certificate and automatic renewal via systemd<\/li>\n<li>UFW firewall with port 3000 blocked from direct public access<\/li>\n<li>Fail2ban blocking SSH brute-force attempts automatically<\/li>\n<li>Helmet.js and express-rate-limit securing your Node.js application code<\/li>\n<li>GitHub Actions CI\/CD pipeline \u2014 tests run first, then zero-downtime deployment on every push to main<\/li>\n<li>PM2 log rotation preventing disk fill in production<\/li>\n<li>Monitoring with PM2 monit, BetterStack uptime alerts, and Sentry error tracking<\/li>\n<\/ul>\n<p>This is the exact same production stack used by professional DevOps teams worldwide. You now have the foundation to host any Node.js project \u2014 from a personal API to a high-traffic SaaS application.<\/p>\n<p>Need a Linux VPS server that is already optimised for Node.js? CloudMinister offers Node.js-ready VPS plans with SSD storage, 99.99% uptime, and 24\/7 expert support \u2014 so you can focus on your code, not your infrastructure. Explore our <a href=\"https:\/\/cloudminister.com\/nodejs-hosting\/\">Node.js hosting plans<\/a> and get your application live today.<\/p>\n<h2><span style=\"font-weight: 400;\">Frequently Asked Questions \u2013 FAQs<\/span><\/h2>\n<h3>1. What is the best way to install Node.js on Linux in 2026?<\/h3>\n<p>The best method is to use NVM (Node Version Manager). NVM lets you install and switch between multiple Node.js versions without root permissions and without breaking other tools on your system. Run:<\/p>\n<p>curl -o- https:\/\/raw.githubusercontent.com\/nvm-sh\/nvm\/v0.39.7\/install.sh | bash<br \/>source ~\/.bashrc<br \/>nvm install 22<br \/>nvm use 22<br \/>nvm alias default 22<\/p>\n<p>This installs Node.js v22 LTS (Iron) \u2014 the current active LTS release, supported until April 2027. Alternatively, use the official NodeSource binary for system-wide installation.<\/p>\n<h3>2. How do I keep my Node.js application running after the terminal is closed or the server reboots?<\/h3>\n<p>Use PM2 \u2014 the industry-standard process manager for Node.js in production. After starting your app with PM2, run these two commands:<\/p>\n<p>pm2 startup systemd<br \/>pm2 save<\/p>\n<p>&#8220;pm2 startup systemd&#8221; generates a systemd service that starts PM2 automatically on boot. Copy and run the command it outputs. &#8220;pm2 save&#8221; saves your current process list so PM2 restores it on startup. Your app will now survive server reboots and terminal closures.<\/p>\n<h3>3. What is the easiest way to expose my Node.js app to the internet?<\/h3>\n<p>Use Nginx as a reverse proxy. Nginx accepts HTTP\/HTTPS traffic on ports 80 and 443 and forwards it to your Node.js app on port 3000 (or whichever port you use). This is the production-standard setup in 2026:<\/p>\n<p>server {<br \/>listen 80;<br \/>server_name yourdomain.com;<br \/>location \/ {<br \/>proxy_pass http:\/\/localhost:3000;<br \/>proxy_set_header Host $host;<br \/>proxy_set_header X-Real-IP $remote_addr;<br \/>proxy_set_header X-Forwarded-Proto $scheme;<br \/>}<br \/>}<\/p>\n<p>After saving the config: sudo nginx -t &amp;&amp; sudo systemctl reload nginx<\/p>\n<h3>4. How do I automate deployments so I do not have to SSH in every time?<\/h3>\n<p>Use GitHub Actions CI\/CD. Create a .github\/workflows\/deploy.yml file in your repository. Set three GitHub Secrets (SERVER_HOST, SERVER_USER, SSH_PRIVATE_KEY) in your repository settings. Every time you push to the main branch, GitHub Actions will run your tests and, if they pass, automatically SSH into your server, pull the latest code, install dependencies, and reload PM2 \u2014 all without any manual steps.<\/p>\n<h3>5. How do I secure and optimise my Node.js deployment?<\/h3>\n<p>A complete production security setup in 2026 includes:<\/p>\n<p>\u2022 UFW firewall: set default deny incoming, allow OpenSSH and Nginx Full, and block port 3000 from public access<br \/>\u2022 Let&#8217;s Encrypt SSL: use Certbot (snap method) to get free HTTPS certificates that auto-renew<br \/>\u2022 Fail2ban: automatically bans IPs after 3 failed SSH login attempts<br \/>\u2022 Helmet.js: sets 11 HTTP security headers in your Express app with one line of code<br \/>\u2022 express-rate-limit: limits API request rate per IP to prevent abuse<br \/>\u2022 .env file: keep all secrets out of your code and out of Git<br \/>\u2022 PM2 cluster mode: uses all CPU cores and enables zero-downtime reloads<\/p>\n<h3>6. Should I use Docker instead of PM2 for Node.js on Linux in 2026?<\/h3>\n<p>Both are valid choices, but they serve different purposes. PM2 is simpler, uses fewer server resources, and is ideal for a single Node.js application on a VPS. Docker is the better choice when you need consistent environments across development, staging, and production, or when you are running multiple services (databases, caches, workers) that need to be isolated. For most startups and individual projects in 2026, PM2 + Nginx on a VPS is the most cost-effective and straightforward approach.<\/p>\n<h3>7. Which Node.js version should I use in production in 2026?<\/h3>\n<p>Use Node.js v22 LTS (codename &#8220;Iron&#8221;). It is the current Active LTS release, supported until April 2027, and includes the latest performance improvements, security patches, and stable APIs. Avoid v18 (reached End of Life in April 2025) and v16 (reached End of Life in September 2023). Always install via NVM to make version upgrades easy in the future.<\/p>\n<h3>8. How do I deploy a Node.js app specifically on Ubuntu 24.04?<\/h3>\n<p>Ubuntu 24.04 LTS (Noble Numbat) is the recommended OS for Node.js in 2026. The entire process in this guide applies directly to Ubuntu 24.04. One important note: the default Node.js version installed by Ubuntu&#8217;s apt repository is v18, which is End of Life. Always install Node.js using NVM or the NodeSource v22 binary instead of running &#8220;sudo apt install nodejs&#8221; directly.<\/p>\n\n\n[\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Node.js powers over 6.3 million websites worldwide and remains the go-to runtime for building scalable APIs, real-time applications, and microservices in 2026. With Node.js v22 LTS (codename \\u201cIron\\u201d) now the current long-term support release, and Ubuntu 24.04 LTS (Noble Numbat) as the dominant Linux server OS, deploying Node.js has never been more streamlined \\u2014 but only when you follow the right steps.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Skipping a process manager, running your app without a reverse proxy, or exposing your Node.js port directly to the internet are mistakes that can bring a production server down within hours. This guide walks you through every step correctly \\u2014 from server setup to CI\/CD automation \\u2014 so your deployment is secure, stable, and ready to scale.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>By the end of this guide, you will have a fully working Node.js application running on a Linux server with PM2 process management, Nginx reverse proxy, Let\\u2019s Encrypt SSL, UFW firewall protection, and an automated GitHub Actions deployment pipeline.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>Before You Begin \\u2014 Prerequisites<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Make sure you have the following ready before starting:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/list&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>A Linux server running Ubuntu 24.04 LTS (recommended) or 22.04 LTS minimum<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>SSH access to the server with sudo privileges<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>A domain name pointed to your server\\u2019s public IP address (needed for SSL)<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Your Node.js application code \\u2014 either locally or hosted on a GitHub repository<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Basic familiarity with the Linux terminal and command line<\/li>&#8221; }\n    ],\n    &#8220;innerHTML&#8221;: &#8220;&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>1. Prepare Your Node.js Application for Production<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Getting Your Code Ready<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Go through your codebase and:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/list&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Remove unused npm packages from package.json<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Optimise database queries and add indexes where needed<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Implement proper error handling \\u2014 unhandled exceptions crash PM2 workers<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Make sure all sensitive values (API keys, database URLs, secrets) are in environment variables, never hardcoded<\/li>&#8221; }\n    ],\n    &#8220;innerHTML&#8221;: &#8220;&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Setting Up Environment Variables with dotenv<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Environment variables keep sensitive data out of your code and out of version control.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p><strong>Step 1<\/strong> \\u2014 Install the dotenv package:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>npm install dotenv<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p><strong>Step 2<\/strong> \\u2014 Create a .env file in your project root:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>PORT=3000\\nDB_URL=mongodb:\/\/localhost:27017\/mydb\\nSECRET_KEY=your_secret_key_here\\nNODE_ENV=production<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p><strong>Step 3<\/strong> \\u2014 Load it at the very top of your main file (server.js or app.js):<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>require('dotenv').config();\\nconst port = process.env.PORT || 3000;\\nconst dbUrl = process.env.DB_URL;<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p><strong>Step 4<\/strong> \\u2014 Add .env to your .gitignore file so it never gets pushed to GitHub:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>echo \\\".env\\\" >> .gitignore<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Configuring package.json Scripts<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Make sure your package.json has the correct scripts:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>\\\"scripts\\\": {\\n  \\\"start\\\": \\\"node server.js\\\",\\n  \\\"dev\\\": \\\"nodemon server.js\\\",\\n  \\\"prod\\\": \\\"NODE_ENV=production node server.js\\\",\\n  \\\"test\\\": \\\"jest --coverage\\\"\\n}<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>To install only production dependencies on the server (excludes devDependencies), use:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>npm ci --omit=dev<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p><strong>Note:<\/strong> \\u201cnpm install &#8211;production\\u201d is deprecated as of npm v9+. Always use \\u201cnpm ci &#8211;omit=dev\\u201d on production servers.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Creating a PM2 Ecosystem File<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Instead of starting your app with a simple command, create an <code>ecosystem.config.js<\/code> file in your project root:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>module.exports = {\\n  apps: [{\\n    name: 'myapp',\\n    script: 'server.js',\\n    instances: 'max',\\n    exec_mode: 'cluster',\\n    env: {\\n      NODE_ENV: 'development'\\n    },\\n    env_production: {\\n      NODE_ENV: 'production',\\n      PORT: 3000\\n    },\\n    error_file: '.\/logs\/err.log',\\n    out_file: '.\/logs\/out.log',\\n    log_date_format: 'YYYY-MM-DD HH:mm:ss'\\n  }]\\n};<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>2. Setting Up a Linux Server<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Choosing a Cloud Provider<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 4 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h4>1. CloudMinister<\/h4>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>CloudMinister is an affordable cloud hosting provider offering stable, Node.js-optimised Linux VPS servers for developers and businesses in India and globally. It provides VPS hosting, dedicated servers, and cloud infrastructure with a focus on performance, security, and 24\/7 managed support.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 4 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h4>2. Amazon Web Services (AWS)<\/h4>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>AWS EC2 is the world\\u2019s leading cloud compute platform, offering hundreds of instance types, global availability zones, auto-scaling groups, and deep integration with other AWS services like RDS, S3, and CloudFront.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 4 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h4>3. DigitalOcean<\/h4>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>DigitalOcean Droplets are developer-friendly virtual machines that balance simplicity and performance. With SSD storage, one-click deployments, a clean dashboard, and transparent flat-rate pricing, DigitalOcean is a top choice for individual developers, startups, and small teams deploying Node.js applications.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 4 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h4>4. Akamai Cloud (formerly Linode)<\/h4>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Akamai Cloud offers high-performance compute instances with transparent, predictable pricing. In 2026, it provides Dedicated CPU instances, GPU servers, and managed Kubernetes. It is well-suited for developers who want bare-metal performance without cloud vendor lock-in.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Recommended OS: Ubuntu 24.04 LTS<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Select Ubuntu 24.04 LTS (Noble Numbat) as your server operating system. It is the current LTS release, supported until April 2029, and the recommended choice for all Node.js production deployments in 2026.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Do not use CentOS \\u2014 it reached End of Life in December 2021. If you need a RHEL-based system, use Rocky Linux 9 or AlmaLinux 9 instead.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Connecting to Your Server via SSH<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>ssh your-username@your-server-ip<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Always use SSH key authentication instead of password authentication. To generate an SSH key pair on your local machine:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>ssh-keygen -t ed25519 -C \\\"your_email@example.com\\\"\\nssh-copy-id your-username@your-server-ip<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Hardening SSH Access<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo nano \/etc\/ssh\/sshd_config\\n\\nPermitRootLogin no\\nPasswordAuthentication no\\nPubkeyAuthentication yes\\n\\nsudo systemctl restart sshd<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>3. Installing Node.js and Required Dependencies<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Updating the System<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo apt update && sudo apt upgrade -y<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Node.js Version Reference for 2026<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/table&#8221;,\n    &#8220;attrs&#8221;: { &#8220;hasFixedLayout&#8221;: true },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<figure class=\\\"wp-block-table\\\"><table><thead><tr><th>Version<\/th><th>Status in 2026<\/th><th>Recommendation<\/th><\/tr><\/thead><tbody><tr><td>v22.x LTS (Iron)<\/td><td>Active LTS \\u2014 supported until April 2027<\/td><td>USE THIS<\/td><\/tr><tr><td>v20.x LTS<\/td><td>Maintenance LTS \\u2014 ends mid-2026<\/td><td>Acceptable<\/td><\/tr><tr><td>v18.x (Hydrogen)<\/td><td>End of Life \\u2014 April 2025<\/td><td>Do NOT use<\/td><\/tr><tr><td>v16.x (Gallium)<\/td><td>End of Life \\u2014 September 2023<\/td><td>Do NOT use<\/td><\/tr><\/tbody><\/table><\/figure>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Installing Node.js via NVM (Recommended)<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>curl -o- https:\/\/raw.githubusercontent.com\/nvm-sh\/nvm\/v0.39.7\/install.sh | bash\\nsource ~\/.bashrc\\nnvm install 22\\nnvm use 22\\nnvm alias default 22\\nnode -v\\nnpm -v<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Alternative: Install Node.js via NodeSource (System-Wide)<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>curl -fsSL https:\/\/deb.nodesource.com\/setup_22.x | sudo -E bash -\\nsudo apt-get install -y nodejs\\nnode -v && npm -v<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Installing PM2<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>npm install -g pm2\\npm2 --version<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Installing Nginx<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo apt install nginx -y\\nsudo systemctl enable nginx\\nsudo systemctl start nginx<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>4. Deploying the Node.js Application<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Setting Up the Application Directory<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo mkdir -p \/var\/www\/myapp\\nsudo chown $USER:$USER \/var\/www\/myapp<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Transferring Your Application Files<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Recommended method \\u2014 Git (industry standard in 2026):<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>cd \/var\/www\/myapp\\ngit clone https:\/\/github.com\/your-username\/your-repo.git .<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Installing Dependencies on the Server<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>cd \/var\/www\/myapp\\nnpm ci --omit=dev<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Starting the App with PM2<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>pm2 start ecosystem.config.js --env production\\npm2 status\\npm2 logs myapp<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Making PM2 Start Automatically on Server Reboot<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>pm2 startup systemd\\npm2 save<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>5. Configuring a Reverse Proxy with Nginx<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Why You Need a Reverse Proxy<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/list&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li><strong>Enhanced Security<\/strong> \\u2014 Your Node.js app never directly faces the internet.<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li><strong>SSL Termination<\/strong> \\u2014 Nginx handles HTTPS encryption so your Node.js app does not have to.<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li><strong>Load Balancing<\/strong> \\u2014 Nginx distributes traffic across multiple Node.js processes.<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li><strong>Gzip Compression<\/strong> \\u2014 Compresses responses, reducing bandwidth and improving load speed.<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li><strong>Static File Serving<\/strong> \\u2014 Serves CSS, JS, and images directly, much faster than routing through Node.js.<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li><strong>Access Control<\/strong> \\u2014 Set rate limits, IP whitelists, and authentication rules at the Nginx level.<\/li>&#8221; }\n    ],\n    &#8220;innerHTML&#8221;: &#8220;&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Installing and Configuring Nginx<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo nano \/etc\/nginx\/sites-available\/myapp<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Paste the following production-ready configuration:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>server {\\n  listen 80;\\n  server_name yourdomain.com www.yourdomain.com;\\n\\n  add_header X-Frame-Options \\\"SAMEORIGIN\\\" always;\\n  add_header X-Content-Type-Options \\\"nosniff\\\" always;\\n  add_header X-XSS-Protection \\\"1; mode=block\\\" always;\\n  add_header Referrer-Policy \\\"no-referrer-when-downgrade\\\" always;\\n\\n  gzip on;\\n  gzip_types text\/plain application\/json application\/javascript text\/css;\\n\\n  location \/ {\\n    proxy_pass http:\/\/localhost:3000;\\n    proxy_http_version 1.1;\\n    proxy_set_header Upgrade $http_upgrade;\\n    proxy_set_header Connection 'upgrade';\\n    proxy_set_header Host $host;\\n    proxy_set_header X-Real-IP $remote_addr;\\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\\n    proxy_set_header X-Forwarded-Proto $scheme;\\n    proxy_cache_bypass $http_upgrade;\\n    proxy_read_timeout 86400;\\n    proxy_connect_timeout 60s;\\n  }\\n\\n  location \/static\/ {\\n    alias \/var\/www\/myapp\/public\/;\\n    expires 30d;\\n    add_header Cache-Control \\\"public, immutable\\\";\\n  }\\n}<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo ln -s \/etc\/nginx\/sites-available\/myapp \/etc\/nginx\/sites-enabled\/\\nsudo nginx -t\\nsudo systemctl reload nginx<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>6. Securing Your Deployment<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Setting Up a Firewall with UFW<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo ufw default deny incoming\\nsudo ufw default allow outgoing\\nsudo ufw allow OpenSSH\\nsudo ufw allow 'Nginx Full'\\nsudo ufw deny 3000\/tcp\\nsudo ufw enable\\nsudo ufw status verbose<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Securing with SSL \\u2014 Let\\u2019s Encrypt and Certbot<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo snap install --classic certbot\\nsudo ln -s \/snap\/bin\/certbot \/usr\/bin\/certbot\\nsudo certbot --nginx -d yourdomain.com -d www.yourdomain.com\\nsudo systemctl status snap.certbot.renew.timer\\nsudo certbot renew --dry-run<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Blocking Brute-Force Attacks with Fail2ban<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo apt install fail2ban -y\\nsudo cp \/etc\/fail2ban\/jail.conf \/etc\/fail2ban\/jail.local\\nsudo nano \/etc\/fail2ban\/jail.local<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Find the [sshd] section and set:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>[sshd]\\nenabled = true\\nport = ssh\\nmaxretry = 3\\nbantime = 3600\\nfindtime = 600<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo systemctl enable fail2ban\\nsudo systemctl start fail2ban\\nsudo fail2ban-client status sshd<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Securing Your Node.js Application Code<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>npm install helmet\\nnpm install express-rate-limit<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>const express = require('express');\\nconst helmet = require('helmet');\\nconst rateLimit = require('express-rate-limit');\\n\\nconst app = express();\\n\\napp.use(helmet());\\napp.set('trust proxy', 1);\\n\\nconst limiter = rateLimit({\\n  windowMs: 15 * 60 * 1000,\\n  max: 100\\n});\\napp.use('\/api\/', limiter);<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>7. Automating Deployment with CI\/CD<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Using GitHub Actions<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Create the following file in your repository: <code>.github\/workflows\/deploy.yml<\/code><\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>name: Deploy Node.js to Linux Server\\n\\non:\\n  push:\\n    branches: [main]\\n\\njobs:\\n  test:\\n    runs-on: ubuntu-latest\\n    steps:\\n      - uses: actions\/checkout@v4\\n      - name: Setup Node.js v22\\n        uses: actions\/setup-node@v4\\n        with:\\n          node-version: '22'\\n          cache: 'npm'\\n      - name: Install dependencies\\n        run: npm ci\\n      - name: Run tests\\n        run: npm test\\n\\n  deploy:\\n    runs-on: ubuntu-latest\\n    needs: test\\n    if: github.ref == 'refs\/heads\/main'\\n    steps:\\n      - name: Deploy via SSH\\n        uses: appleboy\/ssh-action@v1.0.3\\n        with:\\n          host: ${{ secrets.SERVER_HOST }}\\n          username: ${{ secrets.SERVER_USER }}\\n          key: ${{ secrets.SSH_PRIVATE_KEY }}\\n          script: |\\n            cd \/var\/www\/myapp\\n            git pull origin main\\n            npm ci --omit=dev\\n            pm2 reload ecosystem.config.js --env production\\n            pm2 save<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Setting Up GitHub Secrets<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Go to your GitHub repository \\u2192 Settings \\u2192 Secrets and Variables \\u2192 Actions \\u2192 New Repository Secret and add these three secrets:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/list&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li><strong>SERVER_HOST<\/strong> \\u2014 Your server\\u2019s IP address<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li><strong>SERVER_USER<\/strong> \\u2014 Your Linux username (e.g. ubuntu or deploy)<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li><strong>SSH_PRIVATE_KEY<\/strong> \\u2014 Full contents of your private key file (run: <code>cat ~\/.ssh\/id_ed25519<\/code>)<\/li>&#8221; }\n    ],\n    &#8220;innerHTML&#8221;: &#8220;&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>8. Monitoring and Maintenance<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/table&#8221;,\n    &#8220;attrs&#8221;: { &#8220;hasFixedLayout&#8221;: true },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<figure class=\\\"wp-block-table\\\"><table><thead><tr><th>Command<\/th><th>Description<\/th><\/tr><\/thead><tbody><tr><td>pm2 status<\/td><td>Overview of all running apps<\/td><\/tr><tr><td>pm2 monit<\/td><td>Real-time CPU and memory dashboard<\/td><\/tr><tr><td>pm2 logs myapp<\/td><td>Live log stream<\/td><\/tr><tr><td>pm2 logs myapp &#8211;lines 200<\/td><td>View last 200 lines of logs<\/td><\/tr><tr><td>pm2 describe myapp<\/td><td>Full process information<\/td><\/tr><tr><td>pm2 flush<\/td><td>Clear all log files<\/td><\/tr><\/tbody><\/table><\/figure>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Setting Up Log Rotation<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>pm2 install pm2-logrotate\\npm2 set pm2-logrotate:max_size 10M\\npm2 set pm2-logrotate:retain 7\\npm2 set pm2-logrotate:compress true<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Zero-Downtime Reloads with PM2<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>pm2 reload ecosystem.config.js --env production<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>9. Troubleshooting Common Issues<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>502 Bad Gateway from Nginx<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>This means Nginx is running but your Node.js app is not, or is not listening on the expected port.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>pm2 status\\npm2 logs myapp\\npm2 restart myapp<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>Port 3000 Already in Use<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>sudo lsof -i :3000\\nkill -9 [PID]<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>App Crashes on Reboot<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>pm2 startup systemd\\npm2 save<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>Conclusion<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Deploying a Node.js application on Linux in 2026 is a structured, repeatable process. When each layer is correctly configured, the result is a production system that is fast, resilient, and secure. Here is a summary of what you have built through this guide:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/list&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Node.js v22 LTS installed via NVM \\u2014 the correct 2026 method<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Application running under PM2 in cluster mode \\u2014 using all available CPU cores<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Nginx reverse proxy routing traffic with security headers and gzip compression<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>HTTPS with a Let\\u2019s Encrypt SSL certificate and automatic renewal via systemd<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>UFW firewall with port 3000 blocked from direct public access<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Fail2ban blocking SSH brute-force attempts automatically<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Helmet.js and express-rate-limit securing your Node.js application code<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>GitHub Actions CI\/CD pipeline \\u2014 zero-downtime deployment on every push to main<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>PM2 log rotation preventing disk fill in production<\/li>&#8221; }\n    ],\n    &#8220;innerHTML&#8221;: &#8220;&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Need a Linux VPS server that is already optimised for Node.js? <a href=\\\"https:\/\/cloudminister.com\/nodejs-hosting\/\\\">CloudMinister offers Node.js-ready VPS plans<\/a> with SSD storage, 99.99% uptime, and 24\/7 expert support \\u2014 so you can focus on your code, not your infrastructure.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 2 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h2>Frequently Asked Questions \\u2013 FAQs<\/h2>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>1. What is the best way to install Node.js on Linux in 2026?<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>The best method is to use NVM (Node Version Manager). Run:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>curl -o- https:\/\/raw.githubusercontent.com\/nvm-sh\/nvm\/v0.39.7\/install.sh | bash\\nsource ~\/.bashrc\\nnvm install 22\\nnvm use 22\\nnvm alias default 22<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>2. How do I keep my Node.js application running after the terminal is closed or the server reboots?<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Use PM2. After starting your app, run:<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/code&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<pre class=\\\"wp-block-code\\\"><code>pm2 startup systemd\\npm2 save<\/code><\/pre>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>3. What is the easiest way to expose my Node.js app to the internet?<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Use Nginx as a reverse proxy. After saving the config: <code>sudo nginx -t &amp;&amp; sudo systemctl reload nginx<\/code><\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>4. How do I automate deployments so I do not have to SSH in every time?<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Use GitHub Actions CI\/CD. Create a <code>.github\/workflows\/deploy.yml<\/code> file in your repository and set three GitHub Secrets (SERVER_HOST, SERVER_USER, SSH_PRIVATE_KEY). Every push to the main branch will automatically test and deploy your code.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>5. How do I secure and optimise my Node.js deployment?<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/list&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>UFW firewall: deny incoming, allow OpenSSH and Nginx Full, block port 3000<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Let\\u2019s Encrypt SSL: use Certbot (snap method) for free HTTPS certificates that auto-renew<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Fail2ban: automatically bans IPs after 3 failed SSH login attempts<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>Helmet.js: sets 11 HTTP security headers in your Express app with one line of code<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>express-rate-limit: limits API request rate per IP to prevent abuse<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>.env file: keep all secrets out of your code and out of Git<\/li>&#8221; },\n      { &#8220;blockName&#8221;: &#8220;core\/list-item&#8221;, &#8220;attrs&#8221;: {}, &#8220;innerBlocks&#8221;: [], &#8220;innerHTML&#8221;: &#8220;<li>PM2 cluster mode: uses all CPU cores and enables zero-downtime reloads<\/li>&#8221; }\n    ],\n    &#8220;innerHTML&#8221;: &#8220;&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>6. Should I use Docker instead of PM2 for Node.js on Linux in 2026?<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Both are valid choices. PM2 is simpler and uses fewer server resources \\u2014 ideal for a single Node.js application on a VPS. Docker is better when you need consistent environments across dev, staging, and production, or when running multiple isolated services. For most startups in 2026, PM2 + Nginx on a VPS is the most cost-effective approach.<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>7. Which Node.js version should I use in production in 2026?<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Use Node.js v22 LTS (codename \\u201cIron\\u201d). It is the current Active LTS release, supported until April 2027, and includes the latest performance improvements and security patches. Avoid v18 (End of Life April 2025) and v16 (End of Life September 2023).<\/p>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/heading&#8221;,\n    &#8220;attrs&#8221;: { &#8220;level&#8221;: 3 },\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<h3>8. How do I deploy a Node.js app specifically on Ubuntu 24.04?<\/h3>&#8221;\n  },\n  {\n    &#8220;blockName&#8221;: &#8220;core\/paragraph&#8221;,\n    &#8220;attrs&#8221;: {},\n    &#8220;innerBlocks&#8221;: [],\n    &#8220;innerHTML&#8221;: &#8220;<p>Ubuntu 24.04 LTS (Noble Numbat) is the recommended OS for Node.js in 2026. The entire process in this guide applies directly to Ubuntu 24.04. One important note: the default Node.js version installed by Ubuntu\\u2019s apt repository is v18, which is End of Life. Always install Node.js using NVM or the NodeSource v22 binary instead of running <code>sudo apt install nodejs<\/code> directly.<\/p>&#8221;\n  }\n]\n","protected":false},"excerpt":{"rendered":"<p>Node.js powers over 6.3 million websites worldwide and remains the go-to runtime for building scalable APIs, real-time applications, and microservices in 2026. With Node.js v22 LTS (codename &#8220;Iron&#8221;) now the current long-term support release, and Ubuntu 24.04 LTS (Noble Numbat) as the dominant Linux server OS, deploying Node.js has never been more streamlined \u2014 but&#8230;<\/p>\n","protected":false},"author":1,"featured_media":36018,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"footnotes":""},"categories":[620],"tags":[680,682,683,621,679,681],"class_list":["post-9791","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-node-js","tag-app-hosting","tag-domain-ssl-setup","tag-nginx-reverse-proxy","tag-node-js","tag-node-js-deployment","tag-server-configuration"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"Learn how to deploy Node.js on Ubuntu 24.04 with PM2, Nginx, SSL, and GitHub Actions CI\/CD. Follow our complete step-by-step guide for a secure production setup\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Tanuj Chugh\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"CloudMinister -\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"Deploy Node.js on Linux in 2026 -CloudMinister\" \/>\n\t\t<meta property=\"og:description\" content=\"Learn how to deploy Node.js on Ubuntu 24.04 with PM2, Nginx, SSL, and GitHub Actions CI\/CD. Follow our complete step-by-step guide for a secure production setup\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/cloudminister.com\/blog\/wp-content\/uploads\/2025\/07\/Feature-images-98-scaled.png\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/cloudminister.com\/blog\/wp-content\/uploads\/2025\/07\/Feature-images-98-scaled.png\" \/>\n\t\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t\t<meta property=\"og:image:height\" content=\"1340\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-05-18T07:56:27+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-05-21T06:44:02+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"Deploy Node.js on Linux in 2026 -CloudMinister\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Learn how to deploy Node.js on Ubuntu 24.04 with PM2, Nginx, SSL, and GitHub Actions CI\/CD. Follow our complete step-by-step guide for a secure production setup\" \/>\n\t\t<meta name=\"twitter:image\" content=\"https:\/\/cloudminister.com\/blog\/wp-content\/uploads\/2025\/07\/Feature-images-98-scaled.png\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#blogposting\",\"name\":\"Deploy Node.js on Linux in 2026 -CloudMinister\",\"headline\":\"How to Deploy Node.js on Linux: Complete Production Guide 2026\",\"author\":{\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/author\\\/tanuj-chugh\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/Feature-images-98-scaled.png\",\"width\":2560,\"height\":1340},\"datePublished\":\"2026-05-18T07:56:27+00:00\",\"dateModified\":\"2026-05-21T06:44:02+00:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#webpage\"},\"articleSection\":\"node.js, App Hosting, Domain &amp; SSL Setup, Nginx Reverse Proxy, Node.js, Node.js Deployment, Server Configuration\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/category\\\/node-js\\\/#listItem\",\"name\":\"node.js\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/category\\\/node-js\\\/#listItem\",\"position\":2,\"name\":\"node.js\",\"item\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/category\\\/node-js\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#listItem\",\"name\":\"How to Deploy Node.js on Linux: Complete Production Guide 2026\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#listItem\",\"position\":3,\"name\":\"How to Deploy Node.js on Linux: Complete Production Guide 2026\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/category\\\/node-js\\\/#listItem\",\"name\":\"node.js\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/#organization\",\"name\":\"CloudMinister\",\"url\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/author\\\/tanuj-chugh\\\/#author\",\"url\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/author\\\/tanuj-chugh\\\/\",\"name\":\"Tanuj Chugh\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/3395bcaf63eb5840dd73f67c7cb69ffd3dfe33336c1bdb1f1b9aeabe5b05e15a?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Tanuj Chugh\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#webpage\",\"url\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/\",\"name\":\"Deploy Node.js on Linux in 2026 -CloudMinister\",\"description\":\"Learn how to deploy Node.js on Ubuntu 24.04 with PM2, Nginx, SSL, and GitHub Actions CI\\\/CD. Follow our complete step-by-step guide for a secure production setup\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/author\\\/tanuj-chugh\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/author\\\/tanuj-chugh\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/wp-content\\\/uploads\\\/2025\\\/07\\\/Feature-images-98-scaled.png\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#mainImage\",\"width\":2560,\"height\":1340},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/deploy-your-node-js-app-on-linux\\\/#mainImage\"},\"datePublished\":\"2026-05-18T07:56:27+00:00\",\"dateModified\":\"2026-05-21T06:44:02+00:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/\",\"name\":\"CloudMinister\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/cloudminister.com\\\/blog\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Deploy Node.js on Linux in 2026 -CloudMinister","description":"Learn how to deploy Node.js on Ubuntu 24.04 with PM2, Nginx, SSL, and GitHub Actions CI\/CD. Follow our complete step-by-step guide for a secure production setup","canonical_url":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#blogposting","name":"Deploy Node.js on Linux in 2026 -CloudMinister","headline":"How to Deploy Node.js on Linux: Complete Production Guide 2026","author":{"@id":"https:\/\/cloudminister.com\/blog\/author\/tanuj-chugh\/#author"},"publisher":{"@id":"https:\/\/cloudminister.com\/blog\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/cloudminister.com\/blog\/wp-content\/uploads\/2025\/07\/Feature-images-98-scaled.png","width":2560,"height":1340},"datePublished":"2026-05-18T07:56:27+00:00","dateModified":"2026-05-21T06:44:02+00:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#webpage"},"isPartOf":{"@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#webpage"},"articleSection":"node.js, App Hosting, Domain &amp; SSL Setup, Nginx Reverse Proxy, Node.js, Node.js Deployment, Server Configuration"},{"@type":"BreadcrumbList","@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/cloudminister.com\/blog\/#listItem","position":1,"name":"Home","item":"https:\/\/cloudminister.com\/blog\/","nextItem":{"@type":"ListItem","@id":"https:\/\/cloudminister.com\/blog\/category\/node-js\/#listItem","name":"node.js"}},{"@type":"ListItem","@id":"https:\/\/cloudminister.com\/blog\/category\/node-js\/#listItem","position":2,"name":"node.js","item":"https:\/\/cloudminister.com\/blog\/category\/node-js\/","nextItem":{"@type":"ListItem","@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#listItem","name":"How to Deploy Node.js on Linux: Complete Production Guide 2026"},"previousItem":{"@type":"ListItem","@id":"https:\/\/cloudminister.com\/blog\/#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#listItem","position":3,"name":"How to Deploy Node.js on Linux: Complete Production Guide 2026","previousItem":{"@type":"ListItem","@id":"https:\/\/cloudminister.com\/blog\/category\/node-js\/#listItem","name":"node.js"}}]},{"@type":"Organization","@id":"https:\/\/cloudminister.com\/blog\/#organization","name":"CloudMinister","url":"https:\/\/cloudminister.com\/blog\/"},{"@type":"Person","@id":"https:\/\/cloudminister.com\/blog\/author\/tanuj-chugh\/#author","url":"https:\/\/cloudminister.com\/blog\/author\/tanuj-chugh\/","name":"Tanuj Chugh","image":{"@type":"ImageObject","@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/3395bcaf63eb5840dd73f67c7cb69ffd3dfe33336c1bdb1f1b9aeabe5b05e15a?s=96&d=mm&r=g","width":96,"height":96,"caption":"Tanuj Chugh"}},{"@type":"WebPage","@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#webpage","url":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/","name":"Deploy Node.js on Linux in 2026 -CloudMinister","description":"Learn how to deploy Node.js on Ubuntu 24.04 with PM2, Nginx, SSL, and GitHub Actions CI\/CD. Follow our complete step-by-step guide for a secure production setup","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/cloudminister.com\/blog\/#website"},"breadcrumb":{"@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#breadcrumblist"},"author":{"@id":"https:\/\/cloudminister.com\/blog\/author\/tanuj-chugh\/#author"},"creator":{"@id":"https:\/\/cloudminister.com\/blog\/author\/tanuj-chugh\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/cloudminister.com\/blog\/wp-content\/uploads\/2025\/07\/Feature-images-98-scaled.png","@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#mainImage","width":2560,"height":1340},"primaryImageOfPage":{"@id":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/#mainImage"},"datePublished":"2026-05-18T07:56:27+00:00","dateModified":"2026-05-21T06:44:02+00:00"},{"@type":"WebSite","@id":"https:\/\/cloudminister.com\/blog\/#website","url":"https:\/\/cloudminister.com\/blog\/","name":"CloudMinister","inLanguage":"en-US","publisher":{"@id":"https:\/\/cloudminister.com\/blog\/#organization"}}]},"og:locale":"en_US","og:site_name":"CloudMinister -","og:type":"article","og:title":"Deploy Node.js on Linux in 2026 -CloudMinister","og:description":"Learn how to deploy Node.js on Ubuntu 24.04 with PM2, Nginx, SSL, and GitHub Actions CI\/CD. Follow our complete step-by-step guide for a secure production setup","og:url":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/","og:image":"https:\/\/cloudminister.com\/blog\/wp-content\/uploads\/2025\/07\/Feature-images-98-scaled.png","og:image:secure_url":"https:\/\/cloudminister.com\/blog\/wp-content\/uploads\/2025\/07\/Feature-images-98-scaled.png","og:image:width":"2560","og:image:height":"1340","article:published_time":"2026-05-18T07:56:27+00:00","article:modified_time":"2026-05-21T06:44:02+00:00","twitter:card":"summary_large_image","twitter:title":"Deploy Node.js on Linux in 2026 -CloudMinister","twitter:description":"Learn how to deploy Node.js on Ubuntu 24.04 with PM2, Nginx, SSL, and GitHub Actions CI\/CD. Follow our complete step-by-step guide for a secure production setup","twitter:image":"https:\/\/cloudminister.com\/blog\/wp-content\/uploads\/2025\/07\/Feature-images-98-scaled.png"},"aioseo_meta_data":{"post_id":"9791","title":"Deploy Node.js on Linux in 2026 -CloudMinister","description":"Learn how to deploy Node.js on Ubuntu 24.04 with PM2, Nginx, SSL, and GitHub Actions CI\/CD. Follow our complete step-by-step guide for a secure production setup","keywords":null,"keyphrases":{"focus":{"keyphrase":"Deploy Node.js","score":58,"analysis":{"keyphraseInTitle":{"score":9,"maxScore":9,"error":0},"keyphraseInDescription":{"score":9,"maxScore":9,"error":0},"keyphraseLength":{"score":9,"maxScore":9,"error":0,"length":2},"keyphraseInURL":{"score":1,"maxScore":5,"error":1},"keyphraseInIntroduction":{"score":3,"maxScore":9,"error":1},"keyphraseInSubHeadings":{"score":3,"maxScore":9,"error":1},"keyphraseInImageAlt":[],"keywordDensity":{"score":0,"type":"low","maxScore":9,"error":1}}},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"featured","og_image_url":"https:\/\/cloudminister.com\/blog\/wp-content\/uploads\/2025\/07\/Feature-images-98-scaled.png","og_image_width":"2560","og_image_height":"1340","og_image_custom_url":null,"og_image_custom_fields":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"BlogPosting","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":null,"frequency":"default","local_seo":null,"breadcrumb_settings":null,"limit_modified_date":false,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":[],"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"created":"2025-07-15 10:18:46","updated":"2026-05-21 06:56:51","seo_analyzer_scan_date":null,"focus_keyword":"Deploy Node.js","additional_keywords":null,"truseo_locale":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/cloudminister.com\/blog\/\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/cloudminister.com\/blog\/category\/node-js\/\" title=\"node.js\">node.js<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tHow to Deploy Node.js on Linux: Complete Production Guide 2026\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/cloudminister.com\/blog\/"},{"label":"node.js","link":"https:\/\/cloudminister.com\/blog\/category\/node-js\/"},{"label":"How to Deploy Node.js on Linux: Complete Production Guide 2026","link":"https:\/\/cloudminister.com\/blog\/deploy-your-node-js-app-on-linux\/"}],"_links":{"self":[{"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/posts\/9791","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/comments?post=9791"}],"version-history":[{"count":3,"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/posts\/9791\/revisions"}],"predecessor-version":[{"id":37642,"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/posts\/9791\/revisions\/37642"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/media\/36018"}],"wp:attachment":[{"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/media?parent=9791"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/categories?post=9791"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudminister.com\/blog\/wp-json\/wp\/v2\/tags?post=9791"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}