---
title: "WordPress-Backup auf dem Server per Cronjob und Shell-Skript"
date: 2026-09-30T21:13:29+02:00
modified: 2026-11-09T14:34:48+01:00
category: wordpress
type: how_to
author: Marcel Schönfelder
canonical: https://s-edv.com/anleitungen/wordpress-backup-server-cronjob-shell-skript
keywords:
  - WordPress
  - Backup
  - Cron
  - WP-CLI
  - Linux
reading_time: 9
---

# WordPress-Backup auf dem Server per Cronjob und Shell-Skript

Backup-Plugins sind bequem, laufen aber innerhalb von WordPress: Fällt die Website aus, weil ein Update schiefging oder PHP an ein Limit stößt, läuft oft auch das Backup nicht. Haben Sie SSH-Zugriff auf einen eigenen Server oder einen VPS, können Sie die Sicherung unabhängig von WordPress über das Betriebssystem erledigen: ein kurzes Shell-Skript sichert Datenbank und Dateien, ein Cronjob startet es jede Nacht. Diese Anleitung zeigt ein im Labor getestetes Skript mit Sperre gegen Doppelstarts, Prüfung der Archive, Aufbewahrungsregel und Restore-Probe, und erklärt die Stolperstellen, die in der Praxis zu stillen Fehlern führen.

**Kurzfassung:** Das Skript exportiert die Datenbank mit `wp db export`, packt das WordPress-Verzeichnis mit `tar`, prüft beide Archive und löscht Sicherungen, die älter als 14 Tage sind. `flock` verhindert, dass zwei Läufe gleichzeitig arbeiten, halbfertige Dateien werden bei Fehlern entfernt. Der Cronjob läuft als Benutzer, dem die WordPress-Dateien gehören. Weil Cron mit einer minimalen Umgebung startet, setzt das Skript den `PATH` selbst. Kopieren Sie die Sicherungen zusätzlich auf einen zweiten Speicherort und testen Sie die Wiederherstellung regelmäßig.

## Voraussetzungen

- [ ] Linux-Server oder VPS mit SSH-Zugang und `sudo`-Rechten, zum Beispiel Debian 13 oder Ubuntu 24.04
- [ ] WordPress 7.1 (getestet mit 7.1.2) auf PHP 8.x, Datenbank MariaDB oder MySQL
- [ ] WP-CLI (getestet mit 2.12.0) unter `/usr/local/bin/wp` und der Datenbank-Client, der `mariadb-dump` oder `mysqldump` mitbringt
- [ ] Die Werkzeuge `tar`, `gzip`, `flock` und `cron`, auf Debian und Ubuntu in der Grundinstallation enthalten
- [ ] Freier Speicher für alle Sicherungen innerhalb der Aufbewahrungszeit und einen zusätzlichen laufenden Durchgang; im Labor belegte eine frische Installation rund 36 MB je Lauf
- [ ] Ein Konto mit der Rolle Administrator in WordPress für die Kontrolle nach dem Restore

## Schritt 1: Zielverzeichnis anlegen und Rechte setzen

Die Sicherung enthält `wp-config.php` mit dem Datenbankpasswort und einen vollständigen Datenbankauszug mit allen Benutzerkonten. Das Zielverzeichnis darf deshalb nur für den Benutzer lesbar sein, der das Backup erstellt. Es liegt außerhalb des Webroots, damit niemand die Archive über den Browser abrufen kann.

Das Skript läuft als der Benutzer, dem die WordPress-Dateien gehören. Auf Debian und Ubuntu mit Apache ist das häufig `www-data`, bei vielen Hostern ein eigener Systembenutzer. So vermeiden Sie, WP-CLI als root starten zu müssen, was es nur mit `--allow-root` zulässt.

```bash
$ ls -ld /var/www/html
$ sudo mkdir -p /var/backups/wordpress
$ sudo chown www-data:www-data /var/backups/wordpress
$ sudo chmod 700 /var/backups/wordpress
```

