Your First VPS: Hosting a Small Python Web App


Hello, tech enthusiasts! ๐Ÿ‘‹

You built a small Python tool. Maybe it shows a sales summary, or it lets your team look up invoice status. It works great on your laptop. But when your laptop is off, nobody can use it.

The answer is a VPS, a Virtual Private Server. Think of it as renting a small computer in a data centre. It is always on and always online, and small ones cost only a few dollars a month.

Today we will rent one, connect safely, and put a tiny Flask app online.

What you need

  • A small VPS with Ubuntu 24.04 (1 CPU and 1 GB of memory is enough)
  • A terminal on your computer (Windows 10 and 11 have ssh built in)
  • Optional: a domain name, like example.com

Step 1: Rent the server and connect with SSH keys

Pick any hosting provider and create the smallest Ubuntu server. You will get its IP address.

SSH is a safe tunnel to type commands on the server. Instead of a password, we use an SSH key. A key is like a house key that only you have: much harder to guess than a password.

On your own computer, create a key:

ssh-keygen -t ed25519

Most providers let you paste your public key (the file ending in .pub) when you create the server. Do that. Then connect (replace the address with your server’s IP):

ssh root@203.0.113.10

Step 2: Update and create a normal user

First, install all updates:

apt update && apt upgrade -y

Working as root all the time is risky. Create a normal user with admin rights and copy your key to it:

adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

Log out and connect again as ssh deploy@203.0.113.10. Once that works, turn off password logins. Open /etc/ssh/sshd_config, set PasswordAuthentication no, check that no file in /etc/ssh/sshd_config.d/ sets it back to yes, then run sudo systemctl restart ssh.

Step 3: Create the Python app

Install Python tools and nginx:

sudo apt install -y python3-venv nginx

Make a folder and a virtual environment. A venv is a private box for your app’s libraries, so they do not mix with the system.

mkdir ~/myapp && cd ~/myapp
python3 -m venv venv
source venv/bin/activate
pip install flask gunicorn

Create app.py:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello from my first server!"

Step 4: Run it with gunicorn and systemd

Flask’s built-in server is only for testing. Gunicorn is a real web server for Python apps. Test it:

gunicorn --workers 2 --bind 127.0.0.1:8000 app:app

Press Ctrl+C to stop. Now we want it to start by itself, even after a reboot. That is the job of systemd, Linux’s service manager. Create /etc/systemd/system/myapp.service with sudo nano:

[Unit]
Description=My Flask app
After=network.target

[Service]
User=deploy
WorkingDirectory=/home/deploy/myapp
ExecStart=/home/deploy/myapp/venv/bin/gunicorn --workers 2 --bind 127.0.0.1:8000 app:app
Restart=always

[Install]
WantedBy=multi-user.target

Start it and enable it at boot:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

Step 5: Put nginx in front

Nginx is like a receptionist. It receives visitors on the normal web port (80) and passes them to gunicorn. It is fast and safe. Create /etc/nginx/sites-available/myapp:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        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 it, remove the default site, test and reload:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Step 6: Turn on the firewall

A firewall is a door guard. It only lets in the traffic you allow. Allow SSH first, so you do not lock yourself out:

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Now open http://203.0.113.10 (or your domain) in a browser. You should see your message.

Step 7: Add HTTPS

If you have a domain pointing to your server, get a free certificate with certbot:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com

Certbot changes the nginx file for you and renews the certificate automatically.

Common problems

  • 502 Bad Gateway โ†’ Gunicorn is not running. Check sudo systemctl status myapp and journalctl -u myapp.
  • Locked out after enabling ufw โ†’ Use your provider’s web console, then run sudo ufw allow 22/tcp.
  • Code changes do not show โ†’ Restart the app with sudo systemctl restart myapp.
  • certbot fails โ†’ Your domain’s DNS must point to the server’s IP, and port 80 must be open.

Wrap-up

You now have a real server, with key-only SSH, a firewall, a proper app server and a web server in front. That is the same basic setup many big websites use.

Next idea: turn on automatic security updates with sudo apt install unattended-upgrades, so your server stays patched while you sleep.