Den eigenen Web-Server sichern

Keywords: #backup #bash #hosting #shell #web-server

Die Datensicherung des eigenen, öffentlichen Webservers ist nicht nur wichtig, sie kann auch auf vielen Wegen geschehen und wirft vermutlich gerade deshalb sehr viele Fragen auf. Soll ich ein Image des ganzen Servers anlegen oder nur einzelne Ordner sichern? Wie oft muss ich ein Backup machen und wie gelange ich im Notfall an die Daten? Reicht es aus, z.B. als Wordpress-Nutzer, ein Plugin zu verwenden oder ist es sinnvoller externe Software zu nutzen?

Ich habe versucht ein paar solcher Fragen, auch für mich selber, zu beantworten und als Konsequenz daraus ein Backup-Strategie zu entwickeln und dann auch umzusetzen. Das Ziel ist ein gesunder Mittelweg zwischen Sicherheit und Bedienbarkeit. Da ich selber sehr oft mit Wordpress arbeite, soll dieser Beitrag vornehmlich die Wordpress-Nutzer ansprechen. Wenn du Wordpress allerdings nur als Service nutzt (z.B. auf wordpress.org), wird dir dieser Beitrag wenig weiterhelfen. Außerdem ist es sehr hilfreich, wenn du per SSH Zugriff auf deinen Server hast. Sicherlich gibt es auch Möglichkeiten, den hier aufgezeigten Weg ohne SSH zu beschreiten - wie das funktioniert, muss man aber im Einzelnen sehen.

Die Backup-Strategie

Wo soll das Backup gespeichert werden?

Die erste Frage die ich mir gestellt habe war: Wo soll das Backup landen? Direkt auf dem Web-Server? Dann sind die Daten verloren, sobald der ganze Server weg ist. Auf einem Cloud-Speicher? Das ist bequem, birgt allerdings auch Probleme mit den Datenschutzgesetzen. Oder auf einem privaten NAS? Das ist aus Datenschutzsicht zwar in Ordnung, aber dann muss das NAS über das Internet erreichbar sein. Das wollte ich vermeiden.

Ich habe mich deshalb für einen Zwischenweg entschieden: Für den schnellen Zugriff lege ich das Backup auf dem Server ab. Gleichzeitig nutze ich das kostenlose Angebot von blaucloud.de. Das ist ein Cloud-Speicher, dessen Server in Deutschland stehen. Außerdem unterstützt blaucloud, das auf nextcloud basiert, das WebDav-Protokoll. Zusätzlich, aber das ist nur ein Bonus. Außerdem habe ich auf meinem NAS eine Synchronisierung mit der blaucloud eingerichtet. Dadurch bleibt das NAS im Internet verborgen und ich habe trotzdem eine 3. Kopie des Backups in meinen eigenen, sicheren vier Wänden.

Welche Software soll ich nutzen?

Die Auswahl von Plugins für automatische Backups mit Wordpress ist sehr umfangreich, einige davon sind kostenlos und die große Mehrheit ist sehr bequem zu bedienen. Die Backups laufen automatisch und auch die Wiederherstellung ist nur einen Mausklick entfernt. Das klingt paradiesisch, aber der Schein trügt. Zunächst halte ich es für absurd, ein System aus sich selber heraus zu sichern. Das ist wie ein Feuerlöscher, der bei Waldbrandgefahr zwischen den trockenen Bäumen steht.

Sicher kann ich mit den zahlreichen Wordpress-Plugins die Datenbank und das Dateisystem sichern. Aber der Prozess wird eben innerhalb eines System ausgeführt, das auch von außen erreichbar ist. Wenn nur ein anderes Plugin kompromittiert wird, gefährdet das die ganze Backup-Strategie. Außerdem muss ich, bei der Verwendung mehrerer Wordpress-Instanzen jedes Backup-Plugin einzeln pflegen.

Auch die regelmäßige Datensicherung, die viele Hoster von sich aus anbieten, reicht mir nicht aus, da diese nur minimal gesteuert werden kann und dort immer das ganze System gesichert wird, man also bei der Wiederherstellung nicht selektieren kann, welche Backup, welcher Ordner oder welche Datenbank zurück gespielt werden soll. Ich habe mich also für duplicity entschieden. Duplicity wird über die Kommandozeile bedient (deshalb der notwendige SSH-Zugang), unterstützt viele Protokolle (FTP, WebDav, Amazon S3, rsync, …) und es gibt sogar eine grafische Benutzeroberfläche - wenn man doch nicht ohne kann. Außerdem bietet duplicity die Verschlüsselung mit GnuPG an und ist damit auch bestens geeignet, um die Datensicherung in der Cloud abzulegen.

