Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung E-Mail / Mailserver 04.08.2026 · 10 min Lesezeit

Listmonk mit Docker installieren: Newsletter-Server selbst hosten

Listmonk 6.2.0 mit Docker Compose und PostgreSQL selbst hosten: SMTP, Root-URL, Double-Opt-In, HTTPS per Reverse Proxy sowie getestete Sicherung und Wiederherstellung.

Geprüft am 14.10.2026 · für listmonk 6.2.0

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Mehr dazu

Stapel weißer Briefumschläge neben einem kompakten schwarzen Mini PC auf einem sonnigen Holzschreibtisch

Listmonk ist ein selbst gehosteter Newsletter- und Mailinglisten-Manager. Die Anwendung verwaltet Listen, Abonnenten, Kampagnen, Double-Opt-In und Vorlagen und speichert alles in PostgreSQL. Den eigentlichen Versand übernimmt ein SMTP-Server, den Sie hinterlegen: ein eigener Mailserver oder ein Versanddienst. Die Abonnentendaten bleiben damit auf Ihrem Server. Diese Anleitung richtet Listmonk 6.2.0 mit Docker Compose ein, hinterlegt SMTP, prüft Double-Opt-In und Versand und zeigt Sicherung und Wiederherstellung. Alle Schritte wurden in einer Testumgebung mit einem SMTP-Testempfänger nachgestellt.

Voraussetzungen

  • Linux-Server oder VPS mit Docker Engine und Compose-Plugin, siehe Docker und Docker Compose auf Linux installieren. Architektur x86_64 oder ARM.
  • 1 CPU-Kern und 1 GB RAM. Im Test belegten Listmonk rund 30 MB und PostgreSQL rund 85 MB im Leerlauf.
  • 2 GB freier Speicher für Images, Datenbank und Medien.
  • SMTP-Zugang eines Mailservers oder Versanddienstes für die Absenderdomain, mit SPF, DKIM und DMARC. Ohne diese Einträge landen Newsletter häufig im Spam.
  • Subdomain wie newsletter.example.com und ein Reverse Proxy für HTTPS. Die Adresse steckt später in jedem Abmelde- und Bestätigungslink.
EckdatenWert
Imageslistmonk/listmonk:v6.2.0, postgres:17-alpine
Port9000 (HTTP, Weboberfläche und API)
DatenVolume für /var/lib/postgresql/data, Ordner ./uploads für Medien
KonfigurationUmgebungsvariablen LISTMONK_… für Datenbank und Erstkonto, alles andere in der Weboberfläche
LizenzAGPLv3

Schritt 1: Projektordner und .env anlegen

sudo mkdir -p /opt/listmonk/uploads
cd /opt/listmonk
openssl rand -base64 24

Erzeugen Sie mit openssl zwei zufällige Passwörter und tragen Sie sie in die .env ein. Das Datenbankpasswort übernimmt PostgreSQL nur beim allerersten Start in das leere Volume; die Admin-Variablen wirken ebenfalls nur bei der Erstinstallation.

# /opt/listmonk/.env
LISTMONK_DB_PASSWORD=zufaelliges-db-passwort
LISTMONK_ADMIN_USER=admin
LISTMONK_ADMIN_PASSWORD=zufaelliges-admin-passwort
sudo chmod 600 /opt/listmonk/.env

Bleiben die Admin-Variablen leer, legen Sie das erste Konto laut offizieller Compose-Datei beim ersten Aufruf der Weboberfläche an. Für Docker-Secrets unterstützt das Image zu jeder LISTMONK_…-Variable eine Variante mit Endung _FILE.

Verifizieren: sudo cat /opt/listmonk/.env zeigt drei Zeilen NAME=Wert ohne Leerzeichen um das Gleichheitszeichen, ls -la /opt/listmonk zeigt den Ordner uploads.

Schritt 2: compose.yaml erstellen

Die Datei folgt der offiziellen docker-compose.yml aus dem Listmonk-Repository mit drei Änderungen: fester Tag statt latest, Port nur an 127.0.0.1, Passwort aus der .env statt des Beispielwerts listmonk. Zusätzlich wartet die Anwendung, bis PostgreSQL gesund ist, und der Datenbankport wird gar nicht veröffentlicht, weil Listmonk die Datenbank über das interne Compose-Netz erreicht.

x-db-credentials: &db-credentials
  POSTGRES_USER: &db-user listmonk
  POSTGRES_PASSWORD: &db-password ${LISTMONK_DB_PASSWORD:?LISTMONK_DB_PASSWORD fehlt}
  POSTGRES_DB: &db-name listmonk

