Webhuset
Tilbake til Nyheter
5 min lesetid

Slik bygger du en backup av nettsiden din som faktisk lar seg gjenopprette

rsync til en mappe er ikke en backup. Slik setter du opp versjonert object storage på din egen MinIO – med varsling når den feiler, og en restore-test du faktisk har kjørt.

Slik bygger du en backup av nettsiden din som faktisk lar seg gjenopprette

rsync -a /var/www/ /mnt/backup/ er ikke en backup. Det er en kopi. Forskjellen merker du den dagen du sletter feil mappe, en oppdatering korrumperer databasen, eller et innbrudd krypterer alt – og rsync lydig replikerer skaden over på «backupen» din noen timer senere. En kopi som ligger på samme maskin, eller som overskriver seg selv hver natt, dør sammen med originalen.

En ekte backup har tre egenskaper kopien mangler: den ligger et annet sted, den beholder flere generasjoner du kan gå tilbake til, og den sier fra når den feiler. Selvhostet object storage med MinIO gir deg alle tre, på maskinvare du selv eier. Her er hele oppsettet.

Slik henger det sammen: web-serveren sender kryptert, versjonert backup hver natt til MinIO object storage på en egen maskin på en annen lokasjon, med object lock

Vil du ikke bygge dette for hånd, har du to snarveier: du kan la en coding agent sette det opp for deg – ferdig prompt ligger nederst i artikkelen – eller bruke Webhusets egen backup-tjeneste, som kjører automatisk og er laget for å være enkel å leve med. Resten av artikkelen er for deg som vil eie hele stacken selv.

1. Kjør MinIO på din egen server

MinIO er en S3-kompatibel object storage du kjører selv. Enkleste vei er Docker:

docker run -d --name minio \
  -p 9000:9000 -p 9001:9001 \
  -e MINIO_ROOT_USER=admin \
  -e MINIO_ROOT_PASSWORD='<langt-tilfeldig-passord>' \
  -v /srv/minio:/data \
  quay.io/minio/minio server /data --console-address ":9001"

Port 9000 er API-et (det rclone snakker med), 9001 er web-konsollet. Sett en TLS-terminerende reverse proxy foran, eller la MinIO håndtere sertifikatet selv – backup-trafikk skal ikke gå i klartekst.

Én regel er ikke til forhandling: MinIO må stå på en annen maskin enn nettsiden. En backup-server i samme docker compose som det den skal sikre, deler skjebne med originalen – da er du like langt som med rsync til en lokal mappe. Egen VPS, helst en annen fysisk lokasjon. Det er hele poenget med at backupen «ligger et annet sted».

Opprett en bucket og en bruker med mc, MinIOs egen klient:

mc alias set local http://localhost:9000 admin '<passordet-ditt>'
mc mb local/webhuset-backup
mc admin user add local backup-writer '<egen-hemmelig-nokkel>'
mc admin policy attach local readwrite --user backup-writer

2. Slå på versjonering og object lock

Versjonering er det som skiller backup fra kopi, og MinIO gjør det server-side. Aktiver det på bucketen:

mc version enable local/webhuset-backup

Nå beholder MinIO gamle versjoner når en fil overskrives eller slettes. Vil du beskytte deg mot at et kompromittert script – eller ransomware – sletter selve historikken, opprett bucketen med object lock fra start (mc mb --with-lock) og sett en retention-regel. Da blir versjonene immutable i perioden du velger: ingen kan slette dem, heller ikke du, før tiden er ute.

Scriptet under legger i tillegg hver kjøring under en datostemplet path og rydder de eldste selv. Server-side versjonering og generasjoner i scriptet utfyller hverandre: det ene verner mot overskriving og sletting, det andre gir deg ryddige, navngitte gjenopprettingspunkter.

3. Backup-scriptet

Først rclone på web-serveren, pekt mot MinIO-endpointet ditt:

rclone config create backup s3 \
  provider=Minio \
  access_key_id=backup-writer \
  secret_access_key=<egen-hemmelig-nokkel> \
  endpoint=https://minio.dinserver.no:9000

Selve scriptet dumper databasen, pakker webroot, laster opp under en datostemplet path, beholder et antall generasjoner og varsler ved feil. Hver feil-følsom kommando går gjennom fail(), som pinger en healthcheck-tjeneste (healthchecks.io, eller din egen ntfy) – får du ingen ping innen vinduet, vet du at backupen stoppet, i stedet for å oppdage det under en krise.

#!/usr/bin/env bash
set -euo pipefail

SITE="minnettside.no"
WEBROOT="/var/www/${SITE}"
DB_NAME="wordpress"
REMOTE="backup"
BUCKET="webhuset-backup"
KEEP=7
HC_URL="https://hc-ping.com/DIN-UUID"

STAMP="$(date +%F_%H%M)"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT

fail() {
  curl -fsS --max-time 10 "${HC_URL}/fail" --data-raw "$*" || true
  echo "BACKUP FAILED: $*" >&2
  exit 1
}

