how to deploy nodejs on digitalocean

How to deploy a Node.js application on DigitalOcean

We may earn an affiliate commission through purchases made from our guides and tutorials.

We’ll focus on the most common approach: using a Droplet (that’s DigitalOcean’s term for a virtual private server) with Ubuntu, since it gives you full control and is great for production setups. If you’re looking for something simpler without managing servers, I’ll touch on their App Platform at the end as an alternative.

Before we jump in, a quick reality check: You’ll need a DigitalOcean account (sign up at digitalocean.com if you don’t have one—they’ll ask for payment info for verification, but you can start with their free credits if available). Also, assume you have a basic Node.js app ready (like an Express server) and it’s in a Git repo for easy cloning. If not, no worries—we’ll create a simple one as an example. Costs? A basic Droplet starts at around $6/month, but it scales with usage.

Create and set up your Droplet

First things first, head to your DigitalOcean dashboard and create a new Droplet. Choose Ubuntu 22.04 (LTS) as the OS—it’s stable and widely supported. Go for the Basic plan with at least 1 GB RAM (the $6/month option) to handle a simple Node app; bump it up if your app is resource-heavy. Pick a datacenter region close to your users for better latency, like New York if you’re in the US.

For authentication, use SSH keys instead of passwords—it’s more secure and avoids brute-force attacks. If you don’t have an SSH key, generate one on your local machine:

  • On macOS/Linux: Run ssh-keygen -t ed25519 -C "your_email@example.com" in your terminal, then copy the public key with cat ~/.ssh/id_ed25519.pub.
  • On Windows: Use PuTTY or Git Bash to do the same.

Paste that public key into the Droplet creation form. Once the Droplet is spun up (takes a minute), note its public IP address from the dashboard.

Now, connect to it via SSH from your local terminal: ssh root@your_droplet_ip. If everything’s set, you’ll be logged in as root. Immediately create a non-root user for safety—running everything as root is a bad idea in production.

Run these commands:

adduser yourusername
usermod -aG sudo yourusername

Switch to the new user: su - yourusername. Then, copy your SSH key over for this user too:

mkdir ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys

Paste your public key into that file, save (Ctrl+O, Enter, Ctrl+X in nano), and set permissions: chmod 600 ~/.ssh/authorized_keys.

Back as root (type exit), disable root login and password auth for SSH:

nano /etc/ssh/sshd_config

Change PermitRootLogin yes to PermitRootLogin no and PasswordAuthentication yes to PasswordAuthentication no. Save, then restart SSH: systemctl reload sshd.

Finally, set up a basic firewall with UFW:

ufw allow OpenSSH
ufw allow http
ufw allow https
ufw enable

This opens ports for SSH, HTTP, and HTTPS while blocking everything else. Check with ufw status.

This setup draws from standard security practices, skipping it could leave your server vulnerable.

Install Node.js and dependencies

With the server ready, install Node.js. We’ll use Node Version Manager (NVM) for flexibility, as it lets you switch versions easily.

As your non-root user, install NVM:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash

Load it: source ~/.bashrc. Verify: nvm --version.

Install a stable LTS version, like Node 20:

nvm install 20
nvm use 20

Check: node -v and npm -v. If your app needs a specific version, adjust accordingly.

Update your system packages too: sudo apt update && sudo apt upgrade -y.

Deploy your Node.js application

Now, let’s get your app on the server. Clone it from GitHub (assuming it’s public or you have access):

mkdir ~/apps
cd ~/apps
git clone https://github.com/yourusername/your-repo.git yourapp
cd yourapp
npm install

If you don’t have an app, create a simple one for testing. Make a file called server.js:

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello from my Node.js app on DigitalOcean!\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Run it temporarily: node server.js. Test locally on the server: curl http://localhost:3000. It should respond with your message. Stop it with Ctrl+C.

But don’t run it like this in production—it won’t survive reboots or crashes. That’s where PM2 comes in.

Manage your app with PM2

PM2 is a process manager that keeps your app running as a daemon, restarts it if it fails, and handles clustering if needed.

Install it globally: sudo npm install pm2@latest -g.

Start your app: pm2 start server.js --name myapp.

Check status: pm2 list—you’ll see something like:

┌────┬─────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
 id  name         mode           status     cpu       memory   
├────┼─────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
 0   myapp        fork      0     online     0%        25.2mb   
└────┴─────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

Set PM2 to start on boot: pm2 startup systemd. It’ll give you a command to run with sudo—do that.

Save your process list: pm2 save.

Now your app runs in the background on port 3000 (or whatever you set). Useful commands: pm2 stop myapppm2 restart myapppm2 logs myapp for debugging.

Set up Nginx as a reverse proxy

Your app is running, but it’s only accessible via the IP and port (e.g., http://your_ip:3000). For production, use Nginx to handle requests, add caching if needed, and enable HTTPS.

Install Nginx: sudo apt install nginx -y.

Edit the default config: sudo nano /etc/nginx/sites-available/default.

In the server block for port 80, add or replace the location / section:

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_cache_bypass $http_upgrade;
}

This proxies requests to your Node app. Test the config: sudo nginx -t. If good, restart: sudo systemctl restart nginx.

Now, hit http://your_droplet_ip in a browser—you should see your app’s response.

But we’re not done; HTTP is insecure. Let’s add SSL.

Secure with HTTPS using Let’s Encrypt

Point your domain to the Droplet first: In your domain registrar (e.g., GoDaddy), add an A record for @ or www pointing to your Droplet’s IP. Verify with dig yourdomain.com.

Install Certbot for Let’s Encrypt: sudo apt install certbot python3-certbot-nginx -y.

Run: sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com.

It’ll ask for an email, agree to terms, and auto-configure Nginx for HTTPS. It redirects HTTP to HTTPS and renews certs automatically.

Check: sudo systemctl status certbot.timer for the renewal timer.

Your app is now at https://yourdomain.com. If you set up the proxy earlier, it all flows through Nginx securely.

Let’s Encrypt is free and renews every 90 days automatically.

Monitoring, scaling, and best practices

Once deployed, monitor with PM2’s pm2 monit or DigitalOcean’s built-in metrics in the dashboard. For logs, use pm2 logs or set up something like ELK stack if it grows.

If traffic spikes, scale by upgrading your Droplet or using DigitalOcean’s Load Balancers. Also, set environment variables in your app (e.g., for databases) via a .env file and dotenv package—don’t hardcode secrets.

Test everything: Deploy changes by pulling from Git, npm install if needed, then pm2 restart myapp. Automate with GitHub Actions or a simple script for CI/CD.

Common pitfalls? Forgetting to open ports in UFW, mismatched ports between app and proxy, or DNS propagation delays (give it 30-60 minutes).

Alternative option: using DigitalOcean App Platform

If managing a server sounds like too much hassle, try App Platform—it’s their PaaS offering, like Heroku but cheaper.

Fork a sample repo like https://github.com/digitalocean/sample-nodejs, or use your own. In the dashboard, create an app, connect your GitHub repo, select the main branch, and configure (e.g., set env vars, add a database if needed). Pick a region, review the plan (starts free for static sites, $12/month for basic apps), and deploy.

It auto-builds, deploys, and scales. Changes? Push to Git, and it redeploys automatically. See the docs for details—great for quick setups without SSH.

Was this helpful?

Thanks for your feedback!
Alex is the resident editor and oversees all of the guides published. His past work and experience include Colorlib, Stack Diary, Hostvix, and working with a number of editorial publications. He has been wrangling code and publishing his findings about it since the early 2000s.

Leave a comment

Your email address will not be published. Required fields are marked *