Passen Sie Pfad und Benutzer an Ihre Installation an. Den Eigentümer zeigt die erste Zeile.

> **Prüfen:** `ls -ld /var/backups/wordpress` zeigt `drwx------` und den Benutzer, dem auch die WordPress-Dateien gehören.

## Schritt 2: Backup-Skript anlegen

Legen Sie das Skript unter `/usr/local/sbin/wp-backup.sh` an, zum Beispiel mit `sudo nano /usr/local/sbin/wp-backup.sh`:

```bash
#!/usr/bin/env bash
# WordPress-Backup: Datenbank und Dateien, mit Aufbewahrung
set -euo pipefail
umask 077
# cron startet mit PATH=/usr/bin:/bin, wp liegt meist in /usr/local/bin
export PATH="/usr/local/bin:/usr/bin:/bin"

WP_PATH="/var/www/html"
ZIEL="/var/backups/wordpress"
BEHALTEN_TAGE=14
STAMP="$(date +%F_%H%M%S)"
DB="$ZIEL/db_$STAMP.sql.gz"
FILES="$ZIEL/files_$STAMP.tar.gz"

exec 9>"$ZIEL/.lock"
flock -n 9 || { echo "Backup läuft bereits, Abbruch." >&2; exit 1; }

# Bei Abbruch halbfertige Dateien entfernen
trap 'rm -f "$DB.part" "$FILES.part"' EXIT

# 1. Datenbank (Zugangsdaten liest WP-CLI aus wp-config.php)
wp --path="$WP_PATH" db export - --single-transaction | gzip > "$DB.part"

# 2. Dateien ohne Cache und temporären Update-Ordner
tar -czf "$FILES.part" -C "$WP_PATH" \
  --exclude=./wp-content/cache --exclude=./wp-content/upgrade .

# 3. Archive prüfen, erst dann umbenennen
gzip -t "$DB.part"
tar -tzf "$FILES.part" > /dev/null
mv "$DB.part" "$DB"
mv "$FILES.part" "$FILES"

# 4. Sicherungen älter als BEHALTEN_TAGE löschen
find "$ZIEL" -maxdepth 1 -type f \( -name "db_*.sql.gz" -o -name "files_*.tar.gz" \) \
  -mtime +"$BEHALTEN_TAGE" -delete

echo "Backup $STAMP fertig: $(du -ch "$DB" "$FILES" | tail -1 | cut -f1)"
```

Die wichtigsten Entscheidungen im Skript:

- `set -euo pipefail` bricht beim ersten Fehler ab, auch wenn der Fehler in einer Pipe auftritt. Ohne `pipefail` würde ein Datenbankfehler hinter `| gzip` verschwinden.
- `umask 077` sorgt dafür, dass neue Dateien nur für den Eigentümer lesbar sind.
- `flock -n` verhindert Doppelstarts, etwa wenn ein Lauf wegen großer Mediendateien länger dauert als geplant.
- Die Archive entstehen zunächst als `.part`-Datei und bekommen erst nach bestandener Prüfung ihren endgültigen Namen. Der `trap` löscht Reste, wenn das Skript abbricht. So finden Sie im Zielordner nur vollständige Sicherungen.
- `--single-transaction` reicht WP-CLI an `mariadb-dump` bzw. `mysqldump` durch. Bei InnoDB-Tabellen, dem Standard in aktuellen WordPress-Installationen, entsteht so ein konsistenter Stand, ohne die Website zu sperren.
- Ausgeschlossen sind `wp-content/cache` und `wp-content/upgrade`, weil sich beide Ordner neu erzeugen lassen. Weitere Ausschlüsse, etwa Backup-Ordner von Plugins, ergänzen Sie mit zusätzlichen `--exclude`-Angaben.

Machen Sie das Skript ausführbar und prüfen Sie die Syntax:

