Deine Django-Anwendung läuft auf dem Laptop reibungslos, aber nachdem du sie auf den Server hochgeladen hast, zeigt sie "Bad Request (400)", das Admin-Panel ohne Styles oder eine leere Seite mit einem 500er Fehler. Das ist kein Pech, sondern nur ein paar wiederkehrende Fallstricke, in die fast jeder bei der ersten Bereitstellung gerät. Die Django-Entwicklungsumgebung ist bewusst nachsichtig: Sie zeigt Fehler an, liefert statische Dateien selbst und fragt nicht nach der Domain. Die Produktion verzeiht nichts davon.
In diesem Tutorial haben wir 10 der häufigsten Fehler bei der Bereitstellung von Django in der Produktion zusammengefasst. Bei jedem beschreiben wir, wie er sich äußert, woher er kommt und wie man ihn behebt. Am Ende gibt es eine kurze Checkliste zur Überprüfung vor jeder Bereitstellung und Antworten auf die häufigsten Fragen.
Inhaltsverzeichnis
- Warum Django lokal funktioniert, aber auf dem Server nicht?
- Fehler 1: Ist DEBUG = True in der Produktion ein großes Problem?
- Fehler 2: Wo SECRET_KEY und Passwörter speichern?
- Fehler 3: Woher kommt „Bad Request (400)“?
- Fehler 4: Warum erscheint hinter einem Proxy „CSRF verification failed“?
- Fehler 5: Warum hat das Admin-Panel keine Styles?
- Fehler 6: Kann man runserver beibehalten?
- Fehler 7: Was passiert, wenn du die Migrationen vergisst?
- Fehler 8: Reicht SQLite für die Produktion?
- Fehler 9: Warum führt pip install zu einem Rollback der Bereitstellung?
- Fehler 10: Woher weiß man, dass die Anwendung abgestürzt ist?
- Checkliste vor der Bereitstellung
- Wie sieht das bei Saplo aus?
- FAQ
Warum Django lokal funktioniert, auf dem Server aber nicht?
Der Befehl python manage.py runserver startet den Entwicklungsserver, der im Hintergrund eine Menge Arbeit für dich als Entwickler erledigt. Er stellt CSS- und JavaScript-Dateien bereit, zeigt im Fehlerfall eine lesbare Seite mit Stack-Trace an, akzeptiert Anfragen unter localhost und kümmert sich nicht um HTTPS.
In der Produktion ist dieser Komfort nicht mehr gegeben. Die Anwendung läuft hinter einem Webserver oder Reverse Proxy, unter einer echten Domain, der Traffic kommt verschlüsselt an und jedes Element, das lokal „einfach so funktionierte“, muss explizit konfiguriert werden. Die meisten der unten beschriebenen Fehler sind genau dieser Unterschied zwischen diesen beiden Welten.
Bevor du weiterliest, merke dir einen Befehl. Django hat eine integrierte Überprüfung der Produktionseinstellungen:
python manage.py check --deploy
Er listet Warnungen zu DEBUG, Schlüssel, Cookies, HTTPS und Sicherheits-Headern auf. Wenn du ihn vor der ersten Bereitstellung ausführst, werden die Hälfte der Probleme auf dieser Liste erkannt.
Fehler 1: Ist DEBUG = True in der Produktion ein großes Problem?
Ja, ein sehr großes. Wenn DEBUG aktiviert ist, zeigt jeder 500er-Fehler dem Besucher eine vollständige Diagnoseseite an: Codeausschnitte, lokale Variablen, Serverpfade, eine Liste der Einstellungen und SQL-Abfragen. Django maskiert einige sensible Namen, aber nicht alle, und die Anwendungsstruktur selbst ist eine wertvolle Karte für einen Angreifer. Außerdem speichert der DEBUG-Modus jede Datenbankabfrage, sodass der Arbeitsspeicher des Prozesses bei längerer Laufzeit ansteigt.
Wie zu beheben: Setze DEBUG über eine Umgebungsvariable mit einem sicheren Standardwert.
import os
DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"
Lokal setzt du DJANGO_DEBUG=1, und auf dem Server setzt du nichts. Denke jedoch daran, dass nach dem Deaktivieren von DEBUG die ALLOWED_HOSTS-Einstellungen wirksam werden und Django keine statischen Dateien mehr bereitstellt. Deshalb treten die Fehler 3 und 5 normalerweise direkt nach der Behebung dieses Problems auf.
Fehler 2: Wo bewahrt man SECRET_KEY und Passwörter auf?
Nicht im Repository. SECRET_KEY signiert Sessions, Passworthy-Reset-Tokens und CSRF-Cookies. Wer ihn kennt, kann die Session eines eingeloggten Benutzers fälschen. Der von startproject generierte Schlüssel landet oft im ersten Commit und bleibt für immer in der Git-Historie, selbst nachdem er aus der Datei gelöscht wurde.
Das Gleiche gilt für Datenbankpasswörter, API-Schlüssel für Zahlungsdienste oder E-Mails.
Wie man es behebt: Alle Geheimnisse aus Umgebungsvariablen lesen und im Repository nur die Namen hinterlassen.
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
Fehlt die Variable, wird beim Start ein Fehler ausgelöst, und das ist gut so: Es ist besser, die Anwendung startet gar nicht, als dass sie mit einem Standardschlüssel startet. Wenn der Schlüssel bereits in einem öffentlichen Repository gelandet ist, generiere einen neuen. Alle Benutzer werden abgemeldet, aber der alte Schlüssel ist dann nicht mehr gefährlich.
Fehler 3: Woher kommt der „Bad Request (400)“?
Fast immer von einer leeren oder unvollständigen ALLOWED_HOSTS. Wenn DEBUG deaktiviert ist, lehnt Django jede Anfrage ab, deren Host-Header nicht auf dieser Liste steht. Typische Übersehen sind das Fehlen der www-Version, das Fehlen der temporären Subdomain, unter der die Anwendung lief, bevor die Domain verbunden wurde, oder ein Tippfehler.
So behebst du es: Gib alle Domains ein, unter denen die Anwendung antworten soll.
ALLOWED_HOSTS = ["deinefirma.de", "www.deinefirma.de"]
Rette dich nicht mit dem Eintrag ["*"]. Dieser deaktiviert den Schutz vor Host-Header-Angriffen, zum Beispiel dem Einfügen einer gefälschten Domain in Links zum Zurücksetzen des Passworts.
Fehler 4: Warum erscheint „CSRF verification failed“ hinter dem Proxy?
In einer Produktionsumgebung empfängt Django selten direkten Traffic. Davor befindet sich ein Reverse Proxy, der HTTPS annimmt und an die Anwendung einfaches HTTP weiterleitet. Django sieht also eine unverschlüsselte Anfrage, und der Browser sendet das Formular von der Adresse https://. Ab Version 4.0 vergleicht Django den Origin-Header mit einer Liste vertrauenswürdiger Quellen und lehnt Formulare ohne diese ab, einschließlich der Anmeldung im Admin-Panel.
Eine weitere Variante desselben Problems: Du aktivierst SECURE_SSL_REDIRECT = True, und die Seite gerät in eine endlose Umleitungsschleife, weil Django immer noch denkt, dass die Anfrage über HTTP kam.
Wie zu beheben: Teile Django mit, welchen Header der Proxy für HTTPS-Informationen verwendet, und füge vertrauenswürdige Quellen hinzu.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
CSRF_TRUSTED_ORIGINS = ["https://deinefirma.de", "https://www.deinefirma.de"]
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
Die Einstellung SECURE_PROXY_SSL_HEADER ist nur dann sicher, wenn der Proxy diesen Header immer selbst setzt und keine Werte vom Client durchlässt.
Fehler 5: Warum fehlen im Admin-Panel die Styles?
Weil Django nach dem Deaktivieren von DEBUG keine statischen Dateien bereitstellt. Das /admin/ Panel funktioniert, sieht aber aus wie eine Website aus den Neunzigern, und deine eigenen CSS-Dateien und Bilder geben einen 404-Fehler zurück. Hinzu kommt das vergessene collectstatic: Die Dateien liegen in den Anwendungsverzeichnissen, aber niemand hat sie an einem zentralen Ort gesammelt.
So behebst du es: Setze STATIC_ROOT, sammle Dateien bei jeder Bereitstellung und stelle sie über einen Webserver oder die WhiteNoise-Bibliothek bereit.
STATIC_ROOT = BASE_DIR / "staticfiles"
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
# reszta middleware
]
python manage.py collectstatic --noinput
WhiteNoise verarbeitet nur statische Dateien aus dem Code. Von Benutzern hochgeladene Dateien (MEDIA_ROOT, zum Beispiel Produktbilder) müssen separat über einen Webserver oder einen externen Dateispeicher bereitgestellt werden. Dies ist oft die zweite Enttäuschung nach der ersten.
Fehler 6: Kann ich runserver verwenden?
Nein. Die Django-Dokumentation besagt ausdrücklich, dass der Entwicklungsserver keine Sicherheitsaudits oder Leistungstests bestanden hat. Er verarbeitet Anfragen auf eine Weise, die nicht für den Produktionsverkehr geeignet ist, verfügt über keine sinnvolle Prozessverwaltung, und wenn er abstürzt, wird ihn niemand wieder starten.
Wie man es behebt: Starte die Anwendung über einen WSGI-Server, meistens Gunicorn, unter der Kontrolle eines Prozessmanagers, der sie nach einem Absturz oder einem Neustart der Maschine wiederherstellt.
gunicorn yourproject.wsgi:application --bind 127.0.0.1:8000 --workers 2
Die Anzahl der Worker-Prozesse hängt vom Speicher ab. Jeder Gunicorn-Prozess ist eine separate Kopie der Anwendung im RAM. Auf einem kleinen Server mit 1 GB ist es daher besser, mit zwei zu beginnen und den Verbrauch zu überwachen, anstatt acht einzustellen und vom System einen Prozess-Killer zu bekommen.
Fehler 7: Was passiert, wenn du Migrationen vergisst?
Die neue Code-Version verweist auf eine Spalte, die noch nicht in der Datenbank existiert. Die Seite, die dieses Modell verwendet, gibt den Fehler 500 zurück, und in den Logs erscheint no such column oder relation does not exist. Eine Variante dieses Fehlers ist eine Migrationsdatei, die nicht ins Repository gelangt ist, weil sie in .gitignore war oder niemand sie hinzugefügt hat.
Wie zu beheben: Betrachte Migrationen als einen festen Schritt jeder Bereitstellung und nicht als eine manuelle Aufgabe.
python manage.py makemigrations --check --dry-run
python manage.py migrate --noinput
Der erste Befehl, vor dem Commit ausgeführt, endet mit einem Fehler, wenn es Änderungen in den Modellen gibt, für die keine Migrationsdatei existiert. Der zweite Befehl wird auf dem Server vor dem Start der neuen Version ausgeführt. Erstelle vor einer Migration, die Spalten oder Tabellen löscht, eine Datenbankkopie.
Fehler 8: Reicht SQLite für die Produktion aus?
Für eine kleine Anwendung mit wenigen Benutzern und seltenen Schreibvorgängen kann SQLite jahrelang funktionieren. Probleme treten bei vielen gleichzeitigen Schreibvorgängen (database is locked) und bei Backups auf: Eine Datenbankdatei, die während eines Schreibvorgangs kopiert wird, kann beschädigt werden.
Der eigentliche Fehler ist nicht die Wahl von SQLite selbst, sondern die Datenbank dort zu speichern, wo sie bei der Bereitstellung verschwindet. Eine Datei db.sqlite3 in einem Verzeichnis, das bei jedem Deployment überschrieben wird, oder in einem Container ohne persistenten Speicherplatz, bedeutet, dass die nächste Bereitstellung mit einer leeren Datenbank beginnt.
So behebst du es: Wenn die Anwendung wachsen soll, wechsle zu PostgreSQL und gib die Datenbankadresse in der Variablen DATABASE_URL an. Unabhängig von der Datenbank-Engine stelle sicher, dass die Daten an einem persistenten Ort liegen und durch ein Backup gesichert sind.
Fehler 9: Warum schlägt pip install bei der Bereitstellung fehl?
Weil die lokale Umgebung eine andere ist als auf dem Server. Typische Szenarien: Die Datei requirements.txt ohne Version, sodass der Server eine neuere, inkompatible Bibliothek herunterlädt; ein Paket, das lokal manuell installiert wurde und nicht in der Datei vorhanden ist (No module named ...); oder eine andere Python-Version. Ein Praxisbeispiel: Django 6.0 erfordert Python 3.12 oder neuer, daher schlägt auf einem Server mit Python 3.11 der Befehl pip install django ohne Versionsbeschränkung fehl oder die Installation startet nicht.
So behebst du es: Fixiere die Versionen und überprüfe, welches Python auf dem Server läuft.
pip freeze > requirements.txt
python --version
Wenn der Server Python 3.11 hat, gib Django>=5.2,<6 in die Abhängigkeitsdatei ein. Der 5.2-Zweig hat Langzeit-Support, daher ist dies eine vernünftige Wahl für die Produktion.
Fehler 10: Woher weißt du, ob die Anwendung abgestürzt ist?
Das Worst-Case-Szenario ist, dass ein Kunde dich über einen Ausfall informiert. Wenn DEBUG deaktiviert ist, sind Fehler auf der Seite nicht sichtbar. Wenn sie also weder in Protokollen noch per E-Mail landen, verschwinden sie einfach.
So behebst du das Problem:
- Konfiguriere
LOGGINGso, dass Fehler an die Standardausgabe oder in eine Datei geschrieben werden, die nach einem Ausfall überprüft wird. - Richte
ADMINSund die richtige E-Mail-Konfiguration ein, damit Django bei jedem 500er-Fehler eine E-Mail sendet. - Füge eine einfache Prüfadresse hinzu, zum Beispiel
/health/, die{"status": "ok"}zurückgibt, und verbinde diese mit der Verfügbarkeitsüberwachung.
Eine Prüfadresse ist auch bei Implementierungen nützlich: Das Implementierungssystem kann überprüfen, ob die neue Version gestartet ist, bevor es den Datenverkehr dorthin umleitet.
Checkliste vor der Bereitstellung
Bevor du eine neue Version veröffentlichst, gehe diese Punkte durch:
| Punkt | Wie zu prüfen |
|---|---|
| DEBUG deaktiviert | DJANGO_DEBUG ist auf dem Server nicht gesetzt |
| Geheimnisse außerhalb des Repositorys | git grep SECRET_KEY zeigt keine Werte |
| Domains in ALLOWED_HOSTS | Version mit www und ohne, plus temporäre Subdomain |
| HTTPS hinter Proxy | SECURE_PROXY_SSL_HEADER und CSRF_TRUSTED_ORIGINS eingestellt |
| Statische Dateien | collectstatic im Bereitstellungsprozess, Admin hat Styles |
| Anwendungsserver | Gunicorn statt runserver |
| Migrationen | makemigrations --check wird durchlaufen, migrate im Bereitstellungsprozess |
| Datenbank und Backups | Daten an einem dauerhaften Ort, Backup funktioniert |
| Abhängigkeiten | Versionen fixiert, kompatibel mit Python auf dem Server |
| Logs und Monitoring | Fehler werden in Logs erfasst, /health/ antwortet |
| Django Audit | python manage.py check --deploy ohne Warnungen |
Wie sieht das bei Saplo aus?
Bei Saplo ist der größte Teil dieser Liste bereits eingerichtet, bevor du die erste Zeile Code schreibst. Wenn du eine Django-Anwendung aus dem Katalog erstellst, installiert das Rezept Gunicorn und WhiteNoise, richtet STATIC_ROOT ein, trägt die Domain in ALLOWED_HOSTS und CSRF_TRUSTED_ORIGINS ein, fügt SECURE_PROXY_SSL_HEADER hinzu, führt Migrationen und collectstatic durch, und der Anwendungsprozess läuft unter der Aufsicht eines Prozessmanagers, der ihn nach einem Ausfall wieder startet. Das SSL-Zertifikat für die Domain ist im Preis des Plans enthalten.
Beim Deployment deines eigenen Repositorys werden die Schritte von der Datei saplo.yaml gesteuert. Migrationen werden als Pre-Build-Hook eingetragen und das Sammeln statischer Dateien als Build-Befehl:
build:
install: "pip install -r requirements.txt"
command: "python manage.py collectstatic --noinput"
hooks:
pre_build: "python manage.py migrate --noinput"
Ein paar Dinge, die du wissen solltest:
- Python 3.11. Der Builder führt das System-Python 3.11 aus, daher solltest du in
requirements.txtDjango in der 5.x-Version halten (Fehler 9). - Secrets.
SECRET_KEYoder das Datenbankpasswort speicherst du mit dem Befehlsaplo env set --secretoder im Panel unter der Registerkarte „Variablen und Secrets“. Das Panel, die API und der AI-Assistent zeigen dann nur noch Sternchen an. Die Änderung wird ab dem nächsten Deployment oder Neustart wirksam. Details findest du in der Dokumentation zu Variablen und Secrets. - Ein fehlgeschlagenes Deployment legt die Seite nicht lahm. Wenn der Build oder die Health Check fehlschlägt, erhält die neue Version den Status „Fehler“, während die vorherige Version weiterläuft. Der Health Check wartet bis zu 60 Sekunden auf eine Antwort.
- PostgreSQL wird als Container in der Datei
docker-compose.ymlder Anwendung eingerichtet, mit einem Port, der nur für127.0.0.1freigegeben ist und einer Adresse in der VariableDATABASE_URL. - Backups. Jeder Box verfügt über eine tägliche, verschlüsselte Sicherung, die in ein anderes Rechenzentrum übertragen wird (7 Tage zurück bei Box S, M und L). Ab Box L kommen automatische Snapshots zur Wiederherstellung mit einem Klick hinzu, und bei jedem Plan kann ein Snapshot vor einer riskanten Migration manuell erstellt werden.
- Logs können mit dem Befehl
saplo logs --followoder im Panel in den Deployment-Details eingesehen werden.
Das Deployment von GitHub nach jedem git push wird in der Git-Deploy-Anleitung beschrieben, und typische Fehlermeldungen haben wir in der Fehlerbehebung gesammelt.
Welchen Plan? Für eine kleine Django-Anwendung reicht Box S (1 GB RAM, 27 € pro Jahr). Wenn SSH-Zugang zum Server oder eine PostgreSQL-Datenbank in einem separaten Container benötigt wird, ist Box M (2 GB RAM, SSH, 63 € pro Jahr) ein sinnvoller Start. Box L (6 GB RAM, 136 € pro Jahr) bietet zusätzlich automatische Snapshots von 7 Tagen. Der Preis für das erste Jahr ist derselbe wie bei der Verlängerung, und die Migration einer bestehenden Anwendung von einem anderen Hosting übernehmen wir kostenlos. Ein vollständiger Vergleich der Pläne findest du in der Preisliste.
Wenn du gerade erst mit Django anfängst, lies zuerst was Django ist und wofür es sich eignet, und die Startanleitung findest du im Tutorial wie man Django auf Saplo einrichtet.
FAQ
Wie prüfe ich, ob meine Django-Anwendung produktionsbereit ist?
Führe python manage.py check --deploy mit den Produktionseinstellungen aus. Der Befehl gibt Warnungen bezüglich DEBUG, Schlüssel, HTTPS, Cookies und Sicherheits-Headern aus. Gehe danach die Checkliste in diesem Artikel durch, da das Audit keine Migrationen, Backups oder statische Dateien überprüft.
Warum zeigt die Seite nach dem Deaktivieren von DEBUG "Bad Request (400)" an?
Weil Django von diesem Zeitpunkt an ALLOWED_HOSTS prüft, und die Domain, unter der du zugreifst, nicht auf der Liste steht. Füge alle Adressvarianten hinzu, auch mit www und der temporären Subdomain, und starte die Anwendung neu.
Ist WhiteNoise ausreichend für statische Dateien in der Produktion?
Für die meisten kleinen und mittleren Anwendungen ja. WhiteNoise stellt Dateien mit Kompression und langer Cache-Speicherzeit im Browser bereit. Es unterstützt jedoch keine von Benutzern hochgeladenen Dateien, die separat bereitgestellt werden müssen.
Wie viel RAM braucht Django in der Produktion?
Eine einfache Anwendung mit zwei Gunicorn-Prozessen kommt mit einigen hundert Megabyte aus, daher ist ein Server mit 1 GB RAM für den Anfang ausreichend. Mehr Speicher benötigst du bei einer PostgreSQL-Datenbank auf demselben Server, einer Hintergrund-Aufgabenwarteschlange oder einer größeren Anzahl von Worker-Prozessen.
Kann ich eine laufende Django-Anwendung auf Saplo migrieren?
Ja. Die Anwendung wird aus einem Git-Repository oder über die CLI bereitgestellt, und die Migration von einem anderen Hosting-Anbieter führen wir für jeden Plan kostenlos durch. Vor der Migration solltest du die Checkliste in diesem Artikel durchgehen, da Einstellungen, die vom alten Server abhängen, wie z. B. Domains oder Dateipfade, an den neuen angepasst werden müssen.
- #Sicherheit
- #Django
- #Anleitung
- #Python
- #Deployment