Wie oft soll ich ein Backup anlegen?

Die Frage sollte mich nicht länger beschäftigen, vor allem weil ich dazu einen sehr schönen Blog-Eintrag gefunden habe. Dazu muss erklärt werden, dass duplicity mit inkrementellen Backups arbeitet. Dabei wird initial ein komplettes Backup angelegt. Danach werden nur noch die Änderungen an den zu sichernden Dateien erfasst. Für die Wiederherstellung muss also erst das letzte volle Backup zurückgespielt werden, um darauf dann die inkrementellen Backups “anzuwenden. Der Backup-Plan lautet also wie folgt:

  • es erfolgt initial eine volle Datensicherung
  • danach gibt es jeden Tag eine inkrementelle Datensicherung
  • jeden Monat erfolgt eine volle Datensicherung
  • inkrementelle, tägliche Backups, die älter sind als ein Monat, werden gelöscht
  • volle Backups, die älter als 12 Monate sind, werden gelöscht

Im Notfall muss also zuerst das letzte volle Backup eingespielt werden, das nicht älter als ein Monat ist. Muss man Daten wiederherstellen, die älter sind als ein Monat, stehen diese immer nur für die monatlichen vollen Backups zur Verfügung. Für die Zeit vor 12 Monaten gibt es keine Datensicherung.

Welche Daten sollen gesichert werden?

Wie bereits erwähnt, stört mich bei den meisten Hostern, dass immer ein Backup des ganzen Servers angelegt wird. Da auf einem Server gerne aber mehr als eine Domain untergebracht ist, möchte ich die Datensicherung gerne je Domain und Datenbank durchführen. Mein Ziel ist es also, jeden Ordner im Dateisystem der einer (Sub-)Domain zugeordnet ist sowie jede Datenbank getrennt zu sichern. So kann ich eine Wiederherstellung auch punktuell anstoßen.

Vorbereitung

Bevor es jetzt ans Eingemachte geht, der übliche Hinweis zur gebotenen Vorsicht: Wenn du nicht weißt, was hier passiert, lass dich von jemanden unterstützen, der weiß, was hier passiert. Wer auf der Konsole arbeitet, kann sehr schnell sehr viel falsch machen.

Duplicity und Verschlüsselung einrichten

Die erste Hürde, die du nehmen musst, ist die Installation von duplicity. Entweder du bekommst das über die Konsole selber hin - oder du fragst bei deinem Hoster nach. In vielen Fällen kann auch ein Shared Hostern das für dich installieren:

sudo apt-get install duplicity

Als nächstes benötigst du GnuPG. Das ist bei den meisten Hostern vorinstalliert. Sollte dem nicht so sein, fragst du entweder den Support oder erledigst das mit folgendem Befehl selber:

sudo apt-get install gnupg2

GnuPG ist ein Software zur Verschlüsselung von Informationen bzw. Dateien, das z.B. auch bei der Verschlüsselung von E-Mails zum Einsatz kommt. Für diesen Prozess werden zwei sogenannte Schlüssel benötigt - der private und der öffentliche Schlüssel. Der öffentliche Schlüssel dient dazu, die Daten zu verschlüsseln, mit dem privaten Schlüssel kannst du den Prozess “umkehren” - die Daten also entschlüsseln. Diese beiden Schlüssel müssen zunächst einmal erzeugt werden:

gpg --gen-key

Du musst dazu ein paar Fragen beantworten. Die Frage nach der Schlüssel-Art beantwortest du mit 1, bzw. RSA and RSA. Als Schlüssellänge (keysize) empfiehlt sich 2.048 bits. Die Gültigkeitsdauer beträgt “unendlich”. Danach kannst, musst aber nicht, du deinen Namen und Kontaktdaten angeben. Abschließend wirst du nach einem Passwort für den privaten Schlüssel gefragt und aufgefordert durch ein paar zufällige Tasteneingaben eine Entropie zu erzeugen. Danach befindet sich im Ordner ~/.gnupg dein Schlüsselpaar.

