netcup-Snapshots per SCP-REST-API: vor jedem Update automatisch sichern
Teil 17 der Reihe netcup Spezial: Snapshots über die SCP-REST-API anlegen, auflisten, zurückspielen und löschen, mit einem Shell-Skript für den Snapshot vor jedem Update. Mit UEFI-Boot geht das nur offline, das Skript fährt den Server dafür kurz herunter und wieder hoch.
Geprüft am 05.11.2026 · für netcup SCP REST API 2026.1103, jq 1.8.2, curl 8.7.1
Mit KI erstellt – redaktionelle Prüfung ausstehend
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Vor einem großen Update ist ein Snapshot die schnellste Rückfahrkarte. Im Server Control Panel (SCP) von netcup geht das mit ein paar Klicks, automatisch vor jedem Update geht es über die SCP-REST-API. Diese Anleitung zeigt, wie Sie Snapshots per API anlegen, auflisten, zurückspielen und löschen, und liefert ein kleines Shell-Skript dafür. Auf einem Server mit UEFI-Boot geht das nur mit kurzer Abschaltung. Das Skript erkennt das selbst und erledigt den Neustart.
Dies ist Teil 17 der Reihe „netcup Spezial“.
Als Beispiel dient ein netcup-VPS mit Ubuntu und Docker.
Was ein netcup-Snapshot ist und was nicht
Ein Snapshot hält den Zustand einer Festplatte fest. Spielen Sie ihn zurück, ist alles weg, was danach geschrieben wurde: Dateien, Datenbankeinträge, Logs. Im Test fehlte danach sogar der Systemstart dazwischen im Journal.
netcup unterscheidet zwei Arten:
- Online-Snapshot: wird bei laufendem Server angelegt. Laut netcup ist er nicht verfügbar, wenn UEFI-Boot aktiviert ist. Das SCP zeigt dazu den Hinweis „Online-Snapshots sind nicht verfügbar, wenn UEFI-Boot aktiviert ist.“
- Offline-Snapshot: wird bei ausgeschaltetem Server für eine bestimmte Festplatte angelegt, im Test
vda.
Ein Snapshot ersetzt keine Sicherung. Er liegt bei netcup beim Server. Außerdem warnt das SCP bei der Speicheroptimierung: „Snapshots werden durch diesen Vorgang gelöscht.“ Für Daten, die Sie wirklich brauchen, gehört eine Sicherung außerhalb des Servers dazu. Wie Sie UEFI im SCP ein- und ausschalten, steht in Teil 4, die Bedienung der Snapshots in der Oberfläche in netcup VPS-Snapshots: Offline- vs. Online-Snapshot.
Das Bild zeigt den Reiter Medien und Festplatten mit einem angelegten Snapshot und dem Hinweis zur Speicheroptimierung.

