Dealownia
Menu
sprawdzono – jak sprawdzamy oferty

Kopie zapasowe VPS: restic, rclone i kopia poza serwerem

Co kopiować z VPS (bazy, wolumeny, konfiguracja), restic albo rclone crypt, szyfrowana kopia poza serwerem, test odtwarzania i harmonogram w cron.

Bezpieczeństwo i kopiezaktualizowano 7 min czytania

Redakcja serwisu Dealownia · ceny i fakty ze stron dostawców, jak sprawdzamy

Codzienna, szyfrowana kopia zapasowa VPS-a, która leży u innej firmy niż serwer, sama usuwa stare wersje, raz w miesiącu jest sprawdzana odtworzeniem i wysyła sygnał, gdy się nie wykona. Opisujemy dwa sposoby: restic (kopie przyrostowe z deduplikacją) i prostsze archiwum tar wysyłane przez rclone z szyfrowaniem. W wariancie restic skrypt trzyma 7 kopii dziennych, 4 tygodniowe i 6 miesięcznych; w wariancie z archiwum – archiwa z ostatnich 30 dni. Liczby łatwo zmienić, opisujemy gdzie.

Czego potrzebujesz

  • VPS z Ubuntu 24.04 LTS albo Debianem 12 lub 13 i dostęp przez sudo.
  • Miejsce na kopie u innego dostawcy niż serwer: serwer SFTP (np. wynajęty „storage box”), przestrzeń zgodną z S3 albo dysk w chmurze obsługiwany przez rclone. Drugi VPS u tej samej firmy, w tej samej serwerowni, nie chroni przed awarią tej serwerowni ani blokadą konta.
  • Menedżer haseł na hasło szyfrowania. Bez tego hasła kopii nie odczytasz – ani Ty, ani nikt inny.

Krok 1. Ustal, co kopiować

Kopiuj to, czego nie odtworzysz z repozytorium:

Co Gdzie zwykle leży Jak
Bazy danych kontenery Postgres, MariaDB zrzut (pg_dump, mariadb-dump), nie surowe pliki
Dane aplikacji wolumeny i katalogi ./data w /opt/apps pliki
Konfiguracja /opt/apps/*/compose.yaml, .env, /etc/nginx, /etc/cron.d pliki
Certyfikaty i konto ACME /etc/letsencrypt pliki

Strony statyczne pomijamy – zbudujesz je ponownie z repozytorium. Surowych plików Postgresa i SQLite nie kopiuj w trakcie pracy bazy: zapisane w połowie transakcji dają kopię, której baza nie przyjmie. Bazy SQLite kopiuj poleceniem sqlite3 baza.db ".backup kopia.db", a Postgresa przez pg_dump. Pamiętaj, że w kopii są pliki .env z hasłami – całość traktuj jak sejf z hasłami.

Krok 2. Zrzuty baz przed kopią

bash
sudo mkdir -p /var/backups/db && sudo chmod 700 /var/backups/db
cd /opt/apps/umami
docker compose exec -T db pg_dump -U umami -Fc umami | sudo tee /var/backups/db/umami.dump >/dev/null
sudo ls -lh /var/backups/db/

Nazwę usługi (db), użytkownika i bazy weź ze swojego compose.yaml. Format -Fc jest skompresowany i odtwarza się przez pg_restore. Dla MariaDB: docker compose exec -T db mariadb-dump --single-transaction -u root -p"$HASLO" nazwa_bazy.

Krok 3a. restic: instalacja i repozytorium

bash
sudo apt install -y restic
restic version

Wersje w repozytoriach: Ubuntu 24.04 – 0.16.4, Debian 12 – 0.14.0, Debian 13 – 0.18.0. Na Debianie 12 rozważ oficjalny plik binarny z wydań restic na GitHubie – dokumentacja instalacji wprost poleca go, gdy pakiet dystrybucji jest stary.

Konfigurację trzymaj w pliku dostępnym tylko dla roota:

bash
sudo mkdir -p /etc/restic && sudo chmod 700 /etc/restic
openssl rand -base64 32 | sudo tee /etc/restic/password >/dev/null
sudo chmod 600 /etc/restic/password
sudo tee /etc/restic/env >/dev/null <<'EOF'
RESTIC_REPOSITORY=sftp:kopie:/restic/vps
RESTIC_PASSWORD_FILE=/etc/restic/password
EOF
sudo cat /etc/restic/password

Ostatnia komenda wypisuje hasło – przepisz je teraz do menedżera haseł. sftp:kopie:/restic/vps korzysta z aliasu kopie w /root/.ssh/config (host, użytkownik, klucz), bo kopia będzie działać jako root. Inne formaty adresu według dokumentacji restic: s3:https://serwer/bucket dla usług zgodnych z S3 i rclone:zdalny:sciezka dla wszystkiego, co obsługuje rclone. restic szyfruje dane sam, więc pod nim nie potrzebujesz szyfrowania rclone.

bash
sudo bash -c 'set -a; . /etc/restic/env; set +a; restic init'

Krok 3b. Alternatywa: archiwum i rclone crypt

Jeśli wolisz pojedyncze archiwa (łatwo je pobrać i rozpakować bez restic), pakuj je tar i wysyłaj przez rclone ze zdalnym typu crypt. Według dokumentacji rclone crypt najpierw konfigurujesz zwykły zdalny (np. chmura), potem drugi typu crypt z remote = chmura:kopie-vps. Domyślne filename_encryption = standard szyfruje też nazwy plików, a password2 (sól) dokumentacja zaleca ustawić inną niż hasło główne.

bash
sudo apt install -y rclone
sudo rclone config
sudo rclone lsd kopie-crypt:

Hasło w rclone.conf jest tylko „lekko zaciemnione”, nie zaszyfrowane – plik konfiguracji ustaw na 600 i nie dokładaj go do samej kopii. Wysyłkę i rotację zapisz jako skrypt /usr/local/sbin/kopia-tar.sh (działa jako root, bo czyta /etc/letsencrypt):

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

DIR=/var/backups/archiwa
install -d -m 700 "$DIR"
ARCH="$DIR/vps-$(date +%F).tar.gz"

tar -czf "$ARCH" --exclude='*/db-data' /var/backups/db /opt/apps /etc/nginx /etc/letsencrypt /etc/cron.d
rclone copy "$ARCH" kopie-crypt:dzienne
rm -f "$ARCH"
rclone delete --min-age 30d kopie-crypt:dzienne

