This site contains affiliate links — we may earn a commission if you sign up through them.

Node.js Deployment Guide on DigitalOcean Droplet 2026

A practical, step-by-step guide to deploying a production Node.js application on a DigitalOcean Droplet using nvm, PM2, Nginx, and Let's Encrypt.

Node.js Deployment Guide on DigitalOcean Droplet 2026

Deploying a Node.js application to production goes beyond just running npm start — you need a system to keep your app running continuously even when the server reboots, a reverse proxy to manage traffic, and HTTPS to protect data in transit. This guide walks you through every step on a DigitalOcean Droplet, from preparing your instance and installing Node.js via nvm, running your app with PM2, configuring Nginx as a reverse proxy, to setting up free SSL with Let's Encrypt — with every real-world command included.

Preparing Your Droplet and Installing Node.js (nvm)

Before installing anything, you need to choose a Droplet size that matches your workload. For a small to medium Node.js API or web app, DigitalOcean's Basic Droplet with 1 GiB RAM / 1 vCPU / 25 GB SSD at $6/month is sufficient for testing and low-traffic applications. For production workloads that require build processes or handle many concurrent connections, step up to 2 GiB RAM / 2 vCPU / 60 GB SSD at $18/month so PM2 can run in cluster mode across both cores. If your primary users are in Thailand, select the sgp1 (Singapore) region for the lowest latency, with blr1 (Bangalore) as the next best option. Once you've created your Droplet (choose Ubuntu LTS image), SSH in and update the system first with apt update && apt upgrade -y. Create a non-root user instead of running everything as root with adduser deploy && usermod -aG sudo deploy, then enable DigitalOcean's Cloud Firewall, which is free and lets you restrict access to just SSH (22), HTTP (80), and HTTPS (443). Next, install Node.js via nvm (Node Version Manager) rather than using apt directly, since nvm lets you switch versions easily and always have the latest. Run curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash, then source the updated shell profile with source ~/.bashrc. Install the latest LTS version with nvm install --lts and set it as default with nvm use --lts. Verify the installation with node -v and npm -v. If your machine has only 512 MiB or 1 GiB of RAM, running npm install on large dependency sets may kill the process due to memory exhaustion. To be safe, add a 1-2 GB swap file beforehand.

Running Your Node.js App with PM2

From our hands-on testing — pM2 is a process manager for Node.js that keeps your app running in the background, restarts it automatically if it crashes, and supports running multiple instances in parallel on a single machine. Install it globally with npm install -g pm2. Clone your project repository to the machine with git clone https://github.com/user/myapp.git && cd myapp, then install dependencies with npm install --production. If your project needs to build (like TypeScript or a frontend bundle), run npm run build first. When ready, start your app with pm2 start app.js --name myapp — PM2 will immediately run it as a background process and name it for easy management later. Check the status of all processes with pm2 list, view real-time logs with pm2 logs myapp, or open a dashboard showing CPU and memory with pm2 monit. For Droplets with 2 or more vCPUs, you can use cluster mode to squeeze every core with pm2 start app.js -i max --name myapp, which automatically spawns one worker per CPU core and load-balances requests across them round-robin style. For more complex projects that need environment variables or special arguments, use an ecosystem file instead of typing long commands each time. Create ecosystem.config.js with your name, script, instances, env, and other settings, then run with pm2 start ecosystem.config.js. This approach makes redeployments and migrations to new Droplets consistent because the config lives in one file you can commit to your repository.

Configuring Nginx as a Reverse Proxy

