DEV Community

Arthur
Arthur

Posted on

How to Move a Node API From a Free Host to a VPS (Step by Step)

Hi, I'm Arthur.

Free hosting is a great way to get your first backend online.

You can build an API, connect a frontend, test your idea, and start getting users without worrying about server costs.

But eventually, you may notice things like cold starts, memory limits, sleeping applications, restricted background processes, or simply no SSH access when something goes wrong.

That's usually when a VPS starts making sense.

Instead of the hosting platform deciding how your application runs, you get your own Linux environment and control over the software, processes, logs, and configuration.

In this guide, I'll move a small Node.js/Express API from a free hosting environment to an Ubuntu VPS.

I'll cover:

  • Creating a non-root user
  • Installing Node.js and Nginx
  • Deploying the application
  • Running Node with systemd
  • Configuring Nginx
  • Adding HTTPS
  • Creating a simple deployment script

Before You Start

You'll need:

  • An Ubuntu 22.04 or 24.04 VPS
  • Root or sudo access
  • A domain or subdomain
  • Your Node.js application in a Git repository

I'll use this example domain throughout the guide:

api.example.com
Enter fullscreen mode Exit fullscreen mode

Before continuing, create an A record pointing your domain to the VPS IP address.

DNS changes can take some time, and you'll need the domain working before configuring HTTPS.

If you're looking for a small VPS for testing, HelloServer is one option you can compare. The important thing is that your VPS gives you the root or sudo access needed for the setup below.


1. Create a Non-Root User

You don't want to run your application as root.

Connect to your server:

ssh root@YOUR_SERVER_IP
Enter fullscreen mode Exit fullscreen mode

Create a new user:

adduser deploy
usermod -aG sudo deploy
Enter fullscreen mode Exit fullscreen mode

Copy your SSH configuration:

rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
Enter fullscreen mode Exit fullscreen mode

Now open a second terminal and test:

ssh deploy@YOUR_SERVER_IP
Enter fullscreen mode Exit fullscreen mode

Make sure this works before changing your SSH configuration.

Once you've confirmed the new account works, edit:

sudo nano /etc/ssh/sshd_config
Enter fullscreen mode Exit fullscreen mode

Set:

PermitRootLogin no
PasswordAuthentication no
Enter fullscreen mode Exit fullscreen mode

Then restart SSH:

sudo systemctl restart ssh
Enter fullscreen mode Exit fullscreen mode

Keep your existing SSH session open while testing the new login.

That way, if you make a configuration mistake, you still have a working session.


2. Install Node.js and Nginx

Update the server:

sudo apt update && sudo apt upgrade -y
Enter fullscreen mode Exit fullscreen mode

Install the Node.js LTS repository:

curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
Enter fullscreen mode Exit fullscreen mode

Then install Node.js, Nginx, and Git:

sudo apt install -y nodejs nginx git
Enter fullscreen mode Exit fullscreen mode

Check the versions:

node --version
npm --version
nginx -v
Enter fullscreen mode Exit fullscreen mode

Configure the Firewall

If UFW isn't already configured:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Enter fullscreen mode Exit fullscreen mode

The order matters.

Always allow SSH before enabling the firewall, otherwise you can accidentally lock yourself out of the server.


3. Put Your Application on the Server

Move into the deploy user's home directory:

cd /home/deploy
Enter fullscreen mode Exit fullscreen mode

Clone your application:

git clone https://github.com/YOUR_USER/YOUR_REPO.git api
Enter fullscreen mode Exit fullscreen mode

Enter the project:

cd api
Enter fullscreen mode Exit fullscreen mode

Install production dependencies:

npm ci --omit=dev
Enter fullscreen mode Exit fullscreen mode

For testing, your Express application might look something like this:

const express = require('express');

const app = express();

app.get('/health', (req, res) => {
  res.json({ ok: true });
});

app.listen(process.env.PORT || 3000, '127.0.0.1');
Enter fullscreen mode Exit fullscreen mode

Notice that I'm binding the application to:

127.0.0.1
Enter fullscreen mode Exit fullscreen mode

instead of:

0.0.0.0
Enter fullscreen mode Exit fullscreen mode

That's intentional.

Nginx will communicate with Node locally, while the public internet only reaches Nginx.


4. Keep Node Running With systemd

On many free hosting platforms, the provider automatically starts your application again if it crashes.

On your own VPS, you need to configure that yourself.

That's where systemd comes in.

First, create a file for environment variables:

sudo nano /etc/api.env
Enter fullscreen mode Exit fullscreen mode

For example:

DATABASE_URL=postgres://...
JWT_SECRET=change-this
Enter fullscreen mode Exit fullscreen mode

Protect the file:

sudo chmod 600 /etc/api.env
Enter fullscreen mode Exit fullscreen mode

Now create the systemd service:

sudo nano /etc/systemd/system/api.service
Enter fullscreen mode Exit fullscreen mode

Add:

[Unit]
Description=My Node API
After=network.target

[Service]
User=deploy
WorkingDirectory=/home/deploy/api
EnvironmentFile=/etc/api.env
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