Archiwum zawiera klucze prywatne z /etc/letsencrypt i pliki .env z hasłami, dlatego umask 077 i katalog z prawami 700 – nikt poza rootem go nie odczyta. Po udanej wysyłce lokalne archiwum jest usuwane, więc dysk serwera nie zapełnia się kopiami. Dzięki set -e przy błędzie tar albo rclone copy skrypt kończy się wcześniej i nie kasuje starych kopii po drugiej stronie. --min-age 30d zostawia archiwa z ostatnich 30 dni – zmień tę wartość, jeśli chcesz dłużej. Uprawnienia i pierwsze uruchomienie: sudo chmod 700 /usr/local/sbin/kopia-tar.sh && sudo /usr/local/sbin/kopia-tar.sh.

Krok 4. Skrypt kopii (wariant restic)

/usr/local/sbin/kopia.sh:

bash
#!/usr/bin/env bash
set -euo pipefail
umask 077
set -a; . /etc/restic/env; set +a

DUMPS=/var/backups/db
cd /opt/apps/umami
docker compose exec -T db pg_dump -U umami -Fc umami > "$DUMPS/umami.dump"

restic backup --tag nocna --exclude-file=/etc/restic/excludes \
  "$DUMPS" /opt/apps /etc/nginx /etc/letsencrypt /etc/cron.d

