Das Deployen eines Flask PostgreSQL Nginx-Stacks ist der Standardweg, um eine Python-Webanwendung vom Entwicklungsrechner auf einen produktiven Server zu bringen. Diese Anleitung durchläuft jede Schicht: eine PostgreSQL-Datenbank, Gunicorn als WSGI-Applikationsserver, systemd für den dauerhaften Betrieb und Nginx als Reverse Proxy, der dem Internet zugewandt ist. Am Ende hast du ein gehärtetes, produktionsreifes Setup, auf dem du aufbauen kannst.

Voraussetzungen

  • Ubuntu 22.04 oder 24.04 Server (root- oder sudo-Zugang)
  • Ein Domain-Name, der auf die Server-IP zeigt (optional, für SSL später)
  • Grundlegende Kenntnisse der Linux-Kommandozeile und Python
  • Eine Flask-Anwendung, die deployt werden soll (oder das minimale Beispiel aus dieser Anleitung verwenden)

Systemabhängigkeiten installieren

Beginne damit, den Paketindex zu aktualisieren und alles in einem Durchgang zu installieren:

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv \
    postgresql postgresql-contrib \
    nginx curl

Installationen überprüfen:

python3 --version    # Python 3.10+
psql --version       # psql 14+
nginx -v             # nginx/1.22+

PostgreSQL einrichten

Datenbank und Benutzer erstellen

Wechsle zum postgres-Systemkonto und öffne die interaktive Shell:

sudo -u postgres psql

Erstelle innerhalb von psql einen dedizierten Benutzer und eine Datenbank. Verwende niemals den Standard-postgres-Superuser für Anwendungsverbindungen:

CREATE USER flaskapp WITH PASSWORD 'StrongPassw0rd!';
CREATE DATABASE flaskdb OWNER flaskapp;
GRANT ALL PRIVILEGES ON DATABASE flaskdb TO flaskapp;
\q

Verbindung testen

Prüfe die Verbindung, bevor du sie in Flask einbindest:

psql -U flaskapp -h 127.0.0.1 -d flaskdb -c "\conninfo"

Du solltest eine Bestätigung sehen, dass die Verbindung erfolgreich war. Falls nicht, prüfe, ob pg_hba.conf md5- oder scram-sha-256-Authentifizierung für lokale Verbindungen erlaubt:

sudo nano /etc/postgresql/14/main/pg_hba.conf
# Stelle sicher, dass diese Zeile vorhanden ist:
# host  all  all  127.0.0.1/32  scram-sha-256
sudo systemctl restart postgresql

Die Flask-Anwendung deployen

Projektstruktur

Platziere die Anwendung in /srv/flaskapp. Die Verwendung von /srv hält Web-Apps von Systemdateien getrennt:

/srv/flaskapp/
├── venv/
├── app/
│   ├── __init__.py
│   └── models.py
├── wsgi.py
├── .env
└── requirements.txt

Virtualumgebung erstellen

sudo mkdir -p /srv/flaskapp
sudo chown $USER:$USER /srv/flaskapp
cd /srv/flaskapp
python3 -m venv venv
source venv/bin/activate

Python-Pakete installieren

pip install flask flask-sqlalchemy psycopg2-binary gunicorn python-dotenv
pip freeze > requirements.txt

Minimale Flask-Anwendung

Erstelle app/__init__.py:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URL"]
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
    app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]

    db.init_app(app)

    @app.route("/")
    def index():
        return "Flask + PostgreSQL + Nginx is running."

    return app

Erstelle wsgi.py (den Einstiegspunkt, den Gunicorn aufruft):

from app import create_app

app = create_app()

if __name__ == "__main__":
    app.run()

Umgebungsvariablen

Speichere Secrets in /srv/flaskapp/.env. Committe diese Datei niemals in die Versionskontrolle:

DATABASE_URL=postgresql://flaskapp:StrongPassw0rd!@127.0.0.1:5432/flaskdb
SECRET_KEY=replace-with-a-long-random-string
FLASK_ENV=production

Berechtigungen sofort einschränken:

chmod 600 /srv/flaskapp/.env

Gunicorn manuell testen

Bevor systemd eingerichtet wird, prüfe, ob Gunicorn die App starten kann:

source /srv/flaskapp/venv/bin/activate
cd /srv/flaskapp
gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi:app