Ein GnuPG Schlüsselpaar erzeugen

Außerdem quittiert dir gnupg die Erstellung des Schlüsselpaars mit einer Statistik, aus der du dir die Id für den öffentlichen Schlüssel merken musst:

Cloud-Speicher einrichten

Der kostenlose blaucloud-Account für 5 GByte-Speicher ist ziemlich schnell eingerichtet. Dazu benötigst du nur einen beliebigen Benutzernamen, eine E-Mail-Adresse und ein Passwort. Über den Benutzerbnamen wird später auch deine Cloud erreichbar sein. Ich habe eine zufällige Zeichenkette verwendet, damit die Verbindung zur Funktion als Backup-Speicher nicht auf den ersten Blick ersichtlich ist (z.B. qwertz123.blaucloud.de). Danach musst du nur noch deine E-Mail-Adresse bestätigen und schon ist der Cloud-Speicher über folgende URL verfügbar:

webdav://benutzername:passwor@benutzername.blaucloud.de/remote.php/webdav/

MySQL-Benutzer einrichten

Natürlich kannst du für das Backup einfach den Benutzer nutzen, den du auch für administrative Zwecke nutzt. Ich verrate dir aber kein Geheimnis wenn ich dir sage, dass es sehr sinnvoll ist, dafür einen eigenen Benutzer anzulegen, insofern deine Hosting-Umgebung das zulässt. Dazu führst du auf einer beliebigen Oberfläche (phpMyAdmin, MySQL Workbench oder direkt über das MySQL-CLI) folgende Query aus. Hier setzt du nur einen Benutzernamen und ein Passwort ein.

GRANT LOCK TABLES, SELECT ON *.* TO 'USERNAME'@'localhost' IDENTIFIED BY 'PASSWORD';
GRANT SHOW VIEW ON *.* TO 'USERNAME'@'localhost'

Das ging schnell und hat auch gar nicht weh getan. ;)

Die Backup-Strategie umsetzen

Die Zugangsdaten ablegen

Die Informationen, die wir oben gesammelt haben, werden erstmal in der Datei backup.conf abgelegt. Bitte beachte, dass die Id für den öffentlichen Schlüssel nur ein Verweis ist. Die tatsächlichen Schlüssel liegen im Benutzerordner unter ~/.gnugpg/.

# mit diesem Wert steuerst du die Ausgabe von duplicity
# je höher, desto mehr Debug-Nachrichten werden ausgegeben
# das hilft bei der Fehlersuche
export DUPLICITY_VERBOSITY=2

# die Passphrase ist das Passwort für den privaten Schlüssel
# die Id für den öffentlichen Schlüssel wird utner GPG_PUP_KEY abgelegt
export PASSPHRASE=secret_gpgp_key_password
export GPG_PUB_KEY=public_gpgp_key_id

# dieser Ordner wird für die lokalen Backups verwendet
export BASE_PATH_BACKUP=/private-backup/
# dieser Ordner enthält die temporären MySQL-Dumps sowie den Cache von duplicity
export BASE_PATH_TEMP=/private-backup/temp/
export LOG_FILE=backup.log

# das sind die Zugangsdaten zu deinem WebDav-Anbieter
export WEBDAV_USER=webdav_user
export WEBDAV_PASSWORD=webdav_password
export WEBDAV_URL=somewhere.server.de/webdav.php/folder/backup

# schließlich hinterlegst du noch eine E-Mail-Adresse 
# an die Fehlernachrichten geschickt werden 
export SUPERVISOR_EMAIL=error_messages@foobar.com
# und die E-Mail-Adresse des Absenders
export LOCAL_EMAIL=sender@foobar.com

Die Zugangsdaten für den MySQL-Server gehören in eine andere Datei, nämlich database.conf:

[client] user=mysql_user password=mysql_password host=localhost

Die Ordner der virtuellen Hosts sichern

Im Folgenden werde ich die Shell-Scripte und den Prozess ganz kurz erklären.

Zunächst will ich zwei Server-System unterstützen: nginx und apache2. Dazu frage ich den ersten Parameter ab:

if [[ $1 == ‘apache’ ]] then

    SERVER\_SOFTWARE='apache'

elif [[ $1 == ’nginx’ ]] then SERVER_SOFTWARE=‘nginx’

else

    echo 'Keine Server-Architektur angegeben, probiere mal nginx oder apache'

fi