```bash
$ sudo chmod 755 /usr/local/sbin/wp-backup.sh
$ bash -n /usr/local/sbin/wp-backup.sh && echo "Syntax ok"
```

> **Prüfen:** `bash -n` gibt keine Fehlermeldung aus, und `ls -l /usr/local/sbin/wp-backup.sh` zeigt `-rwxr-xr-x`.

## Schritt 3: Skript von Hand testen

Starten Sie das Skript einmal manuell als der Benutzer, unter dem später auch der Cronjob läuft:

```bash
$ sudo -u www-data /usr/local/sbin/wp-backup.sh
$ ls -la /var/backups/wordpress
```

Im Labor lautete die Ausgabe „Backup 2026-09-30_190658 fertig: 36M“. Im Zielordner lagen danach `db_…sql.gz` und `files_…tar.gz`, beide mit den Rechten `-rw-------`. Prüfen Sie stichprobenhaft den Inhalt:

```bash
zcat /var/backups/wordpress/db_*.sql.gz | head -3
tar -tzf /var/backups/wordpress/files_*.tar.gz | grep wp-config.php
```

Die erste Zeile des Dumps beginnt bei MariaDB 11.8 mit einem Kommentar zum Sandbox-Modus, danach folgt „MariaDB dump“ mit Versionsnummer. Im Dateiarchiv muss `./wp-config.php` auftauchen.

Testen Sie auch den Fehlerfall. Im Labor führte ein falscher `WP_PATH` zu „Error: This does not seem to be a WordPress installation.“ und Rückgabewert 1; im Zielordner blieb keine halbe Datei zurück. Ein zweiter Start während eines laufenden Backups endete mit „Backup läuft bereits, Abbruch.“

> **Prüfen:** `echo $?` direkt nach dem Lauf gibt 0 aus, und beide Archive sind größer als wenige Kilobyte. Ein Datenbank-Dump mit 20 Byte ist ein leeres gzip-Archiv und deutet auf einen Fehler hin.

## Schritt 4: Cronjob einrichten

Öffnen Sie die Crontab des Backup-Benutzers:

```bash
$ sudo crontab -u www-data -e
```

und tragen Sie eine Zeile für den nächtlichen Lauf ein, hier täglich um 2:30 Uhr:

```plaintext
30 2 * * * /usr/local/sbin/wp-backup.sh >> /var/backups/wordpress/backup.log 2>&1
```

