Codzienny backup baz MySQL na Debianie, Ubuntu i Linuksie
Mały skrypt plus zadanie cron wystarczą, żeby każdej nocy tworzyć skompresowane kopie wszystkich baz MySQL i MariaDB. Razem z miejscami, w których Debian i Ubuntu się różnią, a backup po cichu przestaje działać.
Na twoim serwerze VPS, serwerze root albo serwerze dedykowanym działa Debian, Ubuntu lub inna dystrybucja Linuksa i chcesz codziennie i automatycznie tworzyć kopie zapasowe wszystkich baz MySQL oraz MariaDB? Dobrze trafiłeś. W tym poradniku skonfigurujesz skrypt backupu, który każdej nocy eksportuje wszystkie bazy do skompresowanego pliku SQL i sam sprząta stare kopie.
Procedura jest taka sama w każdej dystrybucji: działa na Debianie i Ubuntu dokładnie tak samo jak na AlmaLinux, Rocky Linux czy RHEL. Różnice nie tkwią w samym skrypcie, tylko w tym, jaki serwer bazodanowy u ciebie działa i jak się do niego logować. Poświęcamy temu osobny rozdział o różnicach między Debianem a Ubuntu w dalszej części tekstu.
Wymagania
Potrzebujesz dostępu SSH z uprawnieniami roota oraz zainstalowanego serwera MySQL lub MariaDB. Zalecamy aktualne wersje dystrybucji: Debian 13 „Trixie” i Debian 12 „Bookworm”, Ubuntu 24.04 LTS „Noble Numbat” i Ubuntu 22.04 LTS „Jammy Jellyfish” oraz AlmaLinux i Rocky Linux w wersjach 9 i 10. Starszych systemów nie powinieneś już używać produkcyjnie: Debian 10 zakończył wsparcie w czerwcu 2024, Ubuntu 20.04 LTS w maju 2025, a CentOS 7 również nie dostaje już regularnych aktualizacji bezpieczeństwa.
Najpierw zaktualizuj system i doinstaluj edytor tekstu Nano, jeśli jeszcze go nie ma.
Dla Debiana i Ubuntu:
apt update && apt upgrade -y
apt install nano -y
Dla AlmaLinux, Rocky Linux i RHEL:
dnf update -y
dnf install nano -y
W aktualnych systemach z rodziny RHEL dnf jest następcą yum. Stare polecenie zwykle nadal działa tam jako dowiązanie, ale używać powinieneś dnf.
Zaplanuj też odpowiednio dużo miejsca na dysku. Tydzień skompresowanych kopii potrafi zająć kilka gigabajtów, zależnie od wielkości baz. Wolne miejsce sprawdzisz poleceniem df -h.
Bezpieczne przechowywanie danych dostępowych
Hasło nie powinno trafiać wprost do wywołania mysqldump, bo wiersze poleceń są przez ps widoczne dla wszystkich użytkowników systemu. Zamiast tego załóż plik z danymi dostępowymi, który może czytać wyłącznie root:
nano /root/.my.cnf
Zawartość pliku:
[client]
user=root
password=TWOJE_HASLO_BAZY_DANYCH
Następnie ustaw uprawnienia tak, żeby dostęp miał wyłącznie root:
chmod 600 /root/.my.cnf
Kiedy hasła roota w ogóle nie ma
Na Debianie i Ubuntu bazodanowe konto root z reguły nie jest chronione hasłem, tylko tożsamością użytkownika systemowego. MariaDB nazywa tę metodę unix_socket, MySQL nazywa ją auth_socket. Hasło po prostu nie istnieje, więc wpis w pliku z danymi dostępowymi nic nie daje. Czy tak jest u ciebie, pokaże to zapytanie:
mysql -e "SELECT user, host, plugin FROM mysql.user WHERE user='root';"
Jeśli widzisz tam unix_socket albo auth_socket, masz dwie drogi: uruchamiać skrypt jako systemowy użytkownik root i całkiem zrezygnować z pliku dostępowego, albo założyć osobnego użytkownika do backupów. Druga opcja jest lepsza, bo obywa się bez pełnych uprawnień konta root:
CREATE USER 'kh_backup'@'localhost' IDENTIFIED BY 'TWOJE_HASLO_BACKUPU';
GRANT SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES, RELOAD, PROCESS ON *.* TO 'kh_backup'@'localhost';
FLUSH PRIVILEGES;
W pliku z danymi dostępowymi wpisujesz wtedy user=kh_backup. Uprawnienia są celowo skromne: odczyt, widoki, eventy, triggery i awaryjnie blokowanie tabel, które nie korzystają z InnoDB. RELOAD oraz PROCESS da się nadać tylko globalnie, stąd ON *.*. PROCESS jest MySQL 8 potrzebne po to, żeby odczytać informacje o tablespace'ach. Jeśli nie chcesz go nadawać, dopisz w skrypcie --no-tablespaces. A jeśli w ogóle nie możesz się już dostać do bazy, pomoże artykuł Reset hasła roota w MySQL i MariaDB.
Tworzenie skryptu backupu
Teraz tworzymy skrypt w Bashu, który zajmie się eksportem i usuwaniem starych kopii. W naszym przykładzie nazywa się mysql_export_all.sh i leży w katalogu /opt/mysqlbackups:
mkdir -p /opt/mysqlbackups
nano /opt/mysqlbackups/mysql_export_all.sh
Do tego skryptu wpisujesz następującą treść:
#!/bin/bash
set -euo pipefail
BACKUP_DIR="/opt/mysqlbackups"
KEEP_DAYS=7
DATE=$(date +%Y-%m-%d-%H-%M)
mkdir -p "$BACKUP_DIR"
mysqldump --defaults-extra-file=/root/.my.cnf --all-databases --single-transaction --routines --events | gzip > "$BACKUP_DIR/alldbs_$DATE.sql.gz"
find "$BACKUP_DIR" -type f -name "alldbs_*.sql.gz" -mtime +$KEEP_DAYS -delete
Najważniejsze opcje w skrócie:
--defaults-extra-fileczyta użytkownika i hasło z założonego przed chwilą pliku. Ta opcja musi stać jako pierwsza.--single-transactiontworzy przy tabelach InnoDB spójny wewnętrznie stan, bez blokowania bazy.--routinesi--eventszabierają do kopii procedury składowane oraz zaplanowane eventy. Triggerymysqldumpzapisuje i tak sam z siebie.gzipkompresuje eksport i dzięki temu wyraźnie oszczędza miejsce na dysku.- Polecenie
findusuwa wyłącznie pliki kopii starsze niż siedem dni. PrzezKEEP_DAYSdopasujesz okres przechowywania.
Wiersz set -euo pipefail jest ważniejszy, niż wygląda. Bez pipefail powłoka ocenia tylko kod wyjścia gzip, a ten wynosi zero także wtedy, gdy mysqldump wcześniej się przerwał. Miałbyś wtedy technicznie bezbłędne archiwum z niekompletną zawartością i nikt by tego nie zauważył.
To, jaki serwer bazodanowy u ciebie działa, zależy od dystrybucji. Debian nie zawiera pakietu mysql-server i stawia w całości na MariaDB, Ubuntu oferuje natomiast jedno i drugie. Skrypt działa we wszystkich przypadkach bez zmian:
| System | mysql-server | mariadb-server |
|---|---|---|
| Debian 13 | brak | 11.8 |
| Debian 12 | brak | 10.11 |
| Ubuntu 24.04 LTS | 8.0 | 10.11 |
| Ubuntu 22.04 LTS | 8.0 | 10.6 |
Nadanie praw wykonywania i test
Nadaj skryptowi prawo wykonywania:
chmod +x /opt/mysqlbackups/mysql_export_all.sh
Uruchom go raz ręcznie i sprawdź wynik, zanim przejdziesz do automatyzacji:
/opt/mysqlbackups/mysql_export_all.sh
ls -lh /opt/mysqlbackups/
Powstały plik powinien być wyraźnie większy niż zero bajtów. Bardziej wymowny od początku pliku jest jednak jego koniec. Sprawdź więc, czy archiwum nie jest uszkodzone, a eksport faktycznie dobiegł końca:
gzip -t /opt/mysqlbackups/alldbs_*.sql.gz
zcat /opt/mysqlbackups/alldbs_*.sql.gz | tail -n 1
Ostatni wiersz kompletnego eksportu zaczyna się od -- Dump completed on. Jeśli go brakuje, eksport został przerwany, a plik jest jako kopia bezwartościowy, nawet jeśli waży kilkaset megabajtów. Ten jeden wiersz to najszybszy wiarygodny test, jaki masz.
Konfiguracja zadania cron dla codziennego backupu
Otwórz edytor crontaba:
export VISUAL=nano; crontab -e
Dla codziennej kopii o piątej rano dopisz następujący wiersz:
0 5 * * * /opt/mysqlbackups/mysql_export_all.sh >> /var/log/mysql-backup.log 2>&1
Wyjście trafia dzięki temu do pliku logu, więc później prześledzisz ewentualne błędy. Czy wpis został poprawnie zapisany, sprawdzisz poleceniem crontab -l. Zadanie musi przy tym stać w crontabie roota. W wielu obrazach Ubuntu logowanie jako root jest wyłączone, tam wywołanie brzmi sudo crontab -e. Jako zwykły użytkownik założysz w innym wypadku własnego crontaba, a skrypt polegnie później na pliku dostępowym w /root.
W minimalnych instalacjach rodziny RHEL czasem brakuje usługi cron. Instalujesz ją i włączasz tak:
dnf install cronie -y
systemctl enable --now crond
Od teraz wszystkie bazy są eksportowane każdej nocy o piątej, a kopie starsze niż siedem dni znikają automatycznie. Więcej o harmonogramach i typowych pułapkach znajdziesz w artykule Konfiguracja zadania cron w Linuksie.
Przywracanie kopii zapasowej
Kopia jest coś warta dopiero wtedy, gdy potrafisz ją też wgrać z powrotem. Przetestuj więc świadomie przywracanie, najlepiej na systemie testowym:
zcat /opt/mysqlbackups/alldbs_2026-07-26-05-00.sql.gz | mysql --defaults-extra-file=/root/.my.cnf
Jeśli chcesz odzyskać tylko jedną bazę, najpierw rozpakuj kopię i wytnij z niej odpowiedni fragment, albo dodatkowo zapisuj pojedyncze bazy osobno przez mysqldump --databases meinedb. Wygodniej jest jednak od razu zapisywać osobny plik dla każdej bazy. Zamień w tym celu w skrypcie wiersz z mysqldump na taką pętlę:
for DB in $(mysql --defaults-extra-file=/root/.my.cnf -N -B -e "SHOW DATABASES;" | grep -Ev '^(information_schema|performance_schema|sys)$'); do
mysqldump --defaults-extra-file=/root/.my.cnf --single-transaction --routines --events "$DB" | gzip > "$BACKUP_DIR/${DB}_$DATE.sql.gz"
done
Te trzy wykluczone bazy to widoki na wewnętrzny stan serwera i nie da się ich sensownie przywrócić. Dopasuj też wzorzec wyszukiwania w poleceniu find, bo inaczej nie posprząta już nowych nazw plików. Jak wygląda kompletna przeprowadzka na inny serwer, pokazuje na praktycznym przykładzie artykuł Przeniesienie WordPressa na nowy serwer.
Przechowywanie kopii poza serwerem
Kopie leżące wyłącznie na tym samym serwerze nic nie pomogą, gdy stracisz cały system. Przenoś więc pliki dodatkowo w drugie miejsce, na przykład przez rsync albo scp na inny serwer lub do pamięci masowej na kopie zapasowe:
rsync -avz /opt/mysqlbackups/ uzytkownik@serwer-backupu:/sciezka/do/backupu/
Dopisz to polecenie po prostu na końcu swojego skryptu, a transfer będzie się wykonywał automatycznie. Żeby zadziałało to w cronie bez pytania o hasło, root potrzebuje klucza SSH bez passphrase, którego część publiczna leży na systemie docelowym. Jak go założyć, opisujemy w artykule Połączenie z serwerem przez SSH.
Różnice między Debianem a Ubuntu
Oba systemy używają apt, oba trzymają konfigurację w /etc/mysql/, a powyższy skrypt działa na obu bez zmian. Mimo to jest sześć punktów, w których się różnią, i każdy z nich potrafi po cichu wysypać backup:
| Zagadnienie | Debian | Ubuntu |
|---|---|---|
| Serwer bazodanowy | wyłącznie MariaDB | MySQL 8.0 lub MariaDB |
| Pakiet klienta | mariadb-client | mysql-client-8.0 lub mariadb-client |
| Narzędzie do kopii | mariadb-dump, mysqldump jako dowiązanie | przy MySQL tylko mysqldump |
| Logowanie roota | unix_socket | auth_socket przy MySQL z pakietu |
| Konto serwisowe | od MariaDB 10.4 już nie zakładane | debian-sys-maint w /etc/mysql/debian.cnf |
| Log błędów | journal, journalctl -u mariadb | przy MySQL /var/log/mysql/error.log |
Nazwy pakietów i narzędzia
Jeśli tworzysz kopię bazy z innego komputera, potrzebujesz tam tylko pakietu klienta. Na Debianie nazywa się on mariadb-client, na Ubuntu z MySQL 8 nazywa się mysql-client-8.0. Dla skryptu, który ma działać na obu systemach, jest metapakiet default-mysql-client: na Debianie wskazuje on na klienta MariaDB, a na Ubuntu na klienta MySQL.
Od MariaDB 10.5 narzędzia noszą dodatkowo nazwę z przedrostkiem mariadb-, a od MariaDB 11 to ta nazwa jest właściwa i mysqldump stanowi już tylko dowiązanie do niej. Oba wywołania tam działają. Na systemie Ubuntu z MySQL 8 istnieje za to wyłącznie mysqldump, a skrypt z mariadb-dump kończy się tam komunikatem command not found. Dla skryptów, które mają działać w obu światach, właściwym wywołaniem jest więc mysqldump.
Konto serwisowe i uwierzytelnianie
Na Ubuntu pakiet mysql-server nadal zakłada konto serwisowe debian-sys-maint i zapisuje jego dane dostępowe do /etc/mysql/debian.cnf. Ten plik jest czytelny tylko dla roota i da się go bez żadnych przygotowań użyć wprost do backupu:
mysqldump --defaults-file=/etc/mysql/debian.cnf --all-databases --single-transaction --routines --events | gzip > /opt/mysqlbackups/alldbs.sql.gz
Na Debianie z MariaDB od wersji 10.4 to konto nie jest już zakładane na nowo. Plik zwykle jeszcze tam istnieje, ale z reguły wskazuje tylko na roota przez socket. Nie zakładaj więc, że skrypt, który na Ubuntu działa przez ten plik, zrobi na Debianie to samo. Sprawdzisz to w jednym kroku:
mysql --defaults-file=/etc/mysql/debian.cnf -e "SELECT current_user();"
Nowe konta MySQL 8 zakłada metodą caching_sha2_password, MariaDB metodą mysql_native_password. Dla eksportu przez lokalny socket nie ma to znaczenia, ale ma, gdy tworzysz kopię przez sieć ze starszego klienta. Jeśli zgłasza on „The server requested authentication method unknown to the client”, klient jest za stary dla MySQL 8 i nadaje się do aktualizacji.
AppArmor na Ubuntu
Na Ubuntu AppArmor jest aktywny fabrycznie i ogranicza serwer bazodanowy, czyli proces mysqld względnie mariadbd. Nie ogranicza natomiast narzędzia mysqldump, i właśnie ta różnica decyduje o tym, czy w ogóle natkniesz się na tę pułapkę.
Eksport z tego poradnika zapisuje plik przez powłokę (| gzip > ...), czyli jako użytkownik uruchamiający skrypt. Ta droga AppArmora nie dotyczy i działa w każdym katalogu, do którego użytkownik może pisać. Gdy jednak pisze sam serwer, obowiązują inne reguły. Tak jest przy mysqldump --tab=/pfad i przy każdym SELECT ... INTO OUTFILE, bo wtedy plik tworzy proces serwera, a nie twoja powłoka. Wchodzą wówczas w grę dwie niezależne od siebie blokady: zmienna serwera secure_file_priv oraz profil AppArmora dla serwera. Na Ubuntu zmienna wskazuje fabrycznie na /var/lib/mysql-files/ i dokładnie ten katalog jest też odblokowany w profilu AppArmora. Ścieżka docelowa taka jak /opt/mysqlbackups polegnie więc podwójnie.
Podstępne jest przy tym szukanie przyczyny. Pierwsza blokada zgłasza się wyraźnie komunikatem ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement. Druga pojawia się natomiast jako zwykłe Errcode: 13 "Permission denied", choć właściciel i uprawnienia katalogu docelowego wyglądają na pierwszy rzut oka całkiem poprawnie. Kto zaczyna wtedy nadawać prawa do plików, szuka w złym miejscu. Sprawdź w takim przypadku jedno i drugie:
mysql -e "SHOW VARIABLES LIKE 'secure_file_priv';"
aa-status | grep -Ei 'mysqld|mariadbd'
journalctl -k | grep -i 'apparmor.*DENIED' | tail -n 20
Jeśli w ostatnim poleceniu pojawi się wiersz z apparmor="DENIED" i twoją ścieżką docelową, masz przyczynę. Najprościej w ogóle do tego nie dopuszczać i zostać przy eksporcie przez potok. Jeśli naprawdę potrzebujesz zapisu po stronie serwera, pisz do /var/lib/mysql-files/ i przenoś plik dopiero potem. Dopiero gdy obie te drogi odpadają, rozszerz profil o dodatkową ścieżkę. Służy do tego plik /etc/apparmor.d/local/usr.sbin.mysqld, przy MariaDB odpowiednio usr.sbin.mariadbd, a po zmianie systemctl reload apparmor. Wyłączenie AppArmora nie jest rozwiązaniem, tylko usunięciem warstwy ochronnej, która zabezpiecza dokładnie ten serwer.
Częste błędy i rozwiązania
Kopia ma 0 bajtów: Najczęściej nie zgadzają się dane dostępowe w /root/.my.cnf. Przetestuj logowanie poleceniem mysql --defaults-extra-file=/root/.my.cnf -e "SHOW DATABASES;". Często stoi za tym opisany wyżej przypadek, w którym konto działa przez socket, więc zapisane hasło w ogóle nie pasuje.
„Access denied” w cronie, ale nie w konsoli: Zadanie cron działa jako inny użytkownik, niż zakładasz. Wpisz je do crontaba roota i używaj w skrypcie wyłącznie ścieżek bezwzględnych. Kolejne przyczyny zbiera artykuł Naprawa błędu MySQL Access denied for user.
Unknown table 'COLUMN_STATISTICS' in information_schema: Tworzysz kopię bazy MariaDB narzędziem mysqldump z MySQL 8, na przykład z komputera z Ubuntu. MySQL 8 odpytuje przy eksporcie tabelę, której w MariaDB nie ma. Dopisz do wywołania --column-statistics=0. Tę opcję zna tylko klient MySQL, na czystym systemie z MariaDB nie wolno jej ustawiać.
Access denied; you need (at least one of) the PROCESS privilege(s) for this operation: Użytkownik backupu nie może pod MySQL 8 odczytać informacji o tablespace'ach. Nadaj PROCESS tak, jak opisano wyżej, albo dopisz --no-tablespaces.
mariadb-dump: command not found: Skrypt pochodzi z systemu Debian z MariaDB 11, a działa teraz na Ubuntu z MySQL 8. Tam takiej nazwy nie ma. Wpisz mysqldump, to działa na obu systemach.
Can't connect to local MySQL server through socket: Serwer nie działa albo socket leży pod inną ścieżką, niż zakładasz. Możliwe przyczyny opisuje artykuł Naprawa błędów socketu MySQL.
SELinux blokuje skrypt (AlmaLinux, Rocky Linux, RHEL): Sprawdź poleceniem ausearch -m avc -ts recent, czy zapisano odmowę dostępu, i w razie potrzeby dopasuj kontekst katalogu z kopiami.
Dysk się zapełnia: Zmniejsz KEEP_DAYS albo przenieś starsze kopie na zewnętrzną pamięć masową. Co jeszcze zjada miejsce, pokazuje artykuł Pełny dysk w Linuksie: jak posprzątać.
Komunikat o błędzie przy --single-transaction i tabelach MyISAM: Ta opcja działa tylko przy InnoDB. Przy tabelach MyISAM możesz zamiast tego użyć --lock-tables, wtedy tabele zostaną zablokowane na czas eksportu. Pamiętaj też, że tabele systemowe w MariaDB leżą w silniku Aria i --single-transaction ich nie obejmuje.
Skoro i tak siedzisz właśnie przy bazie danych, zrób od razu resztę: usuń anonimowych użytkowników, skasuj bazę testową i wyłącz zdalny dostęp dla roota. Jak to zrobić, opisujemy w artykule Zabezpieczanie MariaDB i MySQL.
Najczęstsze pytania
Dlaczego hasło do bazy nie powinno stać w poleceniu mysqldump?
Czym różnią się Debian i Ubuntu przy tworzeniu kopii baz MySQL?
AppArmor blokuje na Ubuntu zapis pliku kopii. Co zrobić?
Czy poradnik działa na każdej dystrybucji Linuksa?
Co zrobić, gdy bazodanowe konto root w ogóle nie ma hasła?
Jak długo przechowywane są kopie zapasowe?
Czy wystarczy trzymać kopie na tym samym serwerze?
Jak wgrać kopię zapasową z powrotem?
Zadanie cron nie działa albo zgłasza Access denied. Co mogę sprawdzić?
Kopia ma 0 bajtów. Z czego to wynika?
2024-2026 KernelHost GmbH. Wszelkie prawa zastrzeżone. Ten poradnik jest chroniony prawem autorskim. Publikowanie go w innych serwisach, w całości, we fragmentach lub w zmienionej formie, wymaga naszej pisemnej zgody. Cytaty z podaniem źródła i z linkiem są jak najbardziej mile widziane.

