Files
kaffeekasse-saas/docs/betrieb-backup-monitoring.md
T
clemensandClaude Sonnet 5 3fc65812ef M8: Backup/Restore-Prozess und Monitoring dokumentiert
docs/betrieb-backup-monitoring.md: taeglicher mysqldump-Cron-Job mit
Aufbewahrung, Restore-Befehl inklusive Hinweis auf scripts/migrate.php,
vierteljaehrlicher Restore-Test. Produktive PHP-Fehlerkonfiguration
(display_errors aus, log_errors an) und die aktiv zu beobachtenden
Signale ohne dediziertes APM-Tool: audit_log fuer ungewoehnliche
Admin-Aktionen, rate_limit_attempts fuer Brute-Force-Versuche,
outbound_emails.status=failed fuer Mailversand-Probleme.

Damit ist M8 fuer den geplanten Scope abgeschlossen, mit einer bewusst
offenen Ausnahme (Content-Security-Policy, braucht Template-Bereinigung
der bestehenden Inline-Styles).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 20:19:20 +02:00

4.0 KiB
Raw Blame History

Backup, Restore und Monitoring im Produktivbetrieb

Stand: 2026-07-15

Dieses Dokument beschreibt die für den Produktivbetrieb auf dem Webspace empfohlenen Backup-, Restore- und Monitoring-Maßnahmen. Es ergänzt docs/betrieb-mail.md und docs/m8-haertung.md.

Backups

Die App hat kein eigenes Backup-Tooling; das ist bei einer MySQL-Datenbank auf Webspace-Hosting bewusst nicht nötig mysqldump reicht.

Empfohlener täglicher Cron-Job (Zeitpunkt außerhalb der Hauptnutzungszeit):

#!/usr/bin/env bash
set -euo pipefail

TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
BACKUP_DIR="/pfad/ausserhalb/des/webroots/backups"
mkdir -p "$BACKUP_DIR"

mysqldump \
  --single-transaction \
  --routines \
  --triggers \
  -h "$DB_HOST" -P "${DB_PORT:-3306}" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" \
  | gzip > "$BACKUP_DIR/kaffeeliste_${TIMESTAMP}.sql.gz"

# Aufbewahrung: 14 Tage taegliche Backups behalten, aeltere loeschen
find "$BACKUP_DIR" -name 'kaffeeliste_*.sql.gz' -mtime +14 -delete

Wichtige Punkte:

  • --single-transaction sorgt für einen konsistenten Snapshot ohne Tabellen zu sperren (InnoDB, wie hier durchgängig verwendet).
  • Der Backup-Ordner muss außerhalb des über HTTP erreichbaren Webroots liegen, sonst wären Dumps öffentlich abrufbar.
  • Zusätzlich zu lokalen Backups auf dem Webspace mindestens eine Kopie an einen anderen Ort übertragen (z. B. verschlüsselt in Cloud-Speicher), damit ein Ausfall des Webspace-Anbieters nicht auch die Backups vernichtet.
  • var/uploads, var/mail und var/sessions enthalten keine dauerhaft relevanten Daten (Uploads werden nach Verarbeitung gelöscht, var/mail ist nur der Dev-Log-Transport) und müssen nicht gesichert werden.

Restore

gunzip -c kaffeeliste_20260715_030000.sql.gz | mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME"

Danach php scripts/migrate.php laufen lassen, falls das Backup älter als die zuletzt eingespielten Migrationen ist (das Skript ist idempotent und wendet nur fehlende Migrationen an).

Restore-Test: Mindestens einmal pro Quartal einen echten Restore gegen eine separate Test-Datenbank durchführen und die App dagegen starten (scripts/run-dev-server.sh mit den Test-DB-Zugangsdaten). Ein Backup, das nie zurückgespielt wurde, ist kein verlässliches Backup.

Monitoring und Fehlerlogging

PHP-Fehlerausgabe

Im Dev-Modus zeigt display_errors=1 Fehler direkt im Browser (siehe .local/php-dev.ini) das ist für lokale Entwicklung richtig, aber produktiv ein Sicherheitsrisiko (Stacktraces, Dateipfade). Produktiv muss gelten:

display_errors = Off
log_errors = On
error_log = /pfad/ausserhalb/des/webroots/logs/php-error.log

Die meisten Webspace-Anbieter setzen das bereits serverseitig; im Zweifel per .htaccess oder ini_set() am Anfang von config.php erzwingen.

Was aktiv beobachtet werden sollte

  • php-error.log: Fatal Errors und Warnings. Ein plötzlicher Anstieg deutet meist auf eine kaputte Migration oder einen fehlerhaften Deploy hin.
  • audit_log: Ungewöhnliche Häufung sicherheitsrelevanter Aktionen (participant.access_granted, tenant_settings.updated, year_end_bonus.distributed, tenant_data.exported) außerhalb der üblichen Nutzungszeiten eines Mandanten.
  • rate_limit_attempts: Viele Treffer in kurzer Zeit für einen login_ip:*- oder login_email:*-Bucket zeigen einen laufenden Brute-Force-Versuch, auch wenn er durch das Rate-Limit bereits abgewehrt wird.
  • outbound_emails mit status = 'failed': Deutet meist auf ein Problem mit dem lokalen Mailversand hin (siehe docs/betrieb-mail.md), zum Beispiel einen falsch konfigurierten APP_MAIL_FROM oder ein Zustellproblem beim Webspace-Anbieter.

Bewusst nicht umgesetzt

Ein dediziertes APM-/Monitoring-Tool (z. B. Uptime-Checks, strukturiertes Log-Shipping, Alerting) ist für den aktuellen Umfang nicht eingerichtet. Das lohnt sich erst mit echtem Produktivbetrieb und mehreren zahlenden Mandanten; bis dahin reichen die oben genannten manuellen Prüfpunkte.