IONOS VPS Setup: From Bare Ubuntu to Live HTTPS Site

Server: Ubuntu 24.04 on IONOS (77.68.21.40)
Domain: theplanningmachine.co.uk

1. Create an admin user (as root)

adduser ric
usermod -aG sudo ric

Verified with groups ric showing ric : ric sudo.

2. Install nginx

sudo apt update
sudo apt install nginx -y
sudo systemctl enable nginx
sudo ufw allow 'Nginx Full'    # ports 80 + 443

Confirmed working at http://77.68.21.40 with the nginx welcome page. (Also checked the IONOS-side firewall allows 80/443.)

3. Point the domain at the server

In IONOS DNS settings, set A records (not redirects):

TypeHostValue
A@77.68.21.40
Awww77.68.21.40

Verified propagation with nslookup until both names returned the VPS IP.

4. Create the nginx site config

Create /etc/nginx/sites-available/themachine:

sudo nano /etc/nginx/sites-available/themachine

Full final contents of the file — HTTPS from certbot, static pages from /var/www/themachine, and the Flask API proxied to Gunicorn on port 8000:

server {
    listen 80;
    listen 443 ssl;
    server_name theplanningmachine.co.uk www.theplanningmachine.co.uk;

    ssl_certificate /etc/letsencrypt/live/theplanningmachine.co.uk/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/theplanningmachine.co.uk/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    root /var/www/themachine;
    index index.html;

    # Static pages (HTML, CSS, JS, images...)
    location / {
        try_files $uri $uri/ =404;
    }

    # Flask API -> Gunicorn on localhost:8000
    location /api/ {
        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-Proto $scheme;
    }
}

(The SSL lines come from the certbot step below — you can put a plain HTTP-only version in first and let certbot edit it, or paste this in after getting the certificate.)

Enable the site (replacing the default) and activate the config:

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

5. Enable HTTPS with Let's Encrypt

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d theplanningmachine.co.uk -d www.theplanningmachine.co.uk

Chose the Redirect option (HTTP → HTTPS). Certificates now auto-renew. Result: valid padlock on both https://theplanningmachine.co.uk and https://www.theplanningmachine.co.uk.

6. Flask API in a virtualenv (returns 42)

Using virtualenv:

sudo apt install virtualenv -y
mkdir -p ~/flaskapi
cd ~/flaskapi
virtualenv -p /usr/bin/python3 venv
source venv/bin/activate
pip install flask gunicorn

~/flaskapi/api.py:

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/api/answer", methods=["GET", "POST"])
def answer():
    return jsonify(answer=42)

Run it permanently with systemd — /etc/systemd/system/flaskapi.service:

[Unit]
Description=Flask API with Gunicorn
After=network.target

[Service]
User=ric
Group=www-data
WorkingDirectory=/home/ric/flaskapi
ExecStart=/home/ric/flaskapi/venv/bin/gunicorn --workers 2 --bind 127.0.0.1:8000 api:app

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start flaskapi
sudo systemctl enable flaskapi

Handy commands: restart after code changes with sudo systemctl restart flaskapi; view logs with journalctl -u flaskapi -f.

7. Make content editable without sudo

Ownership (the actual permission fix):

sudo chown -R ric:ric /var/www/themachine

Plus convenience symlinks in ric's home:

ln -s /var/www/themachine ~/themachine
ln -s /var/www ~/www

Now nano ~/themachine/index.html edits the live site directly — changes appear on refresh with zero sudo.

8. Test page that calls the API

The index page uses fetch('/api/answer') with GET and POST buttons, displaying {"answer": 42} — see /var/www/themachine/index.html.

Current state

✅ Admin user ric (no more root work) · ✅ nginx serving over HTTP + HTTPS · ✅ Valid auto-renewing Let's Encrypt cert · ✅ Flask API in a virtualenv behind Gunicorn · ✅ Live editable site at https://www.theplanningmachine.co.uk

Still on the to-do list (when you're ready)

  1. Verify cert renewal — sudo certbot renew --dry-run