Öffne http://deine-server-ip:8000 — du solltest die Flask-Antwort sehen. Wenn ja, stoppe Gunicorn mit Ctrl+C und fahre mit systemd fort.

Gunicorn als systemd-Dienst

Gunicorn unter systemd zu betreiben bietet automatische Neustarts bei Absturz, Start beim Booten und zentrale Protokollierung über journalctl.

Worker-Anzahl wählen

Server-RAMEmpfohlene Worker
512 MB2
1 GB3
2 GB5
4 GB+9

Eine gängige Formel: (2 × CPU-Kerne) + 1. Drei Worker sind ein sicherer Standardwert für die meisten kleinen VPS-Instanzen.

Service-Unit-Datei erstellen

sudo nano /etc/systemd/system/flaskapp.service

Folgendes einfügen — Pfade und Benutzer nach Bedarf anpassen:

[Unit]
Description=Gunicorn instance for Flask app
After=network.target postgresql.service

[Service]
User=www-data
Group=www-data
WorkingDirectory=/srv/flaskapp
EnvironmentFile=/srv/flaskapp/.env
ExecStart=/srv/flaskapp/venv/bin/gunicorn \
    --workers 3 \
    --bind unix:/run/flaskapp/gunicorn.sock \
    --access-logfile /var/log/flaskapp/access.log \
    --error-logfile /var/log/flaskapp/error.log \
    wsgi:app
RuntimeDirectory=flaskapp
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

Wichtige Felder erklärt:

  • EnvironmentFile — lädt deine .env-Secrets in die Prozessumgebung
  • RuntimeDirectory — systemd erstellt /run/flaskapp/ automatisch und bereinigt es beim Stoppen
  • After=postgresql.service — stellt sicher, dass die Datenbank läuft, bevor die App startet
  • unix:-Socket — schneller als TCP für die lokale Nginx-zu-Gunicorn-Kommunikation

Log-Verzeichnis erstellen und Besitzer anpassen:

sudo mkdir -p /var/log/flaskapp
sudo chown www-data:www-data /var/log/flaskapp
sudo chown -R www-data:www-data /srv/flaskapp

Dienst aktivieren und starten:

sudo systemctl daemon-reload
sudo systemctl enable flaskapp
sudo systemctl start flaskapp
sudo systemctl status flaskapp

Es sollte Active: active (running) erscheinen. Prüfen, ob der Socket erstellt wurde:

ls -la /run/flaskapp/gunicorn.sock

Nginx als Reverse Proxy konfigurieren

Nginx sitzt vor Gunicorn, verarbeitet eingehenden HTTP/HTTPS-Traffic und leitet Anwendungsanfragen an den Unix-Socket weiter.

Site-Konfiguration erstellen

