This is the setup we recommend for a mobile or web app backend: one Ubuntu server running the API as a systemd service behind Nginx, with PostgreSQL on the same box. It fits comfortably on a VPS Cub for development and a VPS Bear for a first production launch.
Replace api.example.com with your domain, pointed at the server's IP (how to point a domain).
1. First login and basics
ssh root@YOUR_SERVER_IP
apt update && apt upgrade -y
adduser deploy && usermod -aG sudo deploy
ufw allow OpenSSH && ufw allow 80 && ufw allow 443 && ufw enable
2. PostgreSQL
apt install -y postgresql
sudo -u postgres psql -c "CREATE USER app WITH PASSWORD 'choose-a-long-password';"
sudo -u postgres psql -c "CREATE DATABASE app OWNER app;"
PostgreSQL listens only on localhost by default, which is what you want: the API talks to it locally and nothing from the internet can reach it.
3. The application
FastAPI example (Node is the same shape with npm ci and node server.js):
apt install -y python3-venv git
su - deploy
git clone https://github.com/you/your-api.git ~/api
cd ~/api && python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt uvicorn
Put secrets in ~/api/.env (database URL postgresql://app:password@localhost/app, API keys) and never in Git.
4. Run it as a service
Create /etc/systemd/system/api.service:
[Unit]
Description=API
After=network.target postgresql.service
[Service]
User=deploy
WorkingDirectory=/home/deploy/api
EnvironmentFile=/home/deploy/api/.env
ExecStart=/home/deploy/api/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --workers 2
Restart=always
[Install]
WantedBy=multi-user.target
Then systemctl enable --now api. The API now starts on boot and restarts if it crashes. Logs: journalctl -u api -f.
5. Nginx and HTTPS
apt install -y nginx certbot python3-certbot-nginx
/etc/nginx/sites-available/api:
server {
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
ln -s /etc/nginx/sites-available/api /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginx
certbot --nginx -d api.example.com
Certbot issues a free certificate and renews it automatically. Your API is now served at https://api.example.com.
6. Deploying updates
cd ~/api && git pull && . .venv/bin/activate && pip install -r requirements.txt && sudo systemctl restart api
Wrap that in a script or a GitHub Action over SSH when it gets tedious.
7. Backups
Enable the Automated Backups add-on for a daily copy of the whole server, and additionally dump the database nightly so you can restore just the data:
echo '0 3 * * * pg_dump -U app app | gzip > /home/deploy/backups/app-$(date +\%F).sql.gz' | crontab -u deploy -
Scaling when users arrive
- Upgrade the plan (Upgrade tab, pay the difference, stop/start to apply). Add
--workersequal to the vCPU count. - Move PostgreSQL to its own VPS when the database becomes the bottleneck; change the database URL in
.env. - Add a second API server behind a load balancer or DNS round-robin once a single server is saturated.
Most apps never need step 3. A VPS Grizzly handles thousands of daily active users for a typical mobile backend.