# 1. database – konsistent dump uten å låse tabellene
mysqldump --single-transaction --quick "$DB_NAME" \
  | gzip > "$WORK/db.sql.gz" || fail "mysqldump"

# 2. filer
tar -C "$(dirname "$WEBROOT")" -czf "$WORK/files.tar.gz" \
  "$(basename "$WEBROOT")" || fail "tar"

# 3. last opp under datostemplet path
rclone copy "$WORK" "${REMOTE}:${BUCKET}/${SITE}/${STAMP}/" \
  --s3-no-check-bucket || fail "upload"

# 4. rydd: behold de KEEP nyeste generasjonene
mapfile -t gens < <(rclone lsf "${REMOTE}:${BUCKET}/${SITE}/" --dirs-only | sort)
n=${#gens[@]}
if (( n > KEEP )); then
  for old in "${gens[@]:0:n-KEEP}"; do
    rclone purge "${REMOTE}:${BUCKET}/${SITE}/${old}" || fail "prune ${old}"
  done
fi

# 5. meld fra at alt gikk bra
curl -fsS --max-time 10 "$HC_URL" || true

--single-transaction gir deg et konsistent øyeblikksbilde av en InnoDB-database uten å låse den mens nettsiden kjører. set -euo pipefail sørger for at scriptet stopper på første feil i stedet for å laste opp en halv backup og rapportere suksess. Legg den i cron:

17 3 * * * /usr/local/bin/backup.sh

Vil du beskytte deg mot at noen leser backupene dine, krypter $WORK med age før opplasting (age -r <din-public-key> -o bundle.age) og legg nøkkelen et helt annet sted enn serveren.

4. Test at den faktisk lar seg gjenopprette

Dette er steget alle hopper over, og det eneste som beviser at du har en backup i det hele tatt. En backup du aldri har gjenopprettet er en hypotese, ikke en forsikring.

Kjør en ekte restore-øvelse mot et blankt mål – ikke produksjon:

LATEST="$(rclone lsf "${REMOTE}:${BUCKET}/minnettside.no/" --dirs-only | sort | tail -1)"
rclone copy "${REMOTE}:${BUCKET}/minnettside.no/${LATEST}" /tmp/restore/

# database inn i en kastbar base
mysql -e "DROP DATABASE IF EXISTS restore_test; CREATE DATABASE restore_test"
gunzip -c /tmp/restore/db.sql.gz | mysql restore_test
mysql restore_test -e "SELECT COUNT(*) FROM wp_posts;"

# filene
mkdir -p /tmp/restore/files && tar -xzf /tmp/restore/files.tar.gz -C /tmp/restore/files
test -f /tmp/restore/files/${SITE}/wp-config.php && echo "wp-config OK"

Får du et fornuftig radtall og finner filene du forventer, vet du at backupen er hel – ikke bare at den finnes. Sett en påminnelse om å gjøre dette månedlig, eller bygg en restore_test.sh som kjører automatisk og pinger sin egen healthcheck. Da har du flyttet «virker backupen?» fra noe du håper, til noe du måler.

Tre filer, ett script og én cron-linje senere har du backup som ligger trygt et annet sted, lar deg gå tilbake i tid, roper når den feiler – og som du faktisk vet fungerer.

Vil du heller la en agent gjøre jobben?

Hele dette oppsettet er en grei oppgave for en coding agent på serveren din. Kopier prompten under, fyll inn detaljene om miljøet ditt, og kjør den i agenten du bruker:

Du er en erfaren Linux-systemadministrator. Hjelp meg å sette opp automatisk,
versjonert backup av nettsiden min til selvhostet MinIO object storage.

Miljø (fyll inn): VPS-ens OS og ressurser, om backup skal kjøre på en egen
maskin (det bør den), og om nettsiden er WordPress/MySQL eller noe annet.

Gjør dette ett steg om gangen, og forklar hver kommando før du kjører den:
1. Sett opp MinIO i Docker på backup-maskinen (en annen maskin enn nettsiden),
   med sterke root-credentials, et persistent volum og TLS på endpointet.
2. Opprett en bucket for backup, og slå på versjonering og object lock slik at
   gamle versjoner ikke kan slettes før en retention-periode er ute.
3. Installer og konfigurer rclone på web-serveren mot MinIO-endpointet, med en
   egen bruker som kun har readwrite på backup-bucketen.
4. Skriv et backup-script som: dumper databasen konsistent (--single-transaction),
   pakker webroot, laster opp under en datostemplet path, beholder de 7 nyeste
   generasjonene og pinger en healthcheck ved både suksess og feil. set -euo pipefail.
5. Legg scriptet i cron én gang i døgnet.
6. Skriv et eget restore_test-script som henter siste backup, gjenoppretter til
   en kastbar database, sjekker radtall og at nøkkelfiler finnes — og kjør det
   for å bevise at backupen faktisk lar seg gjenopprette.

Ikke anta credentials, porter eller stier — bekreft dem med meg underveis.

Og vil du slippe å vedlikeholde noe av dette selv, kan du bruke Webhusets backup-tjeneste: den kjører automatisk i bakgrunnen, så du slipper både script, cron og restore-øvelser.