sudo nano /etc/nginx/sites-available/flaskapp
server {
    listen 80;
    server_name example.com www.example.com;

    # Statische Dateien werden direkt von Nginx ausgeliefert — kein Gunicorn-Overhead
    location /static/ {
        alias /srv/flaskapp/app/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location / {
        proxy_pass http://unix:/run/flaskapp/gunicorn.sock;
        proxy_http_version 1.1;

        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;

        # Verhindert, dass langsame Clients Gunicorn-Worker blockieren
        proxy_read_timeout 90;
        proxy_connect_timeout 90;
    }
}

Site aktivieren und Konfiguration testen:

sudo ln -s /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

SSL mit Certbot hinzufügen

Für den Produktionsbetrieb TLS immer bei Nginx terminieren. Certbot automatisiert die Zertifikatsausstellung und -erneuerung:

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

Certbot schreibt die Nginx-Konfiguration mit SSL-Direktiven um und richtet die automatische Erneuerung über einen systemd-Timer ein.

Praxisbeispiel

Du betreibst eine SaaS-Anwendung auf Basis von Flask, die Kundenbestellungen in PostgreSQL speichert. Der Traffic erreicht mittags mit ca. 200 gleichzeitigen Nutzern seinen Höhepunkt. Ohne Nginx-Pufferung würden langsame mobile Clients Gunicorn-Worker über 30 Sekunden lang belegen und Request-Timeouts verursachen. Mit dem Setup aus dieser Anleitung:

  1. Nginx akzeptiert die Verbindung sofort und puffert den langsamen Client
  2. Es leitet den vollständigen Request-Body in Millisekunden an Gunicorn weiter
  3. Gunicorn verarbeitet die Anfrage und schreibt die Antwort zurück an Nginx
  4. Nginx streamt die Antwort an den langsamen Client, während Gunicorn bereits für die nächste Anfrage frei ist

Drei Gunicorn-Worker können nun weit mehr als drei gleichzeitige Benutzer bedienen. Füge weitere Worker hinzu oder wechsle zu gevent-Async-Workern, wenn du mehr Skalierung benötigst.

Fallstricke und Sonderfälle

Unix-Socket-Berechtigungen: Der Nginx-www-data-Benutzer muss den Gunicorn-Socket lesen können. Beide unter www-data auszuführen (wie oben gezeigt) ist die einfachste Lösung. Falls sie als unterschiedliche Benutzer laufen, www-data zur App-Gruppe hinzufügen und den Socket auf 660 setzen.

DATABASE_URL mit Sonderzeichen: Passwörter mit @, / oder % müssen in der URL prozent-kodiert werden. Alternativ individuelle Umgebungsvariablen (PGUSER, PGPASSWORD usw.) verwenden und die URL in Python zusammenbauen.

Fehlendes python-dotenv in der Produktion: EnvironmentFile in systemd liest die .env direkt — für den systemd-Pfad wird python-dotenv nicht benötigt. Es wird gebraucht, wenn flask run lokal ausgeführt wird.

App-Factory-Muster vs. Modul-Level-App: Gunicorns wsgi:app erwartet ein Callable. Mit dem Factory-Muster die erstellte App auf Modulebene in wsgi.py wie oben gezeigt bereitstellen. create_app() nicht innerhalb einer Funktion aufrufen, ohne das Ergebnis zu exponieren.

PostgreSQL max_connections: Jeder Gunicorn-Worker öffnet seine eigene Datenbankverbindung. Mit 9 Workern auf 4 Servern könnte das Standard-100-Verbindungslimit erreicht werden. PgBouncer als Connection-Pooler vor PostgreSQL einsetzen bei Deployments mit vielen Workern.

Fehlerbehebung

502 Bad Gateway von Nginx: Prüfen, ob der Gunicorn-Dienst läuft und der Socket vorhanden ist:

sudo systemctl status flaskapp
ls /run/flaskapp/gunicorn.sock

Auch Nginx-Fehlerprotokolle prüfen: sudo tail -50 /var/log/nginx/error.log

[CRITICAL] WORKER TIMEOUT in Gunicorn-Logs: Eine Anfrage dauert länger als das Standard-30-Sekunden-Timeout. Mit --timeout 120 erhöhen oder die auslösende langsame Abfrage optimieren.

OperationalError: could not connect to server: PostgreSQL läuft nicht oder die DATABASE_URL ist falsch. Den Verbindungsstring direkt mit psql $DATABASE_URL testen.

Permission denied am Socket: Benutzer-Mismatch zwischen Nginx und Gunicorn. Prüfen, ob beide als www-data laufen:

ps aux | grep -E "gunicorn|nginx" | awk '{print $1, $11}'

Statische Dateien liefern 404: Der alias-Pfad in Nginx muss dem tatsächlichen statischen Verzeichnis entsprechen. Hinweis: alias erfordert einen abschließenden Schrägstrich; root nicht.

Zusammenfassung

  • PostgreSQL installieren, einen dedizierten Benutzer und eine Datenbank erstellen und .env-Dateiberechtigungen einschränken, um Zugangsdaten zu schützen
  • Flask unter Gunicorn mit einem Unix-Socket betreiben — Gunicorn niemals direkt dem Internet aussetzen
  • Eine systemd-[Service]-Unit mit EnvironmentFile und RuntimeDirectory für sauberes Prozessmanagement und automatischen Neustart verwenden
  • Nginx so konfigurieren, dass / an den Gunicorn-Socket weitergeleitet und /static/ direkt ausgeliefert wird, um unnötige Applikationsserver-Last zu eliminieren
  • SSL über Certbot sofort hinzufügen — reines HTTP ist im Produktionsbetrieb nie akzeptabel
  • Auf Verbindungspool-Erschöpfung bei PostgreSQL beim Skalieren von Gunicorn-Workern achten; bei Bedarf PgBouncer einsetzen

Verwandte Artikel