Wie oben schon angedeutet, will ich nicht einmal das ganze Dateisystem sichern, sondern nur relevante Ordner. Dazu muss ich erwähnen, dass ich für jede Domain eine Konfigurations-Datei angelegt habe - so ist es im übrigen auch üblich (siehe z.B. /etc/apache2/sites-enabled/). Jede dieser Dateien enthält den Verweis auf den Ordner der jeweiligen Domain (oft z.B. /var/www/…) Diese Informationen gilt es nun automatisch herauszufinden. Ich nutze dafür zunächst den folgenden Aufruf, der mir erstmal eine ungefilterte  Liste der gesamten Webserver-Konfiguration ausgibt:

/usr/sbin/apache2ctl -S

Die Ausgabe ist stark gekürzt und enthält natürlich mehr als nur eine Domain und diese auch immer doppelt - einmal für https und einmal für http. Ich benötige aus dieser Liste nun einmal den Pfad zur Konfigurations-Datei jeder Domain. Dazu reduziere ich die Ausgabe also auf die gewünschten Zeilen und extrahiere dann mit awk und sed den Verweis zu der jeweiligen Konfigurations-Datei. Das ist der komplette Aufruf:

/usr/sbin/apache2ctl -S | grep "port 80 namevhost" | awk -F ' ' '{ print $5 }' | sed -E 's/[:()]//g' | sed -E 's/[ 0-9]$//g'

Das Ergebnis ist eine Liste von Konfigurationsdateien, die ich mit grep nach der Angabe des Ordners durchsuchen kann:

grep -oE 'DocumentRoot \"(.*)\"' $configFile | awk -F ' ' '{ print $2 }' | sed -E 's/["]//g'

Diesen Ordner kann ich dann an das Backup-Script übergeben, auf das ich später zurück kommen werden. Das ganze Script ist auf github verfügbar.

Ich muss hier allerdings erwähnen, dass das ganze eine kleinen Nachteil hat: Es werden nur aktivierte virtuelle Hosts erkannt. Wer zusätzliche andere Ordner sichern will, die z.B. nicht über die virtual host-Konfiguration genutzt werden, muss dies per Hand tun - doch dazu später mehr.

Für nginx nutze ich eine etwas einfachere Variante: Ich nehme einfach alle Dateien mit der Endung conf aus dem Konfigurations-Ordner von nginx. Das funktioniert eben nur, solange du die Konfiguration zentral an einem Ort verwaltest. Der ganze Abschnitt sieht dann so aus:

if [[ $SERVER_SOFTWARE == ‘apache’ ]] then configFilesString=$(/usr/sbin/apache2ctl -S | grep “port 80 namevhost” | awk -F ’ ’ ‘{ print $5 }’ | sed -E ’s/[:()]//g’ | sed -E ’s/[ 0-9]$//g’) configFiles=($(echo “$configFilesString” | tr ‘,’ ’ ‘))

elif [[ $SERVER_SOFTWARE == ’nginx’ ]] then

    configFiles=(/etc/nginx/conf.d/\*.conf)

fi

 

Update November 2018:

Da ich mittlerweile auf nginx und php-fpm umgestiegen bin, habe ich auch das Script entsprechend angepasst. Es kann nun für beide Server-Typen angewendet werden.

Nun kann ich jede Config-Datei einmal einlesen um herauszubekommen, an welcher Stelle im Dateisystem sich der Ordner mit dem Document Root befindet - den wollen wir ja schließlich sichern:

if [[ $SERVER_SOFTWARE == ‘apache’ ]] then

srcFolder=$(grep -oE 'DocumentRoot \\"(.\*)\\"' $configFile | awk -F ' ' '{ print $2 }' | sed -E 's/\["\]//g')
dstFolder=$(basename $srcFolder)

elif [[ $SERVER_SOFTWARE == ’nginx’ ]] then

srcFolder=$(grep -oE 'root (.\*);' $configFile | awk -F ' ' '{ print $2 }' | sed -E 's/\[;\]//g')
dstFolder=$(basename ${srcFolder%htdocs})

fi

 

Die Datenbanken des MySQL-Servers sichern

Nun geht es an die Datenbanken. Dazu nutze ich den CLI-MySQL-Client und frage erstmal einfach alle Datenbanken ab:

mysql --defaults-extra-file=database.conf -Bse 'show databases'

Die Zugangsdaten zum Server übergebe ich in der Datei database.conf. Ich könnte das auch über die Kommandozeile tun, würde dann aber eine Warnung von MySQL erhalten, dass das nicht sicher sei:

Warning: Using a password on the command line interface can be insecure.

Da die Ausgabe von mysql nur einen String zurückgibt, muss ich den erst in ein Array umwandeln, dass ich dann durch-loopen kann. Dazu gibt es zwei Wege - den unteren finde ich etwas eleganter. Entscheide dich einfach für einen:

databasesArray=($(echo “$databasesString” | tr ‘,’ ’ ‘)) IFS=’_’ read -r -a databasesArray«< “$databasesString”

Nun hast du also ein Array, dass deine Datenbanken enthält. Als nächstes brauchst du eine Schleife um für jede Datenbank ein Dump anzulegen:

ignoreDatabases=[‘information_schema,sys,performance_schema’] for database in “${databasesArray[@]}” do

    if \[\[ ! " ${ignoreDatabases\[\*\]} " == \*"${database}"\* \]\]
    then
        ...
    fi

done

Wie du siehst, habe ich noch eine Abfrage eingebaut, damit nicht benötigte System-Datenbanken ausgelassen werden. Jetzt kümmern wir uns um den eigentlichen Dump. Die folgenden Zeilen kommen an die Stelle mit den drei Punkten…

Jetzt werden die Daten aus der jeweiligen Datenbank gezogen. Wenn mysqldump einen Fehler zurück gibt, schicke ich mir diesen per E-Mail. Danach wird das Script aufgerufen, dass den Dump mit duplicity an einen “sicheren” Ort kopiert. Zum Abschluss wird der Dump wieder entfernt.

result="$( ( mysqldump –defaults-extra-file=database.conf ${database} > ${BASE_PATH_TEMP}${database}.sql ) 2>&1 )”

len=${#result}

if [ “$len” -gt “0” ] then echo $result | mail -s “Error when dumping mysql database ${database}” $SUPERVISOR_EMAIL -r $LOCAL_EMAIL

fi

./backupFilesystem.sh -f ${BASE_PATH_TEMP}${database}.sql -d “${BASE_PATH_BACKUP}databases/${database}”

rm ${BASE_PATH_TEMP}${database}.sql

 

Eine Sache gibt es zu beachten, die ich hier nicht weiter beschreiben: Ich nutze “mail” um die Fehlernachrichten zu versenden. Ihr könnte hier einen eigenen E-Mail-Client oder eine andere Variante der Fehlerbenachrichtigung nutzen, das bleibt euch überlassen.

Die Daten mit duplicity verschlüsselt sichern

Nun geht es ans Eingemachte bzw. die tatsächliche Datensicherung. Das Script backupFilesystem.sh erwartet zwei Parameter: Die Dateien bzw. der Ordner, der gesichert werden soll und der Name des Zielordners.

Danach wird duplicity mehr als ein mal aufgerufen. Zunächst wird, wenn das letzte Vollbackup älter ist als 1 Monat, ein komplettes Backup angelegt. Danach wird duplicity angewiesen, Backups, die älter sind als 12 Monate, zu entfernen. Dann werden außerdem die inkrementellen Backups entfernt, die älter sind als 1 Monat. Das ganze wird einmal aufgerufen um die Backups lokal abzulegen und dann ein 2. Mal für den WebDav-Speicher.

Wer Ordner und Dateien manuell sichern will, kann also dieses Script mit den entsprechenden Parametern aufrufen.

Automatisieren mit cron

Jetzt muss der ganze Spaß natürlich noch automatisch laufen. Dazu werden die folgende Zeilen in die Datei /etc/cron.d/dailyBackup gepackt:

# m h dom mon dow user  command
0 1 * * * root /bin/bash /backup/backupVirtualhosts.sh
0 2 * * * root /bin/bash /backup/backupDatabase.sh

Willst du zusätzliche Ordner sichern, kannst du das über das Script backupFilesystem.sh tun, z.B:

0 1 * * * root /bin/bash /backup/backupFilesystem.sh -f /home/ -d /private-backup/home/

Github

Die gesammelten Scripte und Vorlagen für die Konfigurationsdateien findest du auf github.com. Du kopierst einfach den Inhalt des Ordners scripts an einen Ort auf deinen Server, wie z.B. /backup.