← All Notes

Deploying a Quart App with Hypercorn and Systemd

Getting a Python web app running locally is easy. Getting it running reliably in production — with SSL, process management, and auto-restart — takes a bit more work. Here's how I deploy Quart apps on a VPS.

The Stack

  • Quart — async Flask-compatible web framework
  • Hypercorn — ASGI server (supports HTTP/2)
  • Nginx — reverse proxy + SSL termination
  • systemd — process management and auto-restart
  • Let's Encrypt — free SSL certificates via Certbot

Setting Up the VPS

Start with a fresh Ubuntu or Debian server. Update packages and create a non-root user:

apt update && apt upgrade -y
adduser deploy
usermod -aG sudo deploy

Install Python 3.13, Nginx, and Certbot:

apt install python3.13 python3.13-venv nginx certbot python3-certbot-nginx

Application Setup

Clone your repo and set up the virtual environment:

cd /home/deploy
git clone your-repo app
cd app
python3.13 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Systemd Service

Create /etc/systemd/system/myapp.service:

[Unit]
Description=My Quart App
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/app
Environment=PATH=/home/deploy/app/.venv/bin
ExecStart=/home/deploy/app/.venv/bin/hypercorn run:app --bind 0.0.0.0:5002
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl enable myapp
sudo systemctl start myapp

Nginx Configuration

The key is proxying requests to Hypercorn while letting Nginx handle SSL:

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:5002;
        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;
    }
}

Monitoring

Check logs with journalctl:

sudo journalctl -u myapp -f

The app will auto-restart on crash thanks to the Restart=always directive. Combined with Nginx's stability, this setup has been rock-solid in production.