services:
  app:
    image: listmonk/listmonk:v6.2.0
    container_name: listmonk_app
    restart: unless-stopped
    ports:
      - "127.0.0.1:9000:9000"
    depends_on:
      db:
        condition: service_healthy
    command: [sh, -c, "./listmonk --install --idempotent --yes --config '' && ./listmonk --upgrade --yes --config '' && ./listmonk --config ''"]
    environment:
      LISTMONK_app__address: 0.0.0.0:9000
      LISTMONK_db__user: *db-user
      LISTMONK_db__password: *db-password
      LISTMONK_db__database: *db-name
      LISTMONK_db__host: db
      LISTMONK_db__port: 5432
      LISTMONK_db__ssl_mode: disable
      TZ: Europe/Berlin
      LISTMONK_ADMIN_USER: ${LISTMONK_ADMIN_USER:-}
      LISTMONK_ADMIN_PASSWORD: ${LISTMONK_ADMIN_PASSWORD:-}
    volumes:
      - ./uploads:/listmonk/uploads:rw

  db:
    image: postgres:17-alpine
    container_name: listmonk_db
    restart: unless-stopped
    environment:
      <<: *db-credentials
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U listmonk"]
      interval: 10s
      timeout: 5s
      retries: 6
    volumes:
      - listmonk-data:/var/lib/postgresql/data

volumes:
  listmonk-data:

Der command stammt unverändert aus der offiziellen Datei: --install --idempotent legt das Schema nur in einer leeren Datenbank an, --upgrade migriert es nach einem Versionswechsel, --config '' sorgt dafür, dass Listmonk nur die Umgebungsvariablen liest. Bleiben Sie bei postgres:17-alpine: Ab PostgreSQL 18 erwartet das offizielle Image das Volume unter /var/lib/postgresql, und ein Wechsel der Hauptversion braucht ohnehin einen Dump und eine Wiederherstellung.

Verifizieren: docker compose config -q endet ohne Ausgabe. Fehlt LISTMONK_DB_PASSWORD in der .env, bricht der Befehl mit der Meldung LISTMONK_DB_PASSWORD fehlt ab.

Schritt 3: Stack starten

cd /opt/listmonk
docker compose up -d
docker compose ps
docker compose logs app

Im Test war PostgreSQL nach gut 20 Sekunden gesund, Listmonk startete direkt danach. Bei der Erstinstallation enthält das Log diese Zeilen:

** first time (idempotent) installation **
install.go:93: creating superadmin user 'admin'
install.go:116: setup complete
upgrade.go:74: no upgrades to run. Database is up to date.
⇨ http server started on [::]:9000

Für einen Erreichbarkeitstest nutzen Sie eine normale GET-Anfrage. curl -I sendet eine HEAD-Anfrage, die Listmonk 6.2.0 im Test mit 404 beantwortete, obwohl die Seite funktionierte:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9000/admin/login
ss -tlnp | grep -E ':(9000|5432) '

Verifizieren: docker compose ps zeigt listmonk_db als (healthy) und listmonk_app als Up mit 127.0.0.1:9000->9000/tcp. Der curl-Aufruf liefert 200, ss zeigt nur 127.0.0.1:9000 und keinen Port 5432.

Schritt 4: Anmelden und Grundeinstellungen setzen

Ohne Reverse Proxy öffnen Sie einen SSH-Tunnel und rufen http://localhost:9000/admin auf. Die Oberfläche ist englisch.

ssh -L 9000:127.0.0.1:9000 benutzer@server

Melden Sie sich mit den Werten aus der .env an und öffnen Sie Settings. Jede Speicherung lädt Listmonk intern neu, die Oberfläche ist dann einige Sekunden nicht erreichbar.

  1. General: Root URL auf die öffentliche Adresse setzen, etwa https://newsletter.example.com ohne Schrägstrich am Ende. Diese Adresse steht in Bestätigungs- und Abmeldelinks. Default from email auf Ihren Absender setzen, etwa Newsletter Beispiel GmbH <newsletter@example.com>.
  2. Media uploads: Upload-Pfad auf /listmonk/uploads ändern. Voreingestellt ist uploads innerhalb des Containers; ohne die Änderung landen Bilder nicht im eingebundenen Ordner und gehen beim Neuerstellen des Containers verloren.

Verifizieren: Nach dem Speichern zeigt Settings > General die neue Root-URL. Ein unter Campaigns > Media hochgeladenes Bild liegt auf dem Host in /opt/listmonk/uploads; im Test lagen dort das Bild und eine Vorschaudatei thumb_…, und die Adresse /uploads/DATEINAME lieferte Status 200.