Voraussetzungen
- Ein netcup-Server im SCP
- Ein zweiter Rechner mit Linux oder macOS,
curlundjq, von dem aus Sie die API ansprechen. Auf dem Server selbst darf das Skript nicht laufen: Es schaltet den Server aus und würde sich dabei mit herunterfahren. - Ein Refresh-Token für die SCP-REST-API, wie in Schritt 1 beschrieben
Planen Sie eine kurze Unterbrechung ein. Dienste, die nicht von selbst starten, starten Sie danach von Hand.
Schritt 1: Zugang zur SCP-REST-API einrichten
Die API meldet Sie über einen Gerätecode an. Wie Sie ihn anfordern, im Browser bestätigen und den Refresh-Token in ~/.config/netcup-scp/refresh_token ablegen, beschreibt Schritt 6 in Teil 19. Das Skript in dieser Anleitung erwartet den Token an derselben Stelle, eine andere Datei geben Sie über die Variable TOKENDATEI an.
Dieser Token gibt Zugriff auf Ihr SCP-Konto. Wer ihn hat, kann Ihre Server ausschalten und Snapshots zurückspielen. Behandeln Sie ihn wie ein Passwort. Wie in Teil 19 beschrieben, können Sie im SCP unter API zusätzlich einen IP-Filter setzen.
test -s ~/.config/netcup-scp/refresh_token && ! grep -qx null ~/.config/netcup-scp/refresh_token && echo ok
Verifizieren: Der Befehl gibt ok aus. Steht in der Datei null, war die Bestätigung im Browser noch nicht abgeschlossen.
Schritt 2: Die Snapshot-Endpunkte
Alle Pfade beginnen mit https://www.servercontrolpanel.de/scp-core/api/v1. Laut OpenAPI-Beschreibung gibt es diese Endpunkte:
| Aufruf | Zweck |
|---|---|
GET /servers/{id}/snapshots | alle Snapshots eines Servers |
POST /servers/{id}/snapshots:dryrun | prüfen, ob ein Snapshot möglich ist |
POST /servers/{id}/snapshots | Snapshot anlegen |
POST /servers/{id}/snapshots/{name}/revert | Snapshot zurückspielen |
POST /servers/{id}/snapshots/{name}/export | Snapshot exportieren |
DELETE /servers/{id}/snapshots/{name} | Snapshot löschen |
PATCH /servers/{id} | Server ein- und ausschalten |
Schreibende Aufrufe antworteten im Test mit HTTP 202 und einer Aufgabe. Erst wenn GET /tasks/{uuid} den Zustand FINISHED meldet, ist der Snapshot angelegt oder der Server aus.
Der Probelauf zeigt, ob ein Online-Snapshot geht. Auf unserem Server mit aktiviertem UEFI kam für {"onlineSnapshot": true}:
HTTP 400
{"code": "server.snapshot.create.error.online.uefi", "message": "Online snapshot creation is not possible if UEFI is activated."}
Für einen Offline-Snapshot verlangt die API den Namen der Festplatte. Der Probelauf mit {"onlineSnapshot": false, "diskName": "vda"} lieferte bei laufendem Server HTTP 200 und eine leere Liste. Laut OpenAPI-Beschreibung heißt das: Mit diesen Angaben ist ein Snapshot möglich. Für den Fall, dass beim Online-Snapshot eine Festplatte angegeben ist, nennt die OpenAPI-Beschreibung den Fehlercode server.snapshot.create.error.online.diskselected. Das Skript sendet den Festplattennamen deshalb nur beim Offline-Snapshot.
Zum Ausschalten dient PATCH /servers/{id} mit dem Body {"state": "OFF"} und dem Content-Type application/merge-patch+json. Ohne weiteren Parameter fährt der Server sauber herunter. Im Journal des Servers stand: „System is powering down (hypervisor initiated shutdown).“ Hart abschalten würde erst der Parameter stateOption=POWEROFF.
Verifizieren: Die Server-ID, die das Skript als erstes Argument braucht, liefert GET /servers. Der Aufruf liste in Schritt 3 prüft dann Token und ID zugleich.
Schritt 3: Das Skript scp-snapshot.sh
Das Skript holt mit dem Refresh-Token ein Zugriffstoken, das laut Anmeldedienst 5 Minuten gilt, und erneuert es nach 4 Minuten. Den neuen Refresh-Token, den der Anmeldedienst dabei mitschickt, speichert es ab. Beim Anlegen prüft es per Probelauf, ob ein Online-Snapshot möglich ist. Lehnt die API wegen UEFI ab, macht es einen Probelauf für den Offline-Snapshot, fährt den Server herunter, legt den Snapshot an und startet den Server wieder, sofern er vorher lief. Jede andere Ablehnung beendet das Skript, ohne den Server anzufassen. Gesichert wird die erste Festplatte des Servers, im Test vda. Speichern Sie es als scp-snapshot.sh:
#!/bin/sh
# Snapshots eines netcup-Servers über die SCP-REST-API: liste | anlegen | zurueck | loeschen
# Läuft auf einem anderen Rechner als dem Server, denn es schaltet den Server aus.
set -eu
TOKENDATEI="${TOKENDATEI:-$HOME/.config/netcup-scp/refresh_token}"
KC=https://www.servercontrolpanel.de/realms/scp/protocol/openid-connect/token
API=https://www.servercontrolpanel.de/scp-core/api/v1
if [ $# -lt 2 ]; then
echo "Aufruf: $0 SERVER-ID liste | anlegen NAME [BESCHREIBUNG] | zurueck NAME | loeschen NAME" >&2
exit 2
fi
SID="$1"; AKTION="$2"; NAME="${3:-}"; BESCHREIBUNG="${4:-}"
case "$AKTION" in
anlegen|zurueck|loeschen)
case "$NAME" in
""|*[!A-Za-z0-9._-]*) echo "NAME fehlt oder enthält andere Zeichen als A-Z a-z 0-9 . _ -" >&2; exit 2 ;;
esac ;;
esac
case "$BESCHREIBUNG" in *[\"\\]*) echo "Beschreibung ohne Anführungszeichen und Backslash" >&2; exit 2 ;; esac
trap 'exit 1' INT TERM HUP # damit der EXIT-trap auch bei Strg+C läuft (dash)
TOKENZEIT=0
token() { # Zugriffstoken gilt 5 Minuten, nach 4 Minuten erneuern
JETZT=$(date +%s)
[ $((JETZT - TOKENZEIT)) -lt 240 ] && return 0
ANTWORT=$(curl -fsS "$KC" -d client_id=scp -d grant_type=refresh_token \
--data-urlencode "refresh_token=$(cat "$TOKENDATEI")")
ACCESS=$(printf '%s' "$ANTWORT" | jq -r .access_token)
NEU=$(printf '%s' "$ANTWORT" | jq -r '.refresh_token // empty')
if [ -n "$NEU" ]; then
(umask 077; printf '%s\n' "$NEU" > "$TOKENDATEI.neu") && mv "$TOKENDATEI.neu" "$TOKENDATEI"
fi
TOKENZEIT=$JETZT
}
api() { # api METHODE PFAD [JSON], gibt Body und in der letzten Zeile den HTTP-Code aus
token
if [ "$1" = PATCH ]; then TYP=application/merge-patch+json; else TYP=application/json; fi
if [ -n "${3:-}" ]; then
curl -sS -X "$1" -H "Authorization: Bearer $ACCESS" -H "Content-Type: $TYP" \
-d "$3" -w '\n%{http_code}' "$API$2" || true
else
curl -sS -X "$1" -H "Authorization: Bearer $ACCESS" -H "Content-Type: $TYP" \
-w '\n%{http_code}' "$API$2" || true
fi
}
pruefe() { # gibt den Body aus, bricht bei allem außer HTTP 2xx ab (auch bei Netzfehler 000)
CODE=$(printf '%s' "$1" | tail -n 1); BODY=$(printf '%s' "$1" | sed '$d')
case "$CODE" in
2[0-9][0-9]) printf '%s' "$BODY" ;;
*) echo "Fehler HTTP $CODE: $BODY" >&2; exit 1 ;;
esac
}
warte_task() { # wartet auf eine Aufgabe aus einer 202-Antwort, höchstens 10 Minuten
UUID=$(printf '%s' "$1" | jq -r .uuid)
i=0
while [ $i -lt 120 ]; do
T=$(pruefe "$(api GET "/tasks/$UUID")")
STAND=$(printf '%s' "$T" | jq -r .state)
case "$STAND" in
FINISHED) echo " $(date +%T) Aufgabe fertig"; return 0 ;;
ERROR|CANCELED) echo "Aufgabe endete mit $STAND: $(printf '%s' "$T" | jq -c .responseError)" >&2; exit 1 ;;
esac
i=$((i + 1)); sleep 5
done
echo "Aufgabe nach 10 Minuten nicht fertig" >&2; exit 1
}
zustand() { S=$(pruefe "$(api GET "/servers/$SID")"); printf '%s' "$S" | jq -r .serverLiveInfo.state; }
schalte() { # schalte ON|OFF, wartet auf RUNNING bzw. SHUTOFF
ZIEL=RUNNING; [ "$1" = OFF ] && ZIEL=SHUTOFF
Z=$(zustand); [ "$Z" = "$ZIEL" ] && return 0
echo "$(date +%T) Server $1"
T=$(pruefe "$(api PATCH "/servers/$SID" "{\"state\":\"$1\"}")"); warte_task "$T"
i=0
while :; do
Z=$(zustand); [ "$Z" = "$ZIEL" ] && return 0
i=$((i + 1)); [ $i -ge 60 ] && { echo "Server nicht $ZIEL" >&2; exit 1; }
sleep 5
done
}
wieder_an() { # vor dem Ausschalten setzen: bricht etwas ab, startet der Server wieder
[ "$1" = RUNNING ] && trap 'echo "Abbruch, starte Server wieder" >&2; TOKENZEIT=0; schalte ON' EXIT
return 0
}
case "$AKTION" in
liste)
L=$(pruefe "$(api GET "/servers/$SID/snapshots")")
printf '%s' "$L" | jq -r '.[] | "\(.name)\t\(.creationTime)\tonline=\(.online)\t\(.description // "")"' ;;
anlegen)
S=$(pruefe "$(api GET "/servers/$SID")")
DISK=$(printf '%s' "$S" | jq -r '.serverLiveInfo.disks[0].dev // empty') # erste Festplatte
[ -n "$DISK" ] || { echo "Keine Festplatte gefunden" >&2; exit 1; }
PROBE=$(api POST "/servers/$SID/snapshots:dryrun" '{"onlineSnapshot":true}')
if printf '%s' "$PROBE" | tail -n 1 | grep -q '^2'; then
ONLINE=true; BODY="{\"name\":\"$NAME\",\"description\":\"$BESCHREIBUNG\",\"onlineSnapshot\":true}"
elif printf '%s' "$PROBE" | grep -q 'online.uefi'; then
ONLINE=false; echo "Online-Snapshot wegen UEFI nicht möglich, Server wird dafür kurz ausgeschaltet"
# Offline-Probelauf, bevor der Server ausgeschaltet wird
pruefe "$(api POST "/servers/$SID/snapshots:dryrun" "{\"onlineSnapshot\":false,\"diskName\":\"$DISK\"}")" > /dev/null
BODY="{\"name\":\"$NAME\",\"description\":\"$BESCHREIBUNG\",\"onlineSnapshot\":false,\"diskName\":\"$DISK\"}"
else
pruefe "$PROBE" > /dev/null
fi
VORHER=$(zustand)
if [ "$ONLINE" = false ]; then wieder_an "$VORHER"; schalte OFF; fi
echo "$(date +%T) Snapshot $NAME auf $DISK"
T=$(pruefe "$(api POST "/servers/$SID/snapshots" "$BODY")"); warte_task "$T"
trap - EXIT
[ "$VORHER" = RUNNING ] && schalte ON
echo "$(date +%T) fertig" ;;
zurueck)
T=$(pruefe "$(api GET "/servers/$SID/snapshots/$NAME")") # erst prüfen, dann ausschalten
VORHER=$(zustand)
wieder_an "$VORHER"; schalte OFF
echo "$(date +%T) Snapshot $NAME zurückspielen"
T=$(pruefe "$(api POST "/servers/$SID/snapshots/$NAME/revert")"); warte_task "$T"
trap - EXIT
[ "$VORHER" = RUNNING ] && schalte ON
echo "$(date +%T) fertig" ;;
loeschen)
T=$(pruefe "$(api DELETE "/servers/$SID/snapshots/$NAME")"); warte_task "$T" ;;
*) echo "Unbekannte Aktion: $AKTION" >&2; exit 2 ;;
esac
chmod +x scp-snapshot.sh
Einige Schutzmechanismen sind eingebaut:
- Die Funktion
pruefebricht bei jeder Antwort außer HTTP 2xx ab, auch bei einem Netzfehler. - Namen dürfen nur Buchstaben, Ziffern, Punkt, Unterstrich und Bindestrich enthalten, Beschreibungen keine Anführungszeichen und Backslashes. Beides landet ungeprüft im JSON und im Pfad.
- Bevor das Skript zum Zurückspielen den Server ausschaltet, prüft es, ob der Snapshot existiert. Ein Tippfehler im Namen schaltet den Server also nicht ab.
- Bricht ein Schritt ab, während der Server aus ist, startet ein
trapihn wieder. Die Zeiletrap 'exit 1' INT TERM HUPsorgt dafür, dass das auch bei Strg+C unterdashklappt, der Standard-Shell von Ubuntu und Debian. - Alle Warteschleifen haben eine Obergrenze.
Getestet haben wir das Skript mit dash und jq 1.8.2 auf einem Mac.
Verifizieren: ./scp-snapshot.sh 123456 liste mit Ihrer Server-ID endet ohne Fehler. Ohne Snapshots bleibt die Ausgabe leer.
Schritt 4: Snapshot vor dem Update anlegen
Geben Sie dem Snapshot einen sprechenden Namen, am besten mit Datum:
./scp-snapshot.sh 123456 anlegen vor-update-$(date +%Y%m%d) "vor apt full-upgrade"
Online-Snapshot wegen UEFI nicht möglich, Server wird dafür kurz ausgeschaltet
16:18:26 Server OFF
16:18:37 Aufgabe fertig
16:18:37 Snapshot vor-update-20261105 auf vda
16:18:43 Aufgabe fertig
16:18:43 Server ON
16:18:54 Aufgabe fertig
16:18:54 fertig
Das Herunterfahren dauerte 11 Sekunden, das Anlegen selbst laut Zeitstempel der Aufgabe im ersten Durchgang rund 2 Sekunden. Das Skript fragt nur alle 5 Sekunden nach. Nach 28 Sekunden meldete es „fertig“, per SSH war der Server 35 Sekunden nach dem Ausschalten wieder erreichbar. Den Abbruchschutz haben wir mit einem zweiten Snapshot gleichen Namens geprüft: Die API lehnte ihn erst nach dem Ausschalten mit server.snapshot.create.nameinuse ab, das Skript meldete „Abbruch, starte Server wieder“, schaltete den Server ein und endete mit Exit-Code 1.
Im SCP erscheint der Snapshot unter Medien und Snapshots mit dem Typ „Offline“. Das Bild stammt aus dem ersten Testdurchgang.

