Server: Ubuntu 24.04 on IONOS (77.68.21.40)
Domain: theplanningmachine.co.uk
adduser ric
usermod -aG sudo ric
Verified with groups ric showing ric : ric sudo.
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.)
In IONOS DNS settings, set A records (not redirects):
| Type | Host | Value |
|---|---|---|
| A | @ | 77.68.21.40 |
| A | www | 77.68.21.40 |
Verified propagation with nslookup until both names returned the VPS IP.
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
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.
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.
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.
The index page uses fetch('/api/answer') with GET and POST buttons, displaying {"answer": 42} — see /var/www/themachine/index.html.
✅ 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
sudo certbot renew --dry-run