1. SSH Verbindung mit Account aufbauen
Bash
# public key
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJJMjHBOhNS+HEpQigcFeX7avlcxhYSc4oI0P8fSIAnw michu@macbook-pro-von-michu.home
# dann unter Account > Sicherheit > SSH-Keys hinzufügen.
# dann unter Webhosting > Übersicht den Anmeldenamen (z.B ertzssk) und die IPv4 Adresse kopieren (z.B 111.111.1.111)
# Verbindung via cli aufbauen
ssh ertzssk@111.111.1.111
# dann bist du drin2. rclone installieren
Bash
# ssh verbindung aufbauen, dann:
mkdir -p ~/bin
curl -L https://downloads.rclone.org/rclone-current-linux-amd64.zip -o /tmp/rclone.zip
unzip /tmp/rclone.zip -d /tmp/
mv /tmp/rclone-*-linux-amd64/rclone ~/bin/
chmod +x ~/bin/rclone
rclone --version3. rclone config
Bash
# speicherorte hinzufügen
rclone config
New remote: n
name: dropbox
storage: dropbox
client_id leer
client_secret leer
advanced config: n
web browser automatically: y
# hier kommt ein fehler, da kein browser auf server
# in der fehlermeldung siehst du den lokalen link mit port z.B http://127.0.0.1:53682/auth?state=ew-6BPRILkM_ZI8LFTvxAAD
# melde dich in anderem cli tab via ssh an: ssh -L 53682:localhost:53682 xyqicuro@s132.cyon.net
# wenn verbunden, gib im browser http://127.0.0.1:53682/auth?state=ew-6BPRILkM_ZI8LFTvxAAD ein und bestätige, fertig
# dann zurück zu terminal tab 1 - access token wurde generiert
keep this dropbox remote: y
quit config: qSSH Tunnel erklärt:
-L 53682:localhost:53682 bedeutet:
- Port 53682 auf deinem Mac → weiterleiten an → Port 53682 auf dem cyon-Server
- rclone wartet auf dem Server auf eine Antwort von Dropbox – aber der Server hat keinen Browser
- Der Tunnel macht deinen Mac-Browser zum „lokalen Browser“ des Servers
4. Backup einrichten
4.1 Backup File erstellen
Bash
nano ~/backup-sitename.sh4.2. Cyon Backup Skript erstellen
- DB Namen aus Cyon > Datenbank > Datenbanken
- dropbox:/PFAD BACKUPS
- –progress zeigt in live Tab progress an
- –stats 30s sendet stats auch an nohup.out
- –transfers 16 = 16 gleichzeitige Dateiübertragungen (Standard: 4)
- –checkers 32 = 32 parallele Prüfungen ob Dateien übertragen werden müssen (Standard: 8)
Bash
#!/bin/bash
set -euo pipefail
SITE="deinedomain.ch"
SITE_DIR="$HOME/public_html/$SITE"
DB_NAME="db_name"
DB_USER="db_user"
DB_PASS='db_password'
DATE=$(date +%Y-%m-%d)
TMP_DB="/tmp/${SITE}-${DATE}.sql"
REMOTE="dropbox:/$SITE"
MARKER_DIR="$HOME/.backup-markers/$SITE"
mkdir -p "$MARKER_DIR"
> ~/nohup.out #logs löschen vor start
echo "==> Config zip..."
if [[ ! -f "$MARKER_DIR/config" ]] || [[ $(find "$SITE_DIR/wp-config.php" "$SITE_DIR/.htaccess" -newer "$MARKER_DIR/config" 2>/dev/null | wc -l) -gt 0 ]]; then
zip -j "/tmp/config.zip" "$SITE_DIR/wp-config.php" "$SITE_DIR/.htaccess"
rclone copy "/tmp/config.zip" "$REMOTE/latest/config/"
rm "/tmp/config.zip"
touch "$MARKER_DIR/config"
else
echo " ✓ keine Änderungen, übersprungen"
fi
echo "==> Plugins zip..."
if [[ -d "$SITE_DIR/wp-content/plugins" ]]; then
if [[ ! -f "$MARKER_DIR/plugins" ]] || [[ $(find "$SITE_DIR/wp-content/plugins" -newer "$MARKER_DIR/plugins" 2>/dev/null | wc -l) -gt 0 ]]; then
zip -r "/tmp/plugins.zip" "$SITE_DIR/wp-content/plugins/"
rclone copy "/tmp/plugins.zip" "$REMOTE/latest/plugins/"
rm "/tmp/plugins.zip"
touch "$MARKER_DIR/plugins"
else
echo " ✓ keine Änderungen, übersprungen"
fi
else
echo " ⚠️ plugins nicht gefunden, übersprungen"
fi
echo "==> MU-Plugins zip..."
if [[ -d "$SITE_DIR/wp-content/mu-plugins" ]]; then
if [[ ! -f "$MARKER_DIR/mu-plugins" ]] || [[ $(find "$SITE_DIR/wp-content/mu-plugins" -newer "$MARKER_DIR/mu-plugins" 2>/dev/null | wc -l) -gt 0 ]]; then
zip -r "/tmp/mu-plugins.zip" "$SITE_DIR/wp-content/mu-plugins/"
rclone copy "/tmp/mu-plugins.zip" "$REMOTE/latest/mu-plugins/"
rm "/tmp/mu-plugins.zip"
touch "$MARKER_DIR/mu-plugins"
else
echo " ✓ keine Änderungen, übersprungen"
fi
else
echo " ⚠️ mu-plugins nicht gefunden, übersprungen"
fi
echo "==> Themes zip..."
if [[ -d "$SITE_DIR/wp-content/themes" ]]; then
if [[ ! -f "$MARKER_DIR/themes" ]] || [[ $(find "$SITE_DIR/wp-content/themes" -newer "$MARKER_DIR/themes" 2>/dev/null | wc -l) -gt 0 ]]; then
zip -r "/tmp/themes.zip" "$SITE_DIR/wp-content/themes/"
rclone copy "/tmp/themes.zip" "$REMOTE/latest/themes/"
rm "/tmp/themes.zip"
touch "$MARKER_DIR/themes"
else
echo " ✓ keine Änderungen, übersprungen"
fi
else
echo " ⚠️ themes nicht gefunden, übersprungen"
fi
echo "==> DB Dump..."
mysqldump -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$TMP_DB"
rclone sync "$TMP_DB" "$REMOTE/latest/db/" \
--backup-dir "$REMOTE/$DATE/db/"
rm "$TMP_DB"
echo "==> Uploads zu Dropbox..."
rclone sync "$SITE_DIR/wp-content/uploads" "$REMOTE/latest/uploads/" \
--progress \
--stats 30s \
--transfers 16 \
--checkers 32
echo "==> Alte Backups löschen (>7 Tage)..."
for dir in $(rclone lsd "$REMOTE/" | awk '{print $5}' | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'); do
if [[ "$dir" < "$(date -d '7 days ago' +%Y-%m-%d)" ]]; then
rclone purge "$REMOTE/$dir"
fi
done
echo "==> Fertig: $DATE"4.3. Backup File ausführbar machen
Bash
chmod +x ~/backup-sitename.sh
# backup ausführen
~/backup-sitename.sh5. Backup erstellen
Bash
# bei kleinen sites <2GB - cli tab offen lassen
~/backup-sitename.sh
# bei grossen sites 2GB+ -
nohup ~/backup-sitename.sh &nohup= läuft weiter nach Terminal-Schliessung&= läuft im Hintergrund- Output landet in
~/nohup.out– kannst du mittail -f ~/nohup.outverfolgen
Bash
tail -f ~/nohup.out
# Zeigt dir live was rclone gerade macht. Mit Ctrl+C beenden (Script läuft weiter).6. Automatisches Backup in der Nacht um 3.00 Uhr
Bash
# in cli tab
crontab -eBash
# zuunterst einfügen
0 3 * * * /home/USERNAME/backup-SITENAME.sh > /home/USERNAME/backup-SITENAME.log 2>&17. Restore Skript erstellen
7.1 File erstellen
Bash
nano ~/restore-sitename.ch7.2 Cyon Restore Skript erstellen
Bash
#!/bin/bash
set -euo pipefail
SITE="deinedomain.ch"
SITE_DIR="$HOME/public_html/$SITE"
DB_NAME="db_name"
DB_USER="db_user"
DB_PASS='db_password'
REMOTE="dropbox:/$SITE"
echo "==> Config wiederherstellen..."
rclone copy "$REMOTE/latest/config/config.zip" /tmp/
unzip -o /tmp/config.zip -d "$SITE_DIR/"
rm /tmp/config.zip
echo "==> Plugins wiederherstellen..."
rclone copy "$REMOTE/latest/plugins/plugins.zip" /tmp/
unzip -o /tmp/plugins.zip -d "$SITE_DIR/wp-content/"
rm /tmp/plugins.zip
echo "==> MU-Plugins wiederherstellen..."
if rclone ls "$REMOTE/latest/mu-plugins/" 2>/dev/null | grep -q "mu-plugins.zip"; then
rclone copy "$REMOTE/latest/mu-plugins/mu-plugins.zip" /tmp/
unzip -o /tmp/mu-plugins.zip -d "$SITE_DIR/wp-content/"
rm /tmp/mu-plugins.zip
else
echo " ✓ keine mu-plugins, übersprungen"
fi
echo "==> Themes wiederherstellen..."
rclone copy "$REMOTE/latest/themes/themes.zip" /tmp/
unzip -o /tmp/themes.zip -d "$SITE_DIR/wp-content/"
rm /tmp/themes.zip
echo "==> Uploads wiederherstellen..."
rclone sync "$REMOTE/latest/uploads/" "$SITE_DIR/wp-content/uploads/" \
--transfers 16 \
--checkers 32 \
--progress \
--stats 30s
echo "==> DB wiederherstellen..."
rclone copy "$REMOTE/latest/db/" /tmp/
mysql -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" < /tmp/*.sql
rm /tmp/*.sql
echo "==> Fertig!"7.3 File ausführbar machen
Bash
chmod +x ~/restore-sitename.sh
# backup ausführen
~/restore-sitename.shWas passiert beim Skript?
- Files – zieht
latest/files/von Dropbox auf den Server, überschreibt nur geänderte/fehlende - DB herunterladen – kopiert den SQL-Dump aus Dropbox nach
/tmp/ - DB wiederherstellen – importiert den SQL-Dump in die DB, überschreibt alles
- Cleanup – löscht den temporären SQL-Dump
- Fertig
Site ist danach identisch zum letzten Backup-Zeitpunkt.
RESTORE DATUM
Bash
#!/bin/bash
export PATH="$HOME/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
set -euo pipefail
if [[ $# -ne 1 ]]; then
echo "Usage: $0 YYYY-MM-DD"
echo "Beispiel: $0 2026-05-15"
exit 1
fi
TARGET="$1"
if ! [[ "$TARGET" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "Fehler: Datum muss im Format YYYY-MM-DD sein"
exit 1
fi
SITE="younique-onlinekurs.com"
SITE_DIR="$HOME/public_html/$SITE"
DB_NAME="xyqicuro_younique"
DB_USER="xyqicuro_younique"
DB_PASS='S[e.RhW5bP(H2)@7p.'
REMOTE="dropbox:/$SITE"
echo "==> Restore auf Stand vom $TARGET"
echo ""
# Liste aller Tagesordner > TARGET, aufsteigend sortiert (ältester zuerst)
NEWER_DIRS=$(rclone lsd "$REMOTE/" | awk '{print $5}' | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' | sort | awk -v t="$TARGET" '$0 > t')
# Ältester Tagesordner > TARGET (für Zip-Restore)
OLDEST_NEWER=$(echo "$NEWER_DIRS" | head -1)
restore_zip() {
local TYPE="$1"
local ZIPNAME="$2"
local UNZIP_DEST="$3"
echo "==> $TYPE wiederherstellen..."
local SOURCE_DIR=""
# Suche ältesten Tagesordner > TARGET, der ein Zip dieses Typs enthält
for dir in $NEWER_DIRS; do
if rclone ls "$REMOTE/$dir/$TYPE/" 2>/dev/null | grep -q "$ZIPNAME"; then
SOURCE_DIR="$REMOTE/$dir/$TYPE/"
echo " Quelle: $dir/$TYPE/ (Version die nach $TARGET ersetzt wurde)"
break
fi
done
# Falls keiner gefunden: nimm latest (zip wurde seit TARGET nicht ersetzt)
if [[ -z "$SOURCE_DIR" ]]; then
if rclone ls "$REMOTE/latest/$TYPE/" 2>/dev/null | grep -q "$ZIPNAME"; then
SOURCE_DIR="$REMOTE/latest/$TYPE/"
echo " Quelle: latest/$TYPE/ (seit $TARGET unverändert)"
else
echo " ⚠️ keine Quelle gefunden, übersprungen"
return
fi
fi
rclone copy "$SOURCE_DIR$ZIPNAME" /tmp/
unzip -o "/tmp/$ZIPNAME" -d "$UNZIP_DEST"
rm "/tmp/$ZIPNAME"
}
restore_zip "config" "config.zip" "$SITE_DIR/"
restore_zip "plugins" "plugins.zip" "$SITE_DIR/wp-content/"
restore_zip "mu-plugins" "mu-plugins.zip" "$SITE_DIR/wp-content/"
restore_zip "themes" "themes.zip" "$SITE_DIR/wp-content/"
echo "==> Uploads wiederherstellen..."
echo " Basis: latest/"
rclone sync "$REMOTE/latest/uploads/" "$SITE_DIR/wp-content/uploads/" \
--transfers 16 --checkers 32 --progress --stats 30s
# Overlay: neueste Tagesordner zuerst, älteste zuletzt (älteste gewinnt → näher am TARGET)
for dir in $(echo "$NEWER_DIRS" | sort -r); do
if rclone ls "$REMOTE/$dir/uploads/" 2>/dev/null | head -1 | grep -q .; then
echo " Overlay: $dir/uploads/"
rclone copy "$REMOTE/$dir/uploads/" "$SITE_DIR/wp-content/uploads/" \
--transfers 16 --checkers 32
fi
done
echo "==> DB wiederherstellen..."
if rclone ls "$REMOTE/$TARGET/db/" 2>/dev/null | grep -q "\.sql\.gz$"; then
echo " Quelle: $TARGET/db/"
rclone copy "$REMOTE/$TARGET/db/" /tmp/
gunzip -c /tmp/*.sql.gz | mysql -u "$DB_USER" -p"$DB_PASS" "$DB_NAME"
rm /tmp/*.sql.gz
else
# Fallback: ältester Tagesordner > TARGET (entspricht Stand kurz nach TARGET)
FALLBACK_DB=""
for dir in $NEWER_DIRS; do
if rclone ls "$REMOTE/$dir/db/" 2>/dev/null | grep -q "\.sql\.gz$"; then
FALLBACK_DB="$dir"
break
fi
done
if [[ -n "$FALLBACK_DB" ]]; then
echo " ⚠️ Keine DB für $TARGET, verwende $FALLBACK_DB (nächster Tag)"
rclone copy "$REMOTE/$FALLBACK_DB/db/" /tmp/
gunzip -c /tmp/*.sql.gz | mysql -u "$DB_USER" -p"$DB_PASS" "$DB_NAME"
rm /tmp/*.sql.gz
else
echo " ⚠️ Keine DB-Sicherung gefunden für $TARGET oder später"
fi
fi
echo ""
echo "==> Fertig: Stand wiederhergestellt auf $TARGET"
8. Laufendes Skript abbrechen – anpassen und neu starten
Bash
ps aux | grep rclone
# es sollte zwei anzeigen. grep selbst und den rclone sync
# nimm die id des rclone syncs und dann
kill 10765333 z.B
# dann nano ~/backup-sitename.sh anpassungen vornehmen und skript danach neu starten9. Bash Profil alias hinzufügen
Bash
# User specific aliases and functions
alias backup-sitename='nohup ~/backup-sitename.sh &'
alias log='tail -f ~/nohup.out'10. Kontext
Täglich automatisch (03:00):
- config,zip (aus htaccess und wp-config.php)
- plugins.zip (wenn 1 plugin aktualisiert, macht es alles neu)
- themes.zip
- mu-plugins.zip
- uploads → inkrementell
- db → inkrementell
- 7-Tage-Rotation
Bei Restore:
- Plugin korrupt → restore Script → wenige Minuten
- Totalausfall → WP-CLI + restore Script → läuft wieder