Verifizieren: ./scp-snapshot.sh 123456 liste zeigt den neuen Snapshot mit online=false. Die Zeit gibt die API in UTC an:
vor-update-20261105 2026-11-05T15:18:39Z online=false vor apt full-upgrade
Schritt 5: Snapshot zurückspielen
Ging das Update schief, spielen Sie den Snapshot zurück. Das Skript fährt den Server dafür immer herunter:
./scp-snapshot.sh 123456 zurueck vor-update-20261105
16:19:57 Server OFF
16:20:14 Aufgabe fertig
16:20:14 Snapshot vor-update-20261105 zurückspielen
16:20:20 Aufgabe fertig
16:20:20 Server ON
16:20:26 Aufgabe fertig
16:20:26 fertig
Eine Markerdatei, die wir nach dem Anlegen geschrieben hatten, war nach dem Zurückspielen verschwunden. Die Docker-Container mit der Neustart-Regel unless-stopped liefen nach dem Start alle von selbst wieder, im letzten Durchgang alle 17. In einem früheren Durchgang lieferte eine Website hinter Caddy rund eine halbe Minute nach dem Einschalten noch HTTP 500 und rund 40 Sekunden nach dem Einschalten wieder 200. Prüfen Sie Ihre Dienste nach dem Zurückspielen also nicht in der ersten Sekunde.
Verifizieren: uptime auf dem Server zeigt einen frischen Start. Dateien, die Sie nach dem Snapshot angelegt haben, fehlen.
Schritt 6: Snapshot löschen
Lief das Update gut, löschen Sie den Snapshot wieder:
./scp-snapshot.sh 123456 loeschen vor-update-20261105
Der Server läuft dabei weiter.
Verifizieren: ./scp-snapshot.sh 123456 liste zeigt den Snapshot nicht mehr. Eine Abfrage des gelöschten Namens beantwortet die API mit HTTP 404 und error.notfound.
Typische Fehler
| Meldung | Ursache und Lösung |
|---|---|
server.snapshot.create.error.online.uefi | UEFI-Boot ist aktiviert, ein Online-Snapshot ist nicht möglich. Offline-Snapshot anlegen, wie es das Skript tut. |
{"code":"server.snapshot.create.nameinuse","message":"Snapshot name is already in use."} | Einen Snapshot mit diesem Namen gibt es schon. Der Probelauf erkennt das nicht, der Fehler kommt erst beim Anlegen, wenn der Server schon aus ist. Das Skript startet den Server dann wieder. Namen mit Datum vermeiden das. |
Fehler HTTP 404: {"code":"error.notfound","message":"Resource not found"} | Den Snapshot gibt es unter diesem Namen nicht. Namen mit liste prüfen. |
Fehler HTTP 403: {"code":"error.forbidden","message":"Access not allowed"} | Die Server-ID gehört nicht zu Ihrem Konto, im Test bei einer falschen ID. |
{"code":"error.content.type.invalid","message":"The HTTP header Content-Type must be 'type/subtype'"} | Auch ein POST ohne Body wie revert braucht einen Content-Type-Header. Das Skript sendet ihn immer mit. Im Test fehlte er in einer ersten Fassung. |
| Website liefert direkt nach dem Start HTTP 500 | Die Container starten noch. Eine halbe Minute warten. |
Häufige Fragen
Wie lange ist der Server beim Offline-Snapshot weg?
Im letzten Durchgang meldete das Skript nach 28 Sekunden „fertig“, per SSH war der Server nach rund 35 Sekunden wieder da. Einmal brauchte allein das Herunterfahren 67 Sekunden. Rechnen Sie mit ein bis zwei Minuten, bis alle Dienste laufen.
Geht der Online-Snapshot, wenn ich UEFI ausschalte?
Die API nennt UEFI als Grund. Ob ein Server ohne UEFI noch startet, hängt von der Installation ab, siehe Teil 4. Für einen kurzen Offline-Snapshot würden wir UEFI nicht umstellen.
Lässt sich das Skript in ein Update-Skript einbauen?
Das Skript fragt nichts ab und endete im Test bei jedem Fehler mit einem Exit-Code ungleich 0. Es muss aber auf einem anderen Rechner laufen als der Server, den es sichert, und es schaltet den Server aus, wenn kein Online-Snapshot möglich ist.
Fazit
Mit der SCP-REST-API wird der Snapshot vor dem Update zu einem Befehl. Mit UEFI geht das nur offline, die Unterbrechung war im Test aber kurz. Wichtig sind drei Dinge: auf die Aufgabe warten statt der ersten 202-Antwort zu trauen, das Skript nie auf dem Server selbst starten und den Token schützen. Eine Sicherung außerhalb des Servers ersetzt der Snapshot nicht.
Weiterführende Anleitungen und Quellen
- netcup VPS-Snapshots: Offline- vs. Online-Snapshot, Export und Wiederherstellung
- Teil 4: SCP-Einstellungen, CPU-Topologie und UEFI
- Teil 5: Das SCP-Protokoll lesen
- Teil 19: Traffic im Blick mit vnstat, ntfy und SCP-API
- Teil 1: Von G12 auf G12.5 umziehen
- netcup: REST-API im SCP
- SCP-REST-API: OpenAPI-Beschreibung


