Deploy Your Node.js API on a VPS: A Step-by-Step Guide That Proves Infrastructure Competence to Recruiters
A hiring manager reviewing two identical candidates will choose the one whose project is deployed on a VPS with Nginx, SSL, and a systemd service file over the one whose project is deployed on Vercel. The reason is not technical elitism. It is that the VPS deployment proves you can configure a production server, which means you require less onboarding before you can contribute to the company's infrastructure. The Vercel deployment proves you can connect a GitHub repo to a platform. Both are valuable. Only one differentiates you. This guide walks through the exact terminal commands to take a Node.js API from localhost to a public URL on your own server, with no skipped steps and no assumed knowledge.
Step 1: Provision a VPS (we use a ₹500/month DigitalOcean droplet or Oracle Cloud free tier). Step 2: Secure the server (SSH key, firewall, non-root user). Step 3: Install Node.js and run your API. Step 4: Configure Nginx as a reverse proxy. Step 5: Set up SSL with Certbot (free HTTPS). Step 6: Create a systemd service to keep your API running after reboots. Step 7: Point a domain to your server. Every step has the exact command. If a command fails, the troubleshooting section tells you why and how to fix it.
Step 1: Provision the VPS and Connect via SSH
Sign up for DigitalOcean, create a droplet with Ubuntu 22.04 LTS, choose the ₹500/month plan (1 GB RAM, 1 vCPU, 25 GB SSD). Add your SSH public key during creation. Once the droplet is provisioned, DigitalOcean emails you the IP address. Connect from your terminal: ssh root@YOUR_SERVER_IP. If this fails with "Permission denied," your SSH key was not added to the droplet. Destroy it and recreate with the key. Do not proceed until SSH works.
Once connected as root, create a non-root user: adduser deploy, then usermod -aG sudo deploy. Copy your SSH key to the new user: rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy. Log out and test SSH as the new user: ssh deploy@YOUR_SERVER_IP. If it works, you are ready. Disable root SSH login: edit /etc/ssh/sshd_config, set PermitRootLogin no, then sudo systemctl restart sshd. This prevents brute-force attacks on the root account, which begin within minutes of a new VPS going online.
Step 2: Configure the Firewall
Run: sudo ufw allow OpenSSH, then sudo ufw allow 'Nginx Full', then sudo ufw enable. This opens ports 22 (SSH), 80 (HTTP), and 443 (HTTPS). Verify: sudo ufw status should show all three rules as active. If you accidentally lock yourself out (close port 22 before allowing OpenSSH), you will need to use the DigitalOcean console to recover. This is why we run ufw allow OpenSSH before ufw enable.
Step 3: Install Node.js and Run Your API
Install Node.js using the NodeSource repository for the LTS version: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -, then sudo apt-get install -y nodejs. Verify: node --version and npm --version. Clone your project: git clone YOUR_REPO_URL, cd your-project, npm install. Create a .env file with your production database URL and secrets. Test: node server.js. If the API starts on port 3000 without errors, stop it (Ctrl+C). You now have a running application. You need it to run as a background service and be accessible through a proper web server.
NGINX REVERSE PROXY CONFIGURATION — COPY THIS FILE
| DIRECTIVE | PURPOSE | VALUE |
|---|---|---|
| server_name | Domain name the server responds to. | api.yourdomain.com |
| proxy_pass | Forwards requests to your Node.js process. | http://localhost:3000 |
| proxy_set_header Host | Passes the original host header so your app knows the domain. | $host |
| proxy_set_header X-Real-IP | Passes the client IP so your logs show real user IPs, not 127.0.0.1. | $remote_addr |
| proxy_set_header X-Forwarded-For | Passes the full proxy chain for debugging. | $proxy_add_x_forwarded_for |
Step 4: Configure Nginx
Install Nginx: sudo apt install nginx -y. Create a config file: sudo nano /etc/nginx/sites-available/your-api. Paste the configuration block with proxy_pass pointing to http://localhost:3000 and server_name set to your domain or server IP. Enable the site: sudo ln -s /etc/nginx/sites-available/your-api /etc/nginx/sites-enabled/. Test the config: sudo nginx -t. If it says "syntax is ok" and "test is successful," reload: sudo systemctl reload nginx. Visit http://YOUR_SERVER_IP. You should see your API responding. If you see "502 Bad Gateway," your Node.js process is not running on port 3000. Start it manually and test again. If you see "404 Not Found," your Nginx config is routing to the wrong location block.
Step 5: SSL with Certbot
If you have a domain pointed to your server IP: sudo apt install certbot python3-certbot-nginx -y, then sudo certbot --nginx -d yourdomain.com. Certbot automatically modifies your Nginx config to serve HTTPS and sets up auto-renewal. Test auto-renewal: sudo certbot renew --dry-run. If you do not have a domain, skip SSL and use the server IP over HTTP. A domain costs ₹500–1,000 per year and makes your deployment look professional. Buy one. It is worth it.
Step 6: systemd Service File
Create a service file: sudo nano /etc/systemd/system/your-api.service. The file needs: Description, After=network.target, User=deploy, WorkingDirectory=/home/deploy/your-project, ExecStart=/usr/bin/node server.js, Restart=always, and EnvironmentFile for your .env. Enable and start: sudo systemctl enable your-api, sudo systemctl start your-api. Check status: sudo systemctl status your-api should show "active (running)." Your API now survives reboots and crashes. If it crashes, systemd restarts it automatically. This is the final step that separates a portfolio project from a production service.
502 Bad Gateway: Your Node.js process is not running. Check: sudo systemctl status your-api. If it is not active, check the logs: sudo journalctl -u your-api -n 50. The most common cause: a missing environment variable or database connection string. Connection Refused: Your firewall is blocking the port. Check: sudo ufw status. Ensure Nginx Full is allowed. Site Shows Default Nginx Page: Your site config is not enabled. Check: ls /etc/nginx/sites-enabled/. If your config is not listed, run the ln -s command again from Step 4. Then test and reload Nginx.