Schritt 5: SMTP hinterlegen und Versand prüfen

Unter Settings > SMTP steht nach der Installation ein Beispieleintrag mit smtp.yoursite.com. Ersetzen Sie ihn durch Ihren Mailserver: Host, Port (meist 587 mit STARTTLS oder 465 mit TLS), Auth protocol (meist LOGIN oder PLAIN), Benutzername und Passwort. Die Links über den Feldern füllen typische Werte für bekannte Versanddienste vor.

Listmonk-Einstellungen SMTP mit eingetragenem Mailserver, Port, Authentifizierung und TLS-Auswahl
Settings, Bereich SMTP: Host, Port, Authentifizierung, TLS und Verbindungsgrenzen eines Versandservers. Im Test zeigte der Eintrag auf einen SMTP-Testempfänger ohne TLS.

Über die Schaltfläche Test am SMTP-Eintrag schickt Listmonk eine Testmail an eine Adresse Ihrer Wahl. Im Test kam sie mit dem Betreff Test connection und dem eingestellten Absender an. Für einen Probelauf ohne echte Zustellung eignet sich ein lokaler SMTP-Fänger wie Mailpit als zusätzlicher Dienst in der compose.yaml; er nimmt alle Mails an und zeigt sie in einer eigenen Weboberfläche. Für den Betrieb gehört dort der echte Mailserver hin.

Prüfen Sie danach den kompletten Ablauf mit Double-Opt-In:

  1. Lists > New: Liste anlegen, Typ Public oder Private, Opt-in Double.
  2. Subscribers > New: eine eigene Testadresse der Liste zuordnen. Listmonk verschickt sofort die Mail Confirm subscription.
  3. Den Bestätigungslink öffnen und bestätigen.
  4. Campaigns > New: Kampagne an die Liste anlegen und starten.
Listmonk-Kampagnenübersicht mit abgeschlossener Testkampagne, 1 von 1 Nachricht versendet
Kampagnenübersicht nach dem Testversand: Die Kampagne ist abgeschlossen, 1 von 1 Nachricht wurde verschickt.

Verifizieren: Die Testmail kommt an. Nach dem Klick auf den Bestätigungslink steht der Abonnent in der Liste auf confirmed, die Kampagne wechselt auf Finished mit Sent 1 / 1, und die Mail enthält unten die Links Unsubscribe und View in browser mit Ihrer Root-URL.

Schritt 6: HTTPS über einen Reverse Proxy

Listmonk spricht nur HTTP. Ein Reverse Proxy auf demselben Host übernimmt das Zertifikat und leitet an 127.0.0.1:9000 weiter. Mit Caddy genügt ein Eintrag, das Zertifikat holt Caddy selbst, siehe Caddy als Reverse Proxy einrichten:

newsletter.example.com {
    reverse_proxy 127.0.0.1:9000
}

Für mehrere Container auf einem Host eignet sich Traefik als Docker-Reverse-Proxy. Die öffentlichen Seiten für Abmeldung, Bestätigung und optional das Anmeldeformular müssen aus dem Internet erreichbar sein, sonst funktionieren die Links in den Mails nicht.

Verifizieren: curl -s -o /dev/null -w '%{http_code}' https://newsletter.example.com/admin/login liefert 200 mit gültigem Zertifikat, und ein Bestätigungslink aus einer neuen Testanmeldung öffnet sich ohne Zertifikatswarnung.

Schritt 7: Sichern und Wiederherstellen

Listmonk hält alles Wichtige in PostgreSQL: Abonnenten, Listen, Kampagnen, Vorlagen und Einstellungen samt SMTP-Zugangsdaten. Dazu kommen die Medien in uploads. pg_dump erzeugt im laufenden Betrieb einen konsistenten Stand:

cd /opt/listmonk
sudo mkdir -p /backup/listmonk
docker compose exec -T db pg_dump -U listmonk -Fc listmonk | sudo tee /backup/listmonk/listmonk-$(date +%F).dump >/dev/null
sudo tar -czf /backup/listmonk/uploads-$(date +%F).tar.gz uploads .env compose.yaml

Das Archiv enthält die .env mit Passwörtern, die Sicherung enthält die SMTP-Zugangsdaten. Lagern Sie beides geschützt. Zum Wiederherstellen stoppen Sie die Anwendung, spielen den Dump zurück und starten neu:

cd /opt/listmonk
docker compose stop app
docker compose exec -T db pg_restore -U listmonk -d listmonk --clean --if-exists < /backup/listmonk/listmonk-2026-10-14.dump
docker compose start app