Your Node.js app running under PM2 typically binds to an internal port like 3000, which shouldn't be exposed directly to users. Setting up Nginx as a reverse proxy lets you serve your site on the standard ports 80/443, support multiple apps on the same Droplet using different domains, and simplifies SSL setup in the next step. Install Nginx with apt install nginx -y. Create a new config file at /etc/nginx/sites-available/myapp with a server block like this: server { listen 80; server_name yourdomain.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } The Upgrade and Connection headers are critical if your app uses WebSocket (like Socket.io), because without them real-time connections will drop or fall back to polling. Enable the config by symlinking it to sites-enabled with ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/. Always test syntax before reloading with nginx -t — if it passes without errors, reload the service with systemctl reload nginx. Don't forget to open the firewall for HTTP/HTTPS with ufw allow 'Nginx Full'. To host multiple apps on the same Droplet, repeat the process by creating separate config files for each domain and pointing each proxy_pass to the different ports your PM2 instances are using.

Key takeaway: Install Nginx with apt install nginx, then create a server block in /etc/nginx/sites-available/

Setting Up SSL with Let's Encrypt

After Nginx is proxying correctly over HTTP, the next step is enabling HTTPS with Let's Encrypt, which issues free certificates and auto-renews them. The tool is Certbot, an Nginx-specific plugin. Install it with apt install certbot python3-certbot-nginx -y. Before you run anything, make sure your domain's DNS A record points to your Droplet's IP — if not, Certbot won't be able to verify it. Once DNS is ready, run certbot --nginx -d yourdomain.com -d www.yourdomain.com. Certbot will ask for an email for expiration warnings, then modify your Nginx config automatically, adding a listen 443 block with the certificate path and a redirect from HTTP to HTTPS. Let's Encrypt certificates last 90 days, but Certbot installs a systemd timer or cron job that checks twice daily and auto-renews when expiration gets close (typically under 30 days remaining). Test that auto-renewal works without waiting by running certbot renew --dry-run — if it completes error-free, auto-renewal is ready. One critical detail: port 80 must stay open even after you enable HTTPS, because Let's Encrypt's HTTP-01 challenge verification during renewal needs it. If your firewall blocks port 80 after setting up HTTPS, auto-renewal will silently fail and you won't realize it until the certificate actually expires.

Auto-restart and Log Management

A point users often miss: one step many people forget after deploying is ensuring PM2 restarts your app when the Droplet reboots, which can happen anytime for maintenance or power issues. After your first pm2 start, always run two companion commands: pm2 startup generates a systemd service directive (copy and run the output with sudo), followed by pm2 save to record the current process list. When the Droplet reboots, systemd will invoke PM2, which loads the saved process list and restarts everything automatically without manual intervention. PM2 stores app stdout/stderr in ~/.pm2/logs/ by default, but if left unmanaged, log files balloon and consume your SSD, especially on smaller Droplets. Install the pm2-logrotate module with pm2 install pm2-logrotate to automatically rotate logs by size or age, compressing old files as .gz. You can configure size limits and retention depth. PM2 auto-restarts crashed processes by default, but if a process restarts too frequently (more than 15 times in 1 minute), PM2 enters an 'errored' state to prevent infinite restart loops — this signals that you need to investigate the actual error in the logs rather than letting it keep failing.

When to Use This Approach (Use Cases)

Deploying Node.js on your own Droplet as described here is ideal when you need full control over your infrastructure, such as customizing Nginx config in detail, running multiple apps or services on one machine, managing background workers or cron jobs alongside your web server, or handling large numbers of WebSocket connections that need persistent connections. Sometimes self-hosting offers more flexibility than managed platforms. Another reason is cost: a Basic Droplet starts at $6/month and can run many apps at once if you allocate resources wisely, unlike App Platform which charges per container instance (e.g., $10/month per app for a shared 1vCPU/1GiB container). Conversely, if your team lacks DevOps expertise, doesn't want to manage OS updates, security patches, SSL renewal, or scaling yourself, DigitalOcean App Platform (a managed PaaS) is better. You deploy straight from Git, and everything else — SSL, scaling, zero-downtime updates — is automatic, though you trade flexibility for simplicity and lose OS-level control. In short, Droplet + PM2 + Nginx suits teams with existing DevOps knowledge who need custom infrastructure or multi-service deployments to save costs per app, such as a mid-size SaaS backend, a mobile app API, webhook receivers, or real-time services like chat or notifications. Teams prioritizing speed to market over infrastructure control should consider App Platform instead.

