Sklep WooCommerce, który zwraca błąd 521, a w logach ma MISCONF Redis is configured to save RDB snapshots, najczęściej ma pełny dysk. Winowajcą bywa backup UpdraftPlus pakowany na tym samym dysku co produkcja albo logi bez rotacji. Poniżej opisujemy, jak taka awaria wygląda od środka, dlaczego strona główna potrafi wtedy dalej odpowiadać 200 i jak ustawić zabezpieczenie, które reaguje, zanim miejsce się skończy, a nie po fakcie.
Jak wygląda pełny dysk w sklepie WooCommerce?
Objawy były zawsze podobne. Koszyk, wp-admin i wp-json zwracały 521 z Cloudflare, a po chwili klient widział komunikat o błędzie połączenia z bazą danych. Strona główna dalej otwierała się z kodem 200, więc zwykły monitoring uptime nie alarmował.
To pułapka. Cloudflare potrafi podać stronę główną z własnej pamięci podręcznej, także wtedy, gdy origin nie żyje, dopóki wpis w cache jest ważny albo włączone jest serwowanie nieaktualnej treści. Jeśli sprawdzasz sklep jednym adresem, widzisz zielone światło przy martwym zapleczu. Diagnozę zaczynamy od kontroli koszyka i adresu dynamicznego, na przykład wp-json, oraz od nagłówka CF-Cache-Status. Losowy parametr (?nc=1234) często omija cache, ale nie zawsze, bo Cloudflare może ignorować query string w kluczu.
Co zapycha dysk produkcyjnego sklepu?
Na dysku 150 GB, na którym stały sklepy i baza, miejsce znikało dwiema drogami.
Backup UpdraftPlus pakowany lokalnie
UpdraftPlus tworzy paczki najpierw w wp-content/updraft, dopiero potem wysyła je do zdalnego magazynu i kasuje lokalne pliki. Katalog uploadów miał 53 GB, a pełny zestaw zajmował zwykle od około 12 do 34 GB. Nieudane próby potrafiły dobić do 50 GB. Raz kopia pocięła uploady na 129 części po około 400 MB, razem około 50 GB. Innym razem wysyłanie do zdalnego magazynu urwało się w połowie. Na dysku zostały 262 pliki, 49,5 GB, i zajętość doszła do 145 z 150 GB.
Logi bez rotacji
Drugi scenariusz to logi. W jednym przypadku zjadły dysk cztery pliki:
- debug log integratora magazynowego: 5,5 GB, bo logowanie debug zostało włączone i nikt go potem nie wyłączył,
- log wtyczki eksportu: 1,9 GB powtarzanego komunikatu „Export is not triggered”,
- log JSON kontenera z WordPressem: 1,5 GB, nakręcany błędami object cache,
- access log serwera: 202 MB.
Błędy Redisa zwiększały log kontenera, a pełny log kontenera pogłębiał problem z miejscem. Pętla zamykała się sama.
Dlaczego pełny dysk wyłącza Redisa, bazę i cały serwer?
Przy pełnym dysku awarie szły kaskadą:
- Redis. Nie może zapisać migawki RDB, więc przy ustawieniu
stop-writes-on-bgsave-error yesodrzuca wszystkie zapisy. W logach WordPressa widaćMISCONForazFailed to execute SET/ZADD. Sklep działa, ale bez object cache. - Kontenery. Trzy kontenery padły z kodem wyjścia 128, w tym kontener WordPressa, Redis i kontener drugiej strony stojącej na tym samym VPS-ie.
- Baza. Nocny restart MariaDB z crona trafił w moment pełnego dysku. Proces mysqld nie doszedł do stanu gotowości, a kontener wyglądał na działający, choć gniazdo było martwe.
- Proxy. Nginx Proxy Manager, wspólny dla wszystkich witryn na serwerze, po restarcie wpadł w pętlę z błędem
[emerg] host not found in upstream, bo jeden z kontenerów stał zatrzymany. Bez proxy nie działał żaden sklep na tej maszynie, nie tylko ten z awarią.
Jak sprawdzić, które kontenery naprawdę działają?
Po awarii docker ps -a pokazywał „Up 33 hours” przy kontenerach, które dawno padły. Stan odczytujemy z inspekcji:
docker inspect --format '{{.Name}} {{.State.Status}} exit={{.State.ExitCode}}' $(docker ps -aq)Kontener w stanie „running” z martwą bazą sprawdzamy połączeniem z poziomu aplikacji, a nie samym statusem.
Masz ten sam problem? Strona główna działa, ale koszyk zwraca 521? Sprawdzimy dysk, kontenery i bazę, przywrócimy sklep i ustawimy alerty. Opisz sytuację →
Jak przywrócić sklep po zapełnieniu dysku?
- Usuń przerwane paczki z
wp-content/updraft, gdy backup się nie dokończył (zostają pliki tymczasowe bez końcowego archiwum) albo kompletna kopia leży w zdalnym magazynie. W ten sposób odzyskaliśmy 40 GB. Jeśli nie masz pewności, przenieś paczki na inny dysk zamiast je kasować. - Uruchom zatrzymane kontenery (Redis, WordPress), potem zrestartuj proxy, a na końcu bazę.
- Sprawdź test konfiguracji nginx w proxy. Przechodzi dopiero wtedy, gdy wszystkie kontenery z konfiguracji istnieją.
- Zweryfikuj sklep z zewnątrz, z parametrem omijającym cache: strona główna, koszyk,
wp-json, przez kilka prób. - Dla Redisa sprawdź
BGSAVEirdb_last_bgsave_status:ok.
Potem sprzątamy to, co zajmuje miejsce na stałe: wyłączamy debug logi, dodajemy rotację, ograniczamy logi Dockera (max-size: 20m, max-file: 3) i usuwamy stare obrazy. W jednym przypadku obraz kontenera zajmował 23 GB, bo budowanie kopiowało do warstwy cały katalog z uploadami. Plik .dockerignore zmniejszył go do 960 MB.
Jak zabezpieczyć dysk, żeby to się nie powtórzyło?
Skrypt sprzątający raz na dobę nie wystarcza. Nasz miał warunek „pliki starsze niż 36 godzin” i działał o 7:30, po oknie backupu. Chronił przed backupem zawieszonym, ale nie przed takim, który zapycha dysk w godzinę. Są to dwa różne tryby awarii i trzeba zabezpieczyć oba.
Dlatego ustawiamy strażnika uruchamianego co 15 minut z progiem wolnego miejsca, u nas 20 GB. Po przekroczeniu progu:
- zatrzymuje procesy
wp-cronsklepu, które niosą zadanie backupu, - usuwa zdarzenie
updraft_backup_resumei dane zadaniaupdraft_jobdata_*, bo bez tego UpdraftPlus wraca przy następnym wywołaniu crona i dokańcza zapychanie, - przenosi paczki na osobny wolumen poleceniem
mv, nierm, bo to może być jedyna kopia.
Przy testach wyszły dwie pułapki. Pierwsza wersja szukała procesów przez pgrep -f wp-cron.php, co łapało też powłokę SSH zawierającą ten ciąg w linii poleceń. Skrypt musi dopasować pełną ścieżkę i sprawdzić, że proces to php. kill -9 przerywa zadanie bez sprzątania, co przy zawieszonym backupie jest tu celowe:
P=/var/www/html/nazwa-sklepu # ścieżka instalacji sklepu
for pid in $(pgrep -f "$P/wp-cron.php"); do
cmd=$(tr '\0' ' ' < /proc/$pid/cmdline)
case "$cmd" in *php*"$P/wp-cron.php"*) kill -9 "$pid";; esac
doneDruga: katalog /var/log ma grupę syslog, więc bez dyrektywy su root syslog logrotate po cichu pomija rotację, uznając uprawnienia za niebezpieczne. Reguła wygląda dobrze, ale nie robi nic.
Uwaga o drugim dysku. Wpis o zamontowaniu wolumenu w docker-compose.yml nie działa, dopóki kontener nie zostanie utworzony od nowa. Symlink na hoście też nie pomoże, bo wewnątrz kontenera katalog /mnt jest pusty. Sprawdź df z poziomu kontenera, nie hosta: ma pokazać drugi dysk.
Co ustawić w sklepie WooCommerce od razu?
- alert przy wolnym miejscu poniżej 20% na każdym wolumenie,
- rotację logów aplikacji i Dockera,
- retencję kopii UpdraftPlus i zdalny magazyn, a staging na osobnym dysku,
- wykluczenie z kopii katalogów, które same są kopiami (na przykład backupy wtyczki do obrazów),
- kontrolę koszyka, nie tylko strony głównej, w monitoringu zewnętrznym,
- wyłączanie debug logów po testach.
Szerzej o tym, co mierzyć poza dostępnością, piszemy w artykule o monitoringu sklepu WooCommerce i PrestaShop.
Kiedy zlecić to specjalistom?
Gdy sklep stoi, a nie wiadomo, co zjadło miejsce i w jakiej kolejności uruchamiać kontenery, żeby nie pogłębić awarii. W ASAPDevs przywracamy sklep, ustalamy przyczynę i ustawiamy zabezpieczenia w ramach opieki nad WordPressem. Gdy liczy się czas reakcji, zobacz Pogotowie IT.
Źródła: dokumentacja Redis, Persistence (zachowanie stop-writes-on-bgsave-error); Cloudflare, Error 521: Web server is down.