Im Test wurde die versendete Kampagne nach der Sicherung gelöscht und per pg_restore zurückgeholt. Danach war sie wieder mit Status finished und einer versendeten Nachricht vorhanden, der Abonnent stand weiter auf confirmed. Für automatische Sicherungen mit Rotation siehe MySQL- und PostgreSQL-Backup automatisieren.

Verifizieren: Die Dump-Datei ist größer als 0 Byte (im Test rund 70 KB für eine kleine Installation), pg_restore -l auf die Datei listet die Tabellen subscribers, lists und campaigns. Nach einer Probe-Wiederherstellung zeigt docker compose logs app wieder http server started, und Ihre Kampagnen sind in der Oberfläche sichtbar.

Schritt 8: Updates einspielen

Lesen Sie die Release Notes auf GitHub, legen Sie eine Sicherung wie in Schritt 7 an und ändern Sie dann den Tag in der compose.yaml. Beim Start migriert --upgrade --yes das Datenbankschema.

cd /opt/listmonk
docker compose pull
docker compose up -d
docker compose logs app | grep -E "upgrade|migration|started"

Automatische Updates per Watchtower sind bei einem Newsletter-System mit Datenbankmigration riskant, weil die Sicherung vor dem Update fehlt.

Verifizieren: Das Log nennt die neue Versionsnummer und endet mit http server started, unter Settings steht die neue Version in der Überschrift, und Ihre Listen sind vollständig.

Typische Fehler

  • password authentication failed for user "listmonk": LISTMONK_DB_PASSWORD wurde nach dem ersten Start geändert. PostgreSQL behält das ursprüngliche Passwort. Entweder den alten Wert zurück in die .env oder das Passwort per docker compose exec db psql -U listmonk -c "ALTER USER listmonk PASSWORD '…';" anpassen.
  • Admin-Passwort aus der .env greift nicht: Die Admin-Variablen wirken nur bei der Erstinstallation. Danach ändern Sie Passwörter unter Users oder nutzen auf der Login-Seite die Funktion zum Zurücksetzen, die funktionierendes SMTP voraussetzt.
  • Bestätigungs- oder Abmeldelinks zeigen auf localhost:9000: Die Root-URL steht noch auf dem Standardwert http://localhost:9000. In Settings > General korrigieren.
  • Testmail scheitert: Port und TLS-Modus passen nicht zusammen (587 mit STARTTLS, 465 mit TLS) oder der Server verlangt eine andere Authentifizierung. Die Fehlermeldung erscheint direkt nach dem Test.
  • Bilder verschwinden nach einem Update: Upload-Pfad steht noch auf uploads statt /listmonk/uploads, siehe Schritt 4.
  • curl -I meldet 404: Listmonk beantwortet HEAD-Anfragen nicht wie GET. Mit einer GET-Anfrage prüfen, siehe Schritt 3.
  • API antwortet mit 403: Die API akzeptiert keine normalen Anmeldedaten, sondern einen API-Benutzer mit Token. Diesen legen Sie unter Users an.

Häufige Fragen

Ersetzt Listmonk einen Mailserver?

Nein. Listmonk verwaltet Listen und Kampagnen und übergibt jede Nachricht an einen SMTP-Server. Für gute Zustellbarkeit brauchen Sie einen sauber eingerichteten Mailserver oder einen Versanddienst mit SPF, DKIM und DMARC, siehe DKIM mit OpenDKIM und Postfix einrichten oder Mailcow als Mailserver.

Kann ich eine vorhandene PostgreSQL-Datenbank nutzen?

Ja. Entfernen Sie den Dienst db, setzen Sie LISTMONK_db__host und die Zugangsdaten auf Ihren Datenbankserver und legen Sie dort eine leere Datenbank mit eigenem Benutzer an.

Wie binde ich ein Anmeldeformular auf der Website ein?

Listmonk erzeugt unter Lists > Forms HTML-Code für öffentliche Listen. Mit Double-Opt-In landen neue Adressen erst nach der Bestätigung als aktive Abonnenten in der Liste.

Fazit

Listmonk läuft mit zwei Containern auf einem kleinen Server und deckt Listen, Double-Opt-In und Kampagnen vollständig ab. Entscheidend für den Betrieb sind eine korrekte Root-URL, ein zuverlässiger SMTP-Server mit SPF, DKIM und DMARC, ein fester Image-Tag und eine geprüfte Sicherung der Datenbank vor jedem Update.

Weiterführende Anleitungen und Quellen