Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Cloud / Hosting 05.11.2026 · 13 min Lesezeit

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

Symbolbild: Rack-Server vor orangefarbener Fläche, Titel Snapshots per API vor jedem Update

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.

netcup SCP, Reiter Medien und Festplatten: Hinweis auf den Snapshot an der Festplatte vda und Warnung, dass die Speicheroptimierung Snapshots löscht
SCP, Medien und Festplatten: Hinweis auf den Snapshot an der Festplatte vda (1) und Warnung, dass die Speicheroptimierung Snapshots löscht (2).

Voraussetzungen

  • Ein netcup-Server im SCP
  • Ein zweiter Rechner mit Linux oder macOS, curl und jq, 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:

AufrufZweck
GET /servers/{id}/snapshotsalle Snapshots eines Servers
POST /servers/{id}/snapshots:dryrunprüfen, ob ein Snapshot möglich ist
POST /servers/{id}/snapshotsSnapshot anlegen
POST /servers/{id}/snapshots/{name}/revertSnapshot zurückspielen
POST /servers/{id}/snapshots/{name}/exportSnapshot 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 pruefe bricht 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 trap ihn wieder. Die Zeile trap 'exit 1' INT TERM HUP sorgt dafür, dass das auch bei Strg+C unter dash klappt, 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.

netcup SCP, Reiter Medien und Snapshots: Offline-Snapshot in der Liste und Hinweis, dass Online-Snapshots mit UEFI-Boot nicht verfügbar sind
SCP, Medien und Snapshots: der per API angelegte Offline-Snapshot (1) und der Hinweis, dass Online-Snapshots mit UEFI-Boot nicht verfügbar sind (2).

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

MeldungUrsache und Lösung
server.snapshot.create.error.online.uefiUEFI-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 500Die 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

netcupnetcup SpezialSnapshotSCP REST APIShell-SkriptBackupVPS