Close Menu
GeekBlog

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    Instagram Already Has an Off Switch for the Algorithm. It Resets Every Time You Close the App.

    September 12, 2026

    Anthropic Banned Five Groups of Working Scientists. It Says It Cannot Prove Any of Them Meant Harm.

    September 12, 2026

    Google Made Its Best Paid Gemini Feature Free. The Price Is Access to Your Inbox.

    September 12, 2026
    Facebook X (Twitter) Instagram Threads
    GeekBlog
    • Home
    • Mobile
    • Tech News
    • Blog
    • Gaming
    • Smartwatch
    • How-To Guides
    • AI & Software
    Facebook
    GeekBlog
    Home»Blog»How to Run Node.js on DigitalOcean: Droplet vs App Platform
    Blog

    How to Run Node.js on DigitalOcean: Droplet vs App Platform

    Ethan CaldwellBy Ethan CaldwellSeptember 9, 202612 Mins Read
    Share Facebook Twitter Pinterest LinkedIn Tumblr Email Copy Link
    Server racks in a data center hosting cloud applications
    Share
    Facebook Twitter LinkedIn Pinterest Email Copy Link

    You can run Node.js on DigitalOcean in two ways: on a Droplet, where you manage Ubuntu, Node, a process manager and Nginx yourself, or on App Platform, where you push a Git repo and DigitalOcean builds and runs it for you. The Droplet route costs less at the low end and gives you full control. App Platform costs a little more per container but removes the server administration entirely. This guide walks through both paths with a real Express app so you can pick the one that fits your project.

    Quick answer: For a hobby project or an app you want to control end to end, create an Ubuntu Droplet, install Node with nvm, run the app under pm2, put Nginx in front as a reverse proxy, get a certificate with Certbot and lock the firewall to ports 22, 80 and 443. For a team that wants Git push deploys, autoscaling and no servers to patch, use App Platform: connect the repo, let the Node.js buildpack detect it, set your environment variables and make sure the app listens on process.env.PORT.

    Both paths share the same Express sample app, so the first section builds that. After that the article splits: the Droplet section covers nvm, pm2, a systemd alternative, Nginx, Certbot, the firewall and zero downtime reloads. The App Platform section covers the buildpack, environment variables, scaling and logs. A troubleshooting section and an FAQ close it out.

    The sample Express app

    Every example below runs this minimal app. The only rule that matters for DigitalOcean is that the port comes from the environment, because App Platform assigns one at runtime and a Droplet behind Nginx should not be listening on 80 directly.

    mkdir hello-node && cd hello-node
    npm init -y
    npm install express
    
    cat > server.js <<'EOF'
    const express = require('express');
    const app = express();
    const port = process.env.PORT || 3000;
    
    app.get('/', (req, res) => {
      res.json({ status: 'ok', node: process.version, time: new Date().toISOString() });
    });
    
    app.get('/health', (req, res) => res.send('healthy'));
    
    app.listen(port, () => console.log(`listening on ${port}`));
    EOF

    Add a start script and an engines field to package.json. The engines field is what App Platform reads to choose a Node version, and it also documents the version for anyone who touches the Droplet later.

    {
      "name": "hello-node",
      "version": "1.0.0",
      "main": "server.js",
      "scripts": {
        "start": "node server.js"
      },
      "engines": {
        "node": "22.x"
      },
      "dependencies": {
        "express": "^4.19.2"
      }
    }

    Commit this to a Git repository on GitHub, GitLab or Bitbucket. App Platform needs a repo, and pulling from Git is also the cleanest way to get code onto a Droplet.

    Path 1: Node.js on a Droplet

    Create the Droplet and harden SSH

    In the DigitalOcean control panel choose Create, then Droplets. Pick Ubuntu 24.04 LTS, the region closest to your users, and the smallest Basic plan for a test. Add your SSH key at creation time instead of using a root password. Once it boots, connect and create a regular user so you are not running Node as root.

    ssh root@YOUR_DROPLET_IP
    adduser deploy
    usermod -aG sudo deploy
    rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
    apt update && apt upgrade -y
    apt install -y nginx git ufw

    From here on, log in as deploy.

    Install Node with nvm

    Ubuntu’s apt package for Node is usually old. nvm lets you install the exact major version your app declares and switch versions later without touching system packages. The install script is published on the nvm GitHub project; check the README for the current version tag before you run it.

    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
    source ~/.bashrc
    nvm install 22
    nvm use 22
    nvm alias default 22
    node -v
    npm -v

    Now clone the app and install production dependencies only.

    cd ~
    git clone https://github.com/YOU/hello-node.git
    cd hello-node
    npm ci --omit=dev
    PORT=3000 node server.js   # sanity check, then Ctrl+C

    Keep it running with pm2

    pm2 is the standard process manager for Node on a single server. It restarts the app if it crashes, runs it in cluster mode across all CPU cores and resurrects everything after a reboot.

    npm install -g pm2
    cat > ecosystem.config.js <<'EOF'
    module.exports = {
      apps: [{
        name: 'hello-node',
        script: 'server.js',
        instances: 'max',
        exec_mode: 'cluster',
        env: { NODE_ENV: 'production', PORT: 3000 }
      }]
    };
    EOF
    pm2 start ecosystem.config.js
    pm2 save
    pm2 startup   # prints a sudo command; copy and run it

    Recommended for you:

    How to Use the TikTok for Developers Documentation
    Blog·Sep 9, 2026

    How to Use the TikTok for Developers Documentation

    Run the exact sudo line that pm2 startup prints; it installs a systemd unit that restores your saved process list at boot.

    Zero downtime reloads

    Because the app runs in cluster mode, pm2 can restart workers one at a time so the listening socket never closes. That is your deploy command from now on.

    cd ~/hello-node
    git pull
    npm ci --omit=dev
    pm2 reload hello-node
    pm2 logs hello-node --lines 50
    Tip: pm2 reload is graceful; pm2 restart kills every worker at once. Use reload for deploys and restart only when you change environment variables that workers read at startup.

    The systemd alternative

    If you would rather not add pm2, a plain systemd unit does the job for a single process. It will not give you cluster mode or graceful reloads, but it is one less tool to learn.

    sudo tee /etc/systemd/system/hello-node.service > /dev/null <<'EOF'
    [Unit]
    Description=hello-node Express app
    After=network.target
    
    [Service]
    User=deploy
    WorkingDirectory=/home/deploy/hello-node
    Environment=NODE_ENV=production
    Environment=PORT=3000
    ExecStart=/home/deploy/.nvm/versions/node/v22.11.0/bin/node server.js
    Restart=always
    RestartSec=3
    
    [Install]
    WantedBy=multi-user.target
    EOF
    sudo systemctl daemon-reload
    sudo systemctl enable --now hello-node
    sudo systemctl status hello-node

    Check the exact node path with which node after nvm use, because the version directory in ExecStart must match what nvm installed.

    Nginx as a reverse proxy

    Nginx terminates TLS, serves static files and forwards everything else to the Node process on port 3000. Replace the default site with this server block.

    sudo tee /etc/nginx/sites-available/hello-node > /dev/null <<'EOF'
    server {
        listen 80;
        server_name example.com www.example.com;
    
        location / {
            proxy_pass http://127.0.0.1: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;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
        }
    }
    EOF
    sudo ln -s /etc/nginx/sites-available/hello-node /etc/nginx/sites-enabled/
    sudo rm /etc/nginx/sites-enabled/default
    sudo nginx -t && sudo systemctl reload nginx

    Point an A record for your domain at the Droplet IP before the next step, because Certbot validates over HTTP on port 80.

    HTTPS with Certbot

    sudo snap install --classic certbot
    sudo ln -s /snap/bin/certbot /usr/bin/certbot
    sudo certbot --nginx -d example.com -d www.example.com
    sudo certbot renew --dry-run

    The Nginx plugin rewrites your server block to listen on 443 and adds a redirect from 80. Renewal runs from a systemd timer that the snap installs, so there is nothing else to schedule. Details are in the official Certbot instructions.

    Firewall

    sudo ufw allow OpenSSH
    sudo ufw allow 'Nginx Full'
    sudo ufw enable
    sudo ufw status

    Port 3000 stays closed to the outside world; only Nginx on the same machine can reach it. A DigitalOcean Cloud Firewall from the Networking menu can enforce the same rules before traffic reaches the Droplet.

    Warning: Run ufw allow OpenSSH before ufw enable. Enabling the firewall with no SSH rule will lock you out of the Droplet, and the only way back in is the recovery console.

    Path 2: Node.js on App Platform

    Create the app

    From the control panel choose Create, then App Platform. Connect your GitHub, GitLab or Bitbucket account, pick the repo and branch, and leave Autodeploy enabled so every push to that branch triggers a build. App Platform detects a Node app when it finds package.json, package-lock.json, yarn.lock or pnpm-lock.yaml in the source directory, according to the Node.js buildpack reference.

    How the buildpack builds and runs your app

    The buildpack installs dependencies with npm ci (or Yarn or pnpm if it finds their lockfiles), runs the build script from package.json if one is present, and then uses your start script as the run command. It reads the Node version from the engines.node field and falls back to Node 22 if you leave that out. Both dependencies and devDependencies are installed for the build, but devDependencies are pruned before deployment, so anything the app needs at runtime must be in dependencies.

    You can override the detected commands on the component’s settings page under Commands, which is useful for monorepos or for a TypeScript app that needs npm run build followed by node dist/server.js.

    Environment variables and secrets

    Open the component, then Settings, then Environment Variables. Add keys like DATABASE_URL and tick Encrypt for anything sensitive. App Platform sets PORT itself, which is why the sample app reads it from the environment rather than hardcoding 3000. If you attach a managed database from the Add Resource menu, App Platform injects its connection string as a bindable variable such as ${db.DATABASE_URL}.

    Scaling

    Under the component’s Resources tab you choose an instance size and a fixed instance count, or turn on autoscaling with a minimum and maximum count and a CPU target. App Platform only routes traffic to a new revision after its health check passes, so set the health check path to /health from the sample app.

    Logs and deploy history

    Runtime logs stream in the Runtime Logs tab, and each deploy has its own build log. From a terminal you can tail them with doctl apps logs APP_ID --type run --follow after installing the doctl CLI. Log forwarding to an external service is available from the app settings if you need longer retention.

    Droplet vs App Platform at a glance

    ConcernDropletApp Platform
    Setup effortAn hour the first time: SSH, nvm, pm2, Nginx, Certbot, ufwMinutes: connect repo, confirm detected settings
    OS patchingYouDigitalOcean
    TLS certificatesCertbot, renewed by a timerAutomatic for custom domains
    Deploysgit pull plus pm2 reload, or a CI job over SSHPush to branch, rolling deploy with health check
    ScalingResize the Droplet, or add a load balancer and more DropletsSlider or autoscaling per component
    Cost at low endCheapest option for one appSlightly more per container, no idle server to pay for otherwise
    Best forFull control, background jobs, unusual runtimes, several apps on one boxWeb APIs and sites with a standard build, small teams

    If you already run other things on a Droplet, such as a forum from our Discourse on DigitalOcean tutorial, adding a Node app behind the same Nginx is cheap. If the Node app is all you host, App Platform removes most of the maintenance. For a static frontend see where you can deploy Vue.js and our Gatsby on Vultr guide.

    Troubleshooting

    502 Bad Gateway from Nginx

    Nginx is up but cannot reach Node. Run pm2 status or systemctl status hello-node to confirm the process is alive, then curl http://127.0.0.1:3000/ on the Droplet. If curl works and Nginx still fails, the proxy_pass port in your server block does not match the app’s PORT.

    App Platform build succeeds but the app never becomes healthy

    Almost always the app is listening on a hardcoded port instead of process.env.PORT, or it is binding to 127.0.0.1 rather than all interfaces. Use app.listen(port) with no host argument. Also check that the health check path returns a 200 within the timeout.

    “Cannot find module” only in production

    Recommended for you:

    How to Use Regular Expressions in Jinja2
    Blog·Sep 9, 2026

    How to Use Regular Expressions in Jinja2

    The package is in devDependencies. App Platform prunes those before deploy, and npm ci --omit=dev skips them on a Droplet. Move it to dependencies. If you truly need devDependencies at runtime on App Platform, set NPM_CONFIG_PRODUCTION=false as documented in the buildpack reference.

    pm2 processes vanish after a reboot

    You ran pm2 startup but not the sudo command it printed, or you added apps after pm2 save. Run the startup command, start your apps, then pm2 save again.

    Certbot fails with a connection or DNS error

    The A record has not propagated, or port 80 is blocked by ufw or a Cloud Firewall. Confirm dig +short example.com returns the Droplet IP and that ufw status shows Nginx Full allowed.

    Frequently asked questions

    Which is cheaper for a small Node.js app, a Droplet or App Platform?

    The entry level Droplet is the cheapest way to host one Node app, and you can put several apps on it. App Platform’s smallest paid container costs a bit more, but you are not paying for an idle server, TLS or patching. For a single low traffic API the difference is a few dollars a month, so decide on maintenance effort rather than price.

    Do I need Nginx if I use pm2?

    Not strictly, but you should use it. Nginx handles TLS termination, HTTP/2, static files, gzip and request buffering far better than Express does, and it lets Node run as an unprivileged user on a high port. Exposing Node directly on port 80 or 443 means running it as root or juggling capabilities.

    How do I run a TypeScript app on App Platform?

    Keep typescript in devDependencies, add a build script that runs tsc, and set the start script to node dist/server.js. The buildpack runs the build script before pruning devDependencies, so the compiled JavaScript ships without the compiler. Set the build and run commands explicitly in the component settings if detection picks the wrong ones.

    Can I deploy to a Droplet automatically from GitHub?

    Yes. A GitHub Actions workflow with an SSH step can run git pull, npm ci --omit=dev and pm2 reload on every push to main. Store the private key as a repository secret and restrict the deploy user’s permissions. It is a few lines of YAML and gives you most of App Platform’s push to deploy flow.

    The bottom line

    Running Node.js on DigitalOcean comes down to one question: do you want to own the server? If yes, a Droplet with nvm, pm2, Nginx, Certbot and ufw is a proven stack that takes about an hour to set up and years to outgrow. If no, App Platform’s Node.js buildpack turns a repo with a start script and an engines field into a running, TLS secured service with rolling deploys.

    Either way, write the app to read PORT from the environment, keep runtime packages in dependencies, and add a health endpoint. Those three habits make the app portable between both paths, so you can start on a cheap Droplet and move to App Platform later, or the other way around, without touching the code.

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Telegram Email Copy Link
    Previous ArticleHow to Restart a Pod in Minikube (4 Methods That Work)
    Next Article How to Filter Posts in WordPress by Category (WP_Query, pre_get_posts, REST API and Blocks)
    Ethan Caldwell

      Ethan Caldwell is GeekBlog's resident Apple specialist, covering the entire Apple ecosystem - iPhone, iPad, Mac, Apple Watch, AirPods and the software that ties them together. A longtime iOS user and gadget collector, Ethan tracks Cupertino's every move, breaking down Apple keynotes, A- and M-series chip benchmarks, iOS feature updates and the rumor mill into clear, practical takes that help readers decide whether the latest Apple hardware is worth the upgrade.

      Related Posts

      11 Mins Read

      South Carolina vs Michigan: Which State Is Better to Live In?

      11 Mins Read

      Are There Cordless Vacuums With Replaceable Batteries?

      12 Mins Read

      How to Use Parabolic SAR (Stop and Reverse) for Day Trading

      10 Mins Read

      Michigan vs Illinois: Which State Is Better to Live In?

      11 Mins Read

      California or Florida: Which State Is Better to Move To?

      11 Mins Read

      Best Front End Development Books to Learn From in 2026

      Top Posts

      How to Use YouTube: A Beginner’s Guide

      July 7, 20265 Views

      Gal Gadot’s Lawyers Spent Six Months on One AI Clause. Then SAG Called Them for Pointers.

      September 2, 20264 Views

      Every iPhone Camera Ranked in 2026 (Best to Worst)

      July 6, 20263 Views
      Stay In Touch
      • Facebook

      Subscribe to Updates

      Get the latest tech news from FooBar about tech, design and biz.

      Most Popular

      How to Change HEIC to JPG on iPhone, Mac, Android and Windows (No Software Needed)

      September 3, 20266 Views

      Gal Gadot’s Lawyers Spent Six Months on One AI Clause. Then SAG Called Them for Pointers.

      September 2, 20265 Views

      How to Spot AI Generated Images in 2026 (The Old Tricks Stopped Working)

      September 3, 20263 Views
      Our Picks

      Instagram Already Has an Off Switch for the Algorithm. It Resets Every Time You Close the App.

      September 12, 2026

      Anthropic Banned Five Groups of Working Scientists. It Says It Cannot Prove Any of Them Meant Harm.

      September 12, 2026

      Google Made Its Best Paid Gemini Feature Free. The Price Is Access to Your Inbox.

      September 12, 2026

      Subscribe to Updates

      Get the latest creative news from FooBar about art, design and business.

      HEICJPG.online - Convert HEIC to JPG online
      Facebook
      • About Us
      • Contact us
      • Privacy Policy
      • Disclaimer
      • Terms and Conditions
      • Editorial Policy
      • Cookie Policy
      © 2026 GeekBlog

      Type above and press Enter to search. Press Esc to cancel.