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
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
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
Create a new user:
adduser deploy
usermod -aG sudo deploy
Copy your SSH configuration:
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
Now open a second terminal and test:
ssh deploy@YOUR_SERVER_IP
Make sure this works before changing your SSH configuration.
Once you've confirmed the new account works, edit:
sudo nano /etc/ssh/sshd_config
Set:
PermitRootLogin no
PasswordAuthentication no
Then restart SSH:
sudo systemctl restart ssh
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
Install the Node.js LTS repository:
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
Then install Node.js, Nginx, and Git:
sudo apt install -y nodejs nginx git
Check the versions:
node --version
npm --version
nginx -v
Configure the Firewall
If UFW isn't already configured:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
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
Clone your application:
git clone https://github.com/YOUR_USER/YOUR_REPO.git api
Enter the project:
cd api
Install production dependencies:
npm ci --omit=dev
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');
Notice that I'm binding the application to:
127.0.0.1
instead of:
0.0.0.0
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
For example:
DATABASE_URL=postgres://...
JWT_SECRET=change-this
Protect the file:
sudo chmod 600 /etc/api.env
Now create the systemd service:
sudo nano /etc/systemd/system/api.service
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
Reload systemd:
sudo systemctl daemon-reload
Start the API:
sudo systemctl enable --now api
Check the logs:
journalctl -u api -f
You can also test the API locally:
curl http://127.0.0.1:3000/health
You should get:
{"ok":true}
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
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;
}
}
Enable the configuration:
sudo ln -s /etc/nginx/sites-available/api /etc/nginx/sites-enabled/
Test it:
sudo nginx -t
If the configuration is valid:
sudo systemctl reload nginx
Now visit:
http://api.example.com
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);
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
Then request a certificate:
sudo certbot --nginx -d api.example.com
Certbot can update the Nginx configuration automatically.
Afterward, test renewal:
sudo certbot renew --dry-run
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
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."
Make it executable:
chmod +x /home/deploy/deploy.sh
Now you can deploy with:
./deploy.sh
If you want to run it remotely:
ssh deploy@YOUR_SERVER_IP ./deploy.sh
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
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
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
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
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.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.