Das Protokoll landet im Zielordner, weil der Benutzer `www-data` dort schreiben darf, unter `/var/log` in der Regel nicht. Die Grundlagen der Zeitangaben erklärt die Anleitung [Cron und crontab richtig nutzen](https://s-edv.com/anleitungen/cron-crontab-grundlagen-linux).

Cron startet Befehle mit einer sehr knappen Umgebung. Im Labor zeigte der erste Cron-Lauf ohne die `PATH`-Zeile im Skript „wp-backup.sh: line 20: wp: command not found“, obwohl derselbe Aufruf in der SSH-Sitzung funktionierte. Deshalb setzt das Skript den `PATH` selbst. Dasselbe gilt für Umgebungsvariablen: Liest Ihre `wp-config.php` Zugangsdaten aus der Umgebung, wie es etwa das offizielle Docker-Image tut, fehlen sie im Cron-Lauf. Im Labor endete das mit „mariadb-dump: Got error: 2005: "Unknown server host 'mysql'"“. Tragen Sie solche Variablen dann oben in die Crontab ein.

Zum Testen setzen Sie die Zeit vorübergehend auf die nächste Minute, warten den Lauf ab und stellen danach auf den endgültigen Zeitpunkt zurück.

> **Prüfen:** `sudo crontab -u www-data -l` zeigt die Zeile, und nach dem ersten Lauf steht in `backup.log` „Backup … fertig“ ohne Fehlermeldung davor.

## Schritt 5: Sicherungen vom Server wegkopieren

Ein Backup auf demselben Server schützt vor Bedienfehlern und fehlgeschlagenen Updates, nicht aber vor einem Plattendefekt, einer Kündigung durch den Anbieter oder einem Angreifer mit Root-Rechten. Kopieren Sie die Archive deshalb zusätzlich auf einen zweiten Speicherort: ein NAS im Büro, einen Speicherdienst mit S3-Schnittstelle oder einen zweiten Server. Wie Sie das mit `rclone` oder `restic` umsetzen, zeigen die Anleitungen [3-2-1-Backup-Strategie umsetzen](https://s-edv.com/anleitungen/3-2-1-backup-strategie-praktisch-umsetzen) und [Datenbank-Backups mit cron automatisieren](https://s-edv.com/anleitungen/mysql-postgresql-backup-automatisieren-cron-cloud).

Die Aufbewahrung im Skript bezieht sich nur auf den lokalen Ordner. Am externen Ziel brauchen Sie eine eigene Regel, sonst wächst der Speicher unbegrenzt oder die Kopien verschwinden zu früh.

Spätestens an dieser Stelle zeigt sich, dass ein Backup kein einmaliges Projekt ist: Speicherplatz, Log und externe Kopie wollen regelmäßig kontrolliert werden, dazu kommen die Updates. Wer diese Aufgaben nicht selbst im Kalender halten möchte, kann sie abgeben: Die [WordPress-Wartung von wordpressupdate.de](https://wordpressupdate.de/?utm_source=s-edv.com&utm_medium=artikel&utm_campaign=20) übernimmt wöchentliche Backups auf externen Speicher mit vier Wochen Aufbewahrung sowie Updates von WordPress, Plugins und Themes.

> **Prüfen:** Die jüngste Sicherung liegt auch am externen Ziel. Vergleichen Sie zusätzlich die Prüfsummen beider Kopien, um Übertragungsfehler zu erkennen.

## Schritt 6: Wiederherstellung proben

Ein Backup gilt erst als funktionierend, wenn Sie daraus wiederhergestellt haben. Proben Sie das auf einer isolierten Testinstanz, nicht auf der Live-Website, denn der Import überschreibt die Datenbank vollständig. Übertragen Sie die entpackten Dateien in deren WordPress-Verzeichnis, passen Sie Eigentümer und Dateirechte an und tragen Sie vor dem Import in der wiederhergestellten wp-config.php ausschließlich die Datenbankzugangsdaten der Testinstanz ein. Die gesicherte Konfiguration darf niemals auf die produktive Datenbank zeigen.

```bash
$ mkdir -p /tmp/restore && cd /tmp/restore
$ tar -xzf /var/backups/wordpress/files_DATUM.tar.gz
$ gunzip -c /var/backups/wordpress/db_DATUM.sql.gz | wp --path=/pfad/zur/testinstanz db import -
```

Im Labor wurde nach dem Backup ein zusätzlicher Beitrag „NachBackup“ angelegt. Nach dem Import meldete WP-CLI „Success: Imported from 'STDIN'.“, und `wp post list` zeigte nur noch den Stand zum Zeitpunkt der Sicherung. Genau das erwarten Sie: Alles, was nach dem Backup entstand, ist nach einer Wiederherstellung verloren. Daraus leitet sich der sinnvolle Abstand der Sicherungen ab.

Liegt die Testinstanz unter einer anderen Adresse, passen Sie die URLs danach mit `wp search-replace` an, niemals per SQL-Ersetzung, weil WordPress Einstellungen serialisiert speichert. Eine feste Routine für solche Proben beschreibt die Anleitung [Backup-Restore-Test als feste Routine etablieren](https://s-edv.com/anleitungen/backup-restore-test-routine-etablieren).

> **Prüfen:** Die Testinstanz startet, Sie können sich anmelden, und der jüngste Beitrag entspricht dem Stand zum Zeitpunkt der Sicherung.

## Typische Fehler

| Meldung oder Symptom | Ursache | Lösung |
| --- | --- | --- |
| „wp: command not found“ nur im Cron-Lauf | Cron nutzt einen knappen `PATH` | `export PATH=…` im Skript oder vollen Pfad zu `wp` |
| „Got error: 2005: "Unknown server host …"“ | Datenbankzugang kommt aus Umgebungsvariablen, die Cron nicht kennt | Variablen oben in die Crontab eintragen |
| „Error: This does not seem to be a WordPress installation.“ | Falscher `WP_PATH` | Pfad zum Ordner mit `wp-config.php` eintragen |
| „Backup läuft bereits, Abbruch.“ | Vorheriger Lauf noch aktiv | Laufzeit prüfen, Cron-Zeitpunkt verschieben |
| Datenbankarchiv nur 20 Byte groß | Export ist gescheitert, leeres gzip | Skript mit `set -o pipefail` nutzen, Log lesen |
| Keine Log-Datei, Cron scheint nicht zu laufen | Log-Pfad nicht beschreibbar, etwa unter `/var/log` | Log in den Backup-Ordner schreiben |
| Übrig gebliebene `.part`-Dateien | Prozess wurde hart beendet, der `trap` lief nicht | Dateien löschen, Ursache (Speicher, Neustart) klären |

## Häufige Fragen

### Reicht der Snapshot meines Hosters nicht aus?

Ein Snapshot des ganzen Servers ist eine sinnvolle Ergänzung, liegt aber meist beim selben Anbieter und lässt sich oft nur komplett zurückspielen. Das Skript liefert einzelne Archive, aus denen Sie gezielt nur die Datenbank oder einzelne Dateien holen können.

### Wie oft sollte das Backup laufen?

So oft, wie Sie Datenverlust hinnehmen können. Für eine Website, die sich wöchentlich ändert, genügt ein nächtlicher Lauf. Bei einem Shop mit täglichen Bestellungen sichern Sie die Datenbank häufiger, etwa alle paar Stunden mit einem zweiten Cronjob nur für den Datenbankteil.

### Funktioniert das auch auf Shared Hosting?

Nur, wenn der Hoster SSH, WP-CLI und eigene Cronjobs erlaubt. Viele Hoster bieten Cronjobs im Kundenmenü an; der Befehl bleibt derselbe, die Pfade nennt der Hoster.

## Fazit

Ein Shell-Skript mit Cronjob sichert WordPress unabhängig vom Zustand der Website und lässt sich vollständig nachvollziehen. Entscheidend sind die Details: Abbruch bei Fehlern, geprüfte Archive, ein eigener `PATH` für Cron, eine Kopie außerhalb des Servers und eine geprobte Wiederherstellung. Wenn Sie Backups und Updates lieber in feste Hände geben, übernimmt das die [WordPress-Wartung von Marcel Schönfelder](https://wordpressupdate.de/?utm_source=s-edv.com&utm_medium=fazit&utm_campaign=20).

## Weiterführende Anleitungen und Quellen

- [Cron und crontab richtig nutzen: die Grundlagen](https://s-edv.com/anleitungen/cron-crontab-grundlagen-linux)
- [3-2-1-Backup-Strategie umsetzen](https://s-edv.com/anleitungen/3-2-1-backup-strategie-praktisch-umsetzen)
- [Backup-Restore-Test als feste Routine etablieren](https://s-edv.com/anleitungen/backup-restore-test-routine-etablieren)
- [Docker und Docker Compose auf Linux installieren](https://s-edv.com/anleitungen/docker-compose-linux-selfhosting-grundlage)
- [WP-CLI-Handbuch: wp db export](https://developer.wordpress.org/cli/commands/db/export/)
- [WP-CLI-Handbuch: wp db import](https://developer.wordpress.org/cli/commands/db/import/)
- [WordPress Advanced Administration: Backups](https://developer.wordpress.org/advanced-administration/security/backup/)