restic forget --tag nocna --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
bash
sudo tee /etc/restic/excludes >/dev/null <<'EOF'
/opt/apps/*/db-data
/opt/apps/*/data/*.sqlite-wal
EOF
sudo chmod 700 /usr/local/sbin/kopia.sh
sudo /usr/local/sbin/kopia.sh

Pomyśl też o scenariuszu, w którym ktoś przejmuje serwer. Dane logowania do miejsca kopii leżą wtedy na tym samym serwerze i pozwalają nie tylko dopisywać, ale też kasować kopie. Jeśli miejsce na kopie to umożliwia, nadaj kontu serwera prawo tylko do zapisu (bez usuwania) albo włącz po stronie dostawcy migawki lub wersjonowanie, a usuwanie starych kopii uruchamiaj z innej maszyny. Przy kopii prywatnych projektów często wystarczy, że raz w miesiącu ściągniesz ostatnie archiwum na własny dysk.

Wykluczenia dopasuj do swoich katalogów – chodzi o surowe pliki baz, które zastępują zrzuty. forget zostawia 7 kopii dziennych, 4 tygodniowe i 6 miesięcznych (zmienisz to parametrami --keep-…), a --prune od razu zwalnia miejsce. Od wersji 0.17 restic umie też zapisać wynik komendy bez pliku pośredniego (backup --stdin-from-command -- pg_dump …), ale na Ubuntu 24.04 z pakietu tej opcji nie ma.

Krok 5. Harmonogram i sygnał „kopia się wykonała”

/etc/cron.d/kopia:

tekst
30 3 * * * root /usr/local/sbin/kopia.sh >>/var/log/kopia.log 2>&1; curl -fsS -m 10 --retry 3 -o /dev/null https://hc-ping.com/TWÓJ-UUID/$?

W wariancie z archiwum zamiast kopia.sh wpisz kopia-tar.sh. Adres z $? na końcu to funkcja Healthchecks.io: 0 oznacza sukces, inna liczba – błąd. Jeśli sygnał nie przyjdzie w ogóle (serwer leży, cron nie działa), usługa też zaalarmuje. Więcej o takim „martwym przełączniku” w poradniku o monitoringu serwera. Ostatnia linia pliku w /etc/cron.d musi kończyć się znakiem nowej linii, a końce linii muszą być w formacie LF.

Krok 6. Test odtworzenia

Kopia, której nikt nie odtworzył, jest tylko nadzieją. Raz w miesiącu:

bash
sudo bash -c 'set -a; . /etc/restic/env; set +a
restic snapshots --tag nocna
restic check --read-data-subset=5%
restic restore latest --target /tmp/test-odtworzenia --include /etc/nginx
restic dump latest /var/backups/db/umami.dump > /tmp/umami.dump'
pg_restore --list /tmp/umami.dump | head
sudo rm -rf /tmp/test-odtworzenia /tmp/umami.dump

check --read-data-subset=5% czyta losowe 5% danych z repozytorium i sprawdza, czy dają się odszyfrować. pg_restore --list (pakiet postgresql-client) pokaże spis tabel – jeśli plik jest uszkodzony, skończy się błędem. Pełny test raz na kwartał: nowy, tymczasowy VPS, odtworzenie konfiguracji i baz, uruchomienie usług. Wtedy dowiesz się, ile naprawdę trwa powrót do pracy.

Najczęstsze problemy

  • Cron przestał uruchamiać wszystkie zadania. Plik w /etc/cron.d zapisany na Windowsie ma końce linii CRLF. Jeden taki plik potrafi zatrzymać wszystkie zadania. Przed wgraniem usuń znaki \r (sed -i 's/\r$//' plik) i sprawdź journalctl -u cron.
  • Zrzut bazy ma 0 bajtów, a kopia „się udała”. Bez set -o pipefail błąd pg_dump w potoku ginie. Skrypt z kroku 4 przerwie się na błędzie, a Healthchecks dostanie kod różny od zera.
  • Fatal: wrong password or no key found. Inne hasło niż przy restic init albo zmienna z innego pliku. Dlatego hasło ma być w menedżerze haseł, a nie tylko na serwerze.
  • Repozytorium rośnie mimo forget. forget bez --prune usuwa tylko wpisy migawek, dane zostają. Dodaj --prune albo uruchamiaj restic prune osobno.
  • Wysyłka przez rclone do dysku w chmurze kończy się błędem limitu. Wspólny identyfikator aplikacji rclone bywa ograniczany przez dostawcę. Załóż własny klucz API zgodnie z instrukcją danego zdalnego w dokumentacji rclone.

Co dalej

Dołóż monitoring z alertami, żeby dowiedzieć się o zapełnionym dysku, zanim kopia przestanie się mieścić. Jeśli kopiujesz narzędzia z jednego pliku compose, dopisz do skryptu zrzut każdej bazy. Serwer albo przestrzeń na kopie u innego dostawcy znajdziesz w dziale promocje: VPS.

Powiązane poradniki