Reload systemd:

sudo systemctl daemon-reload
Enter fullscreen mode Exit fullscreen mode

Start the API:

sudo systemctl enable --now api
Enter fullscreen mode Exit fullscreen mode

Check the logs:

journalctl -u api -f
Enter fullscreen mode Exit fullscreen mode

You can also test the API locally:

curl http://127.0.0.1:3000/health
Enter fullscreen mode Exit fullscreen mode

You should get:

{"ok":true}
Enter fullscreen mode Exit fullscreen mode

The enable command is important because it makes the service start automatically after a server reboot.


5. Put Nginx in Front of Node

Node is running on port 3000, but we don't want users accessing that port directly.

Nginx will act as the reverse proxy.

Create a new Nginx configuration:

sudo nano /etc/nginx/sites-available/api
Enter fullscreen mode Exit fullscreen mode

Add:

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Enable the configuration:

sudo ln -s /etc/nginx/sites-available/api /etc/nginx/sites-enabled/
Enter fullscreen mode Exit fullscreen mode

Test it:

sudo nginx -t
Enter fullscreen mode Exit fullscreen mode

If the configuration is valid:

sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

Now visit:

http://api.example.com
Enter fullscreen mode Exit fullscreen mode

Your request should reach Nginx first, which then forwards it to Node.

If your Express application uses secure cookies, rate limiting, or other features that depend on the original request information, you may also need:

app.set('trust proxy', 1);
Enter fullscreen mode Exit fullscreen mode

6. Add HTTPS

At this point, the API works over HTTP.

The next step is HTTPS.

Install Certbot:

sudo apt install -y certbot python3-certbot-nginx
Enter fullscreen mode Exit fullscreen mode

Then request a certificate:

sudo certbot --nginx -d api.example.com
Enter fullscreen mode Exit fullscreen mode

Certbot can update the Nginx configuration automatically.

Afterward, test renewal:

sudo certbot renew --dry-run
Enter fullscreen mode Exit fullscreen mode

If everything is configured correctly, your API should now be available over HTTPS.


7. Create a Simple Deploy Script

Manually pulling the repository and restarting the application every time gets old quickly.

Create:

nano /home/deploy/deploy.sh
Enter fullscreen mode Exit fullscreen mode

Add:

#!/usr/bin/env bash

set -e

cd /home/deploy/api

git pull origin main
npm ci --omit=dev

sudo systemctl restart api

echo "Deployed."
Enter fullscreen mode Exit fullscreen mode

Make it executable:

chmod +x /home/deploy/deploy.sh
Enter fullscreen mode Exit fullscreen mode

Now you can deploy with:

./deploy.sh
Enter fullscreen mode Exit fullscreen mode

If you want to run it remotely:

ssh deploy@YOUR_SERVER_IP ./deploy.sh
Enter fullscreen mode Exit fullscreen mode

Later, you can connect this script to GitHub Actions and turn the process into a basic CI/CD workflow.


What You Now Have

At this point, the setup looks roughly like this:

Internet
   |
   v
Nginx :443
   |
   v
Node.js :3000
   |
   v
Your API
   |
   v
Database
Enter fullscreen mode Exit fullscreen mode

Nginx handles the public traffic and HTTPS.

Node handles the application.

systemd makes sure the application stays running.

And your deploy script handles basic updates.

It's a simple setup, but it's enough for many small APIs and personal projects.


What You Take Responsibility For With a VPS

Moving from free hosting to a VPS gives you more control, but that also means more responsibility.

Updates

Keep the operating system and installed packages updated.

You can install automatic security updates with:

sudo apt install unattended-upgrades
Enter fullscreen mode Exit fullscreen mode

Backups

If your database is running on the same server, don't assume the VPS itself is a backup.

For example, PostgreSQL backups can be created with:

pg_dump DATABASE_NAME > backup.sql
Enter fullscreen mode Exit fullscreen mode

But don't keep your only backup on the same server.

If the server disappears, the backup disappears with it.

Monitoring

You don't need an expensive monitoring system for a small project.

A simple uptime monitor checking:

https://api.example.com/health
Enter fullscreen mode Exit fullscreen mode

is a good starting point.


When Should You Actually Move to a VPS?

I wouldn't automatically recommend a VPS for every project.

If you're building a prototype and the free tier works, stay there.

Free hosting can be perfectly fine for learning and testing.

I'd consider moving when:

  • Your API keeps going to sleep
  • Cold starts are affecting users
  • You need background workers
  • You need a database alongside the API
  • You need SSH access
  • You need more control over the server
  • Your application is consistently hitting CPU or memory limits

The biggest advantage isn't simply getting more resources.

It's getting control.

You decide what runs.

You decide how it is configured.

And when something breaks, you can actually log into the machine and investigate it.

That's the part I appreciate most about having a VPS.

If you're practicing this setup yourself, a small VPS from HelloServer or another provider can be enough to go through the entire process without needing a large production server.

Start small, break things, rebuild the server, and learn what each component actually does.

That's usually more valuable than simply following a deployment tutorial once.

Top comments (0)