Common Mistakes and How to Fix Them

The most common mistake is forgetting pm2 startup && pm2 save after deploy, so your app vanishes the moment the Droplet reboots without any warning. Users typically only notice hours later when the site is down. Protect yourself by checking pm2 list after every Droplet maintenance or reboot to confirm processes are still running. The second frequent issue is Nginx not forwarding Upgrade and Connection headers, causing WebSocket apps like Socket.io to fail to connect or drop frequently even though local testing works fine. Check the browser console for WebSocket connection errors. Fix by verifying your Nginx server block includes proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection 'upgrade';. A third problem is EADDRINUSE errors when trying to run a new app on a port that's already in use by a lingering process. Use lsof -i :3000 to find what's holding the port, then kill it or use pm2 delete myapp before starting fresh. A fourth issue is forgetting to open the firewall for Nginx after a fresh install, leaving your site unreachable from outside even though the service is running. Check with ufw status to see if there's a rule allowing Nginx Full or ports 80/443. Finally, certificate auto-renewal fails silently when your firewall or Nginx config blocks port 80 after enabling HTTPS, breaking the Let's Encrypt HTTP-01 challenge. Keep port 80 open at all times even after HTTPS is live, and periodically check certbot certificates to see expiration dates in advance.

Best Practices

For security, disable SSH password login and use SSH keys exclusively, plus create a non-root user for daily work and use sudo only when necessary. Enable DigitalOcean's free Cloud Firewall alongside ufw on the machine to restrict open ports to just what's required. For configuration, never hardcode sensitive values like database passwords or API keys directly in code — use environment variables via a .env file with a library like dotenv, and always add .env to .gitignore to prevent accidental leaks to your repository. For monitoring, DigitalOcean provides free Monitoring and 1 Uptime Check per account — enable them to watch CPU, memory, and disk usage in real-time and get alerts if your site goes down before users do. For backups, create a Droplet snapshot before major changes like upgrading a major Node.js version or altering database schema — snapshots cost only $0.06 per GiB per month, much cheaper than recovering from a failed change. For performance, use PM2 cluster mode on any Droplet with 2+ vCPU cores to harness all cores instead of leaving Node.js single-threaded. For networking, if you need a static IP for whitelisting with external systems, a Reserved IP attached to an active Droplet is free, but if left unattached it costs $5/month — always tie it to your production Droplet to avoid waste.

Get $200 Free Credit →

Frequently Asked Questions

What size Droplet do I need to deploy a Node.js app?
For testing or small apps, a Basic Droplet with 1 GiB RAM/1 vCPU ($6/month) is enough. For production needing cluster mode and higher traffic, step up to 2 GiB RAM/2 vCPU ($18/month) or larger.
How does PM2 differ from systemd? Do I have to use PM2?
PM2 is built specifically for Node.js with built-in cluster mode, log management, and easier commands than systemd, which is a general-purpose Linux service manager. You can use systemd alone, but you'd have to write a service file yourself and you won't get cluster mode built-in.
Should I deploy on a Droplet myself or use App Platform?
Choose a Droplet if you need full server control, multiple apps on one machine, or heavy background worker/WebSocket loads. Choose App Platform (managed PaaS) if you want fast deployments without managing the server yourself.
Does a Let's Encrypt SSL certificate auto-renew?
Yes, Certbot installs a systemd timer to auto-renew. Certificates are valid 90 days and renew automatically as expiration approaches. Test with certbot renew --dry-run, but you must keep port 80 open for the HTTP-01 challenge.
Can I run multiple Node.js apps on a single Droplet?
Yes — run each app via PM2 with different names and internal ports, then create separate Nginx server blocks per domain, each proxy_pass pointing to its app's port.
Do I need a domain before following this guide?
You can set up Node.js, PM2, and Nginx proxy using the Droplet IP directly without a domain. But Let's Encrypt SSL requires a domain with DNS pointing to the Droplet first.