Kopie zapasowe serwera trzymaj według zasady 3-2-1: trzy kopie, na dwóch różnych nośnikach, jedna poza serwerem. Bazę danych kopiuj narzędziem bazy (np.pg_dump,mysqldump,sqlite3 .backup), a nie przez zwykłe kopiowanie plików działającej bazy. Najważniejszy jest test odtwarzania: kopia, której nie odtworzyłeś, może okazać się bezużyteczna.
Dysk się zepsuje, ktoś skasuje plik albo źle skonfigurujesz serwer. Kopia zapasowa decyduje, czy to drobna niedogodność, czy koniec projektu. W artykule o serwerze VPS pisałem, że przy VPS niezarządzanym za kopie odpowiadasz sam. Tu pokazuję, jak to zrobić dobrze, ze szczególnym naciskiem na bazę danych. Polecenia dla PostgreSQL, SQLite i narzędzia restic sprawdziłem lokalnie na macOS (na danych testowych), a MySQL opisałem z dokumentacji, bo nie uruchamiałem go. Na prawdziwym serwerze z Linuxem nie testowałem niczego, więc czasy i rozmiary nie są reprezentatywne.
Co oznacza zasada 3-2-1?
Zasada 3-2-1 mówi, że trzymasz trzy kopie danych (jedną podstawową i dwie zapasowe), na co najmniej dwóch różnych rodzajach nośników, z czego jedna kopia leży poza miejscem, w którym stoi serwer. Tak opisuje ją brytyjskie DfE Cyber Security Hub (strona sprawdzana w lipcu 2026), które powołuje się na rekomendację NCSC, że nawet przy rozwiązaniach chmurowych warto stosować 3-2-1. Dla VPS oznacza to na przykład:
| Kopia | Gdzie |
|---|---|
| 1 (podstawowa) | dane na serwerze |
| 2 | kopia na tym samym serwerze lub w jego migawce, do szybkiego odtwarzania |
| 3 | kopia poza serwerem: inny dostawca, chmura albo Twój komputer |
Dwa dopowiedzenia z dokumentów rządowych. Według przewodnika CISA #StopRansomware (wydanie z września 2023) warto trzymać kopie offline i szyfrowane, bo wiele odmian ransomware szuka dostępnych kopii, żeby je usunąć lub zaszyfrować. A według DfE jedna kopia powinna być odłączona od sieci, żeby przynajmniej ona pozostała nietknięta. Oba źródła zalecają też regularne testy odtwarzania.
Co kopiować na serwerze?
Nie kopiuj wszystkiego na ślepo. Zdecyduj, co jest trudne do odtworzenia:
| Co | Czy kopiować | Dlaczego |
|---|---|---|
| Baza danych | tak, zawsze | to są Twoje dane, których nie odtworzysz z kodu |
| Pliki wgrane przez użytkowników | tak | też nie ma ich nigdzie indziej |
Konfiguracja (np. /etc, pliki .env) | tak | odtworzenie serwera trwa o wiele krócej |
| Klucze i certyfikaty | tak, w bezpiecznym miejscu | kopię z kluczami szyfruj (restic robi to domyślnie), bo to wrażliwe dane |
| Kod aplikacji | zwykle nie, jeśli jest w Gicie | wystarczy repozytorium, ale sprawdź, czy ma kopię |
Zależności i cache (node_modules, .cache) | nie | odtworzysz je jednym poleceniem |
Dla każdej z tych rzeczy zadaj sobie pytanie: „ile godzin zajmie mi odtworzenie tego od zera?”. Jeśli dużo, kopiuj.
Jak zrobić kopię bazy danych?
To najważniejsza część. Nie kopiuj plików działającej bazy zwykłym cp, bo mogą być w trakcie zapisu, a kopia nie będzie spójna. Zamiast tego użyj narzędzia samej bazy, które robi kopię logiczną (zrzut, dump) albo spójną migawkę. Poniżej trzy najczęstsze bazy.
Jak zrobić kopię PostgreSQL (pg_dump)?
Według dokumentacji PostgreSQL pg_dump tworzy spójną kopię bazy: odpowiada stanowi z momentu startu programu, a w trakcie nie blokuje pozostałych operacji (wyjątkiem są operacje wymagające blokady ekskluzywnej, np. większość ALTER TABLE). Dwa najważniejsze formaty:
# format tekstowy (SQL), skompresowany
pg_dump sklep | gzip > sklep.sql.gz
# format "custom": skompresowany, pozwala odtwarzać wybrane tabele
pg_dump -Fc sklep > sklep.dump
Odtwarzanie jest osobnym krokiem i baza docelowa musi istnieć, bo kopia jej nie tworzy:
# odtwarzanie formatu custom do NOWEJ bazy
createdb sklep_odtworzona
pg_restore -d sklep_odtworzona sklep.dump
# odtwarzanie z pliku tekstowego, z zatrzymaniem przy pierwszym błędzie
createdb sklep_z_sql
gunzip -c sklep.sql.gz | psql -X --set ON_ERROR_STOP=on -d sklep_z_sql
Sprawdziłem to na PostgreSQL 16 na własnej, tymczasowej instancji: tabela z 1000 wierszy została skopiowana do pliku -Fc (ok. 6,6 KB) i do tekstowego z gzip (ok. 4,9 KB), a po odtworzeniu do nowej bazy obie kopie dały 1000 wierszy. Obcięty plik kopii dał wyraźny błąd „could not read from input file: end of file”, co pokazuje, że uszkodzoną kopię widać dopiero przy odtwarzaniu.
Pułapka z rolami. Według dokumentacji pg_dump kopiuje tylko jedną bazę i nie zawiera informacji o rolach ani tablespace'ach. W moim teście plik kopii miał 0 wierszy CREATE ROLE, ale zawierał GRANT SELECT … TO aplikacja i OWNER TO, więc przy odtwarzaniu na świeżym serwerze role muszą już istnieć. Dlatego obok kopii bazy zrób kopię ról: pg_dumpall --globals-only (u mnie zawierała CREATE ROLE aplikacja;). Przy dużych bazach dokumentacja zaleca format katalogowy i równoległość (pg_dump -j, pg_restore -j) oraz uruchomienie ANALYZE po odtworzeniu.
Jak zrobić kopię MySQL (mysqldump)?
Według dokumentacji MySQL mysqldump wykonuje kopie logiczne, czyli zestaw instrukcji SQL, które odtwarzają obiekty i dane. Dokumentacja zaznacza, że nie jest to narzędzie do szybkich ani skalowalnych kopii dużych ilości danych, a dla dużych baz lepsza jest kopia fizyczna. Typowe polecenie wygląda tak:
mysqldump --single-transaction --routines --triggers --databases sklep > sklep.sql
Co znaczą opcje według dokumentacji: --single-transaction wysyła BEGIN przed zrzutem danych (bez niej potrzebne jest uprawnienie LOCK TABLES), --routines dodaje procedury i funkcje, --triggers dodaje wyzwalacze (domyślnie włączone), a --databases sprawia, że w pliku są instrukcje CREATE DATABASE. Przywracasz poleceniem mysql < sklep.sql i musisz mieć uprawnienia do wykonania instrukcji z pliku. Tego narzędzia nie uruchamiałem, więc nie podaję wyników, a spójność kopii InnoDB przy --single-transaction warto sprawdzić w pełnej dokumentacji.
Jak zrobić kopię SQLite?
SQLite to jeden plik, więc kuszące jest zwykłe cp. Dokumentacja SQLite opisuje, że takie kopiowanie wymaga blokady bazy na czas operacji, a awaria w trakcie może uszkodzić kopię. Bezpieczniej użyć mechanizmu samej bazy. W powłoce sqlite3 służy do tego polecenie .backup:
sqlite3 app.db ".backup 'app_backup.db'"
sqlite3 app_backup.db "pragma integrity_check"
Sprawdziłem to na SQLite 3.51: kopia miała 500 wierszy tak jak oryginał, a integrity_check zwrócił ok. Dokumentacja wspomina też o VACUUM INTO, które robi kopię działającej bazy do osobnego pliku (u mnie również 500 wierszy). Samego polecenia .backup dokumentacja SQLite na stronie o kopiach nie opisuje, więc opieram się na własnym teście.
Jak zrobić spójną kopię, gdy baza jest duża?
Zrzut logiczny przy dużej bazie trwa długo, a odtwarzanie jeszcze dłużej (dokumentacja PostgreSQL mówi o „wielu godzinach”). Wtedy rozważ kopie fizyczne (dla PostgreSQL i MySQL istnieją osobne narzędzia, których tu nie opisuję i nie testowałem) oraz równoległość tam, gdzie dokumentacja ją przewiduje. Zawsze zmierz, ile trwa pełne odtworzenie, bo to ono decyduje, jak długo będzie przestój po awarii.
Jak wysłać kopię poza serwer?
Trzecia kopia ma leżeć poza serwerem. Dwa proste sposoby:
- Archiwum wysłane przez SFTP lub
rsync. Spakuj, wyślij i sprawdź sumę kontrolną. W moim teście skrót SHA-256 archiwum przed i po skopiowaniu był identyczny. Wysyłanie plików opisuje artykuł o FTP i SFTP, a łączenie z serwerem o SSH. - Narzędzie do kopii z szyfrowaniem i wersjami, np. restic (opisane niżej).
Przy rsync pamiętaj o opcji --dry-run, która pokazuje, co zostanie skopiowane, zanim cokolwiek zmienisz (u mnie pokazała tylko nowy plik).
Jak działa restic i dlaczego warto go znać?
Restic to program do kopii, który szyfruje repozytorium hasłem, robi migawki (snapshots) i deduplikuje dane: tego samego pliku nie zapisuje dwa razy. Dokumentacja definiuje snapshot jako zawartość katalogu w określonym momencie. Przetestowałem na lokalnym repozytorium:
export RESTIC_REPOSITORY=/sciezka/do/repozytorium
export RESTIC_PASSWORD="haslo-do-repozytorium"
restic init # utwórz zaszyfrowane repozytorium
restic backup dane # pierwsza kopia
restic backup dane # druga, bez zmian: nic nie dopisuje
restic check # sprawdź integralność
restic restore latest --target odtworzone
restic forget --keep-last 1 --dry-run # co zostałoby usunięte
Wyniki z mojego testu:
- Pierwsza kopia dodała 4 nowe pliki. Druga, bez zmian, dodała 0 B, czyli deduplikacja zadziałała. Po zmianie jednego pliku trzecia kopia dodała tylko około 1,8 KiB.
restic checkzakończył się komunikatem „no errors were found”.restoredo nowego katalogu dał identyczną zawartość (diff -rbez różnic).forget --keep-last 1 --dry-runpokazał „keep 1 snapshots” i „remove 2 snapshots”, czyli politykę retencji można sprawdzić bez usuwania.- Przy złym haśle: „wrong password or no key found”. Zapisz hasło do repozytorium poza serwerem, np. w menedżerze haseł, bo bez niego nie odtworzysz kopii.
Testowałem tylko lokalne repozytorium. Restic obsługuje też zdalne repozytoria (m.in. przez SFTP i S3), ale tego nie sprawdzałem.
Jak zautomatyzować kopie?
Kopia robiona ręcznie bywa pomijana, więc ustaw harmonogram. Na Linuxie służy do tego cron. Poniżej przykład skryptu, który robi zrzut bazy PostgreSQL i usuwa stare pliki. To przykład z wiedzy ogólnej, którego nie uruchamiałem na serwerze (uruchamiałem tylko pojedyncze polecenia pg_dump i pg_restore):
#!/usr/bin/env bash
set -euo pipefail
DIR=/var/backups/baza
mkdir -p "$DIR"
# zrzut bazy z datą w nazwie
pg_dump -Fc sklep > "$DIR/sklep-$(date +%F).dump"
# usuń zrzuty starsze niż 14 dni
find "$DIR" -name 'sklep-*.dump' -mtime +14 -delete
Do uruchamiania co noc możesz użyć wpisu w crontab -e, np. 0 3 * * * /usr/local/bin/backup-baza.sh (codziennie o 3:00). Pamiętaj, że sam zrzut leży jeszcze na serwerze, więc drugim krokiem harmonogramu musi być wysłanie go poza serwer. Uprawnienia plików i skryptów (chmod) opisuje artykuł o chmod w Linuksie. Hasło do bazy do skryptu przekazuj przez plik konfiguracyjny z ograniczonymi uprawnieniami, a nie w treści polecenia.
Jak testować odtwarzanie kopii?
Uwaga mentora: najpierw wybierz, kiedy robisz test odtwarzania, a dopiero potem narzędzie do kopii. Kopia bez zapisanego terminu testu zwykle nigdy nie zostanie sprawdzona.
To najważniejszy krok i najczęściej pomijany. Dokumenty CISA i DfE zalecają regularne testy, choć CISA nie podaje, jak często. Test polega na tym, że naprawdę odtwarzasz kopię w osobnym miejscu i sprawdzasz, czy dane są kompletne:
- Utwórz nową, pustą bazę lub środowisko (nie nadpisuj produkcji).
- Odtwórz kopię (
pg_restore,psql,mysqllubrestic restore). - Sprawdź liczbę rekordów i kilka znanych wartości. U mnie zwykłe
select count(*)pokazało 1000 wierszy w oryginale i w każdej odtworzonej bazie. - Zmierz czas odtwarzania, bo od niego zależy, jak długo potrwa przestój.
- Zapisz wynik i termin kolejnego testu.
Pamiętaj też o rolach i uprawnieniach bazy, bo bez nich odtworzona baza może się nie uruchomić z aplikacją.
Jakie błędy popełniają początkujący przy kopiach zapasowych?
| Błąd | Skutek | Jak uniknąć |
|---|---|---|
| Kopia leży tylko na tym samym serwerze | awaria serwera lub atak niszczy i dane, i kopię | trzymaj kopię poza serwerem (3-2-1) |
| Nigdy nie testowano odtwarzania | kopia okazuje się pusta lub uszkodzona dopiero w awarii | harmonogram testów i zapis wyników |
Kopiowanie plików działającej bazy zwykłym cp | niespójna kopia | użyj pg_dump, mysqldump lub sqlite3 .backup |
| Brak ról i uprawnień bazy (PostgreSQL) | odtworzona baza zgłasza błędy uprawnień | dodaj pg_dumpall --globals-only |
| Zgubione hasło do repozytorium | zaszyfrowanej kopii nie odtworzysz | zapisz hasło poza serwerem |
| Kopie bez retencji | dysk się zapełnia i kopie przestają się robić | polityka usuwania starych kopii |
| Kopia dostępna z serwera z prawem zapisu | atak lub błąd może ją skasować | konto z minimalnymi uprawnieniami, kopia offline lub niezmienna |
| Hasło bazy w skrypcie widocznym dla innych | wyciek hasła | plik z uprawnieniami 600 |
Jak wygląda minimalny, dobry plan kopii dla VPS?
- Baza: codzienny zrzut narzędziem bazy do pliku z datą.
- Pliki i konfiguracja: kopia katalogów, których nie odtworzysz z kodu.
- Poza serwer: wysłanie kopii na drugi serwer, do chmury albo na własny komputer (SFTP,
rsynclub restic). - Retencja: np. 14 dziennych kopii i kilka starszych, a kopię offline raz w miesiącu.
- Test odtwarzania: co kwartał lub po każdej większej zmianie, z zapisanym wynikiem.
- Zabezpieczenie: szyfrowanie, osobne konto do kopii i hasło do repozytorium poza serwerem.
Przykładowy harmonogram, który możesz dopasować do własnego projektu. To moja propozycja, a nie standard:
| Co | Jak często | Gdzie leży kopia | Kto lub co sprawdza |
|---|---|---|---|
| Zrzut bazy danych | codziennie, np. w nocy | serwer i poza serwerem | alert, gdy zrzut się nie utworzył |
| Pliki wgrane przez użytkowników i konfiguracja | codziennie lub tygodniowo | poza serwerem (np. restic lub rsync) | restic check lub porównanie sum kontrolnych |
| Kopia offline | raz w miesiącu | nośnik lub konto odłączone od sieci | zapis daty w notatkach |
| Test odtwarzania | co kwartał i po większej zmianie | osobna baza lub środowisko testowe | zapisany wynik: liczba rekordów i czas |
| Przegląd retencji i miejsca na dysku | raz w miesiącu | serwer i miejsce docelowe | sprawdzenie wolnego miejsca |
Pełny obraz bezpieczeństwa serwera tworzą: zapora (artykuł o firewallu na serwerze Linux), bezpieczne logowanie (SSH) i kopie, o których jest ten artykuł. Wdrożenie aplikacji na prawdziwym serwerze ćwiczysz w module wdrożenie produkcyjne w chmurze w kursie AI Engineer.
Podsumowanie
- Zasada 3-2-1: trzy kopie, dwa rodzaje nośników, jedna poza serwerem. Dokumenty CISA i DfE zalecają też kopię offline i szyfrowanie.
- Bazę kopiuj narzędziem bazy:
pg_dump(PostgreSQL),mysqldump(MySQL),sqlite3 .backup(SQLite), a nie zwykłym kopiowaniem plików działającej bazy. - PostgreSQL:
pg_dumpnie zawiera ról, więc dodajpg_dumpall --globals-only. Docelowa baza musi istnieć przed odtwarzaniem. - Restic daje szyfrowanie, migawki i deduplikację, ale bez hasła nie odtworzysz kopii.
- Najważniejszy jest test odtwarzania: odtwórz kopię w osobnym miejscu i sprawdź dane. Obcięta kopia daje błąd dopiero przy odtwarzaniu.
- Polecenia dla PostgreSQL, SQLite i restic sprawdziłem lokalnie na danych testowych, MySQL opisałem z dokumentacji, a na prawdziwym serwerze nie testowałem niczego.


