Każdy właściciel strony marzy o “viralu”. O momencie, w którym jego artykuł trafia na Wykop, Reddit, lub stronę główną Onetu.
Ale dla administratora serwera ten moment często zamienia się w koszmar. Strona zaczyna ładować się 30 sekund, a potem użytkownicy widzą już tylko “Error 500: Internal Server Error” lub “503 Service Unavailable”.
W tym artykule, opartym na mojej prezentacji z “Fuckup Night”, pokażę Ci, jak przygotować WordPressa na duży ruch (High Traffic) i dlaczego Twoja wtyczka cache to za mało.
Jak limit PHP workers ogranicza ruch na WordPressie
Większość awarii WordPressa nie wynika z braku RAMu czy procesora, ale z wyczerpania limitu PHP Workers.
Czym jest PHP worker
Wyobraź sobie PHP Workera jako kasjera w sklepie.
- Jeśli masz 10 kasjerów (10 workers).
- Każdy klient (odwiedzający) potrzebuje 1 sekundy na obsłużenie (czas generowania strony).
- Możesz obsłużyć maksymalnie 10 klientów na sekundę.
Jeśli przyjdzie 11. klient, musi czekać w kolejce. Jeśli kolejka się zapełni, serwer zwraca błąd 503.
W PHP-FPM tym “kasjerem” jest proces potomny puli. Dokumentacja PHP opisuje dyrektywę pm.max_children wprost: ustala ona limit jednocześnie obsługiwanych żądań (php.net, konfiguracja FPM). Żądania ponad ten limit czekają w kolejce gniazda, której długość wyznacza listen.backlog (domyślnie 511 na Linuksie).
Jak obliczyć przepustowość PHP workers
Maksymalny Ruch (RPS) = Liczba Workerów / Średni Czas Generowania Strony
Przykładowy rachunek dla 10 workerów na hostingu współdzielonym:
- Strona generuje się 0,5 s, więc możesz przyjąć 20 żądań na sekundę.
- Strona generuje się 2,0 s, więc możesz przyjąć tylko 5 żądań na sekundę.
Ten sam wzór pokazuje, co oznacza tytułowe 10 tys. użytkowników na godzinę. To średnio niecałe 3 odsłony na sekundę, czyli liczba, którą przy 0,5 s na stronę udźwignie nawet 10 workerów. Problem zaczyna się wtedy, gdy ruch nie rozkłada się równo, tylko spada w kilka minut po publikacji linku, albo gdy pojedyncza odsłona wywołuje kilka żądań PHP (AJAX, REST API, koszyk).
Wniosek: Aby skalować, musisz albo dokupić kasjerów (droższe serwery VPS/Dedykowane), albo sprawić, by kasjerzy pracowali szybciej (Optymalizacja kodu/Cache).
Jak ustawić pm.max_children w PHP-FPM
Liczbę workerów ustawiasz w pliku puli PHP-FPM (zwykle www.conf). Dyrektywa pm przyjmuje trzy wartości (php.net):
Tryb pm | Jak działa | Kiedy ma sens |
|---|---|---|
static | stała liczba procesów równa pm.max_children | dedykowany serwer z przewidywalnym ruchem |
dynamic | liczba procesów zmienia się między pm.min_spare_servers a pm.max_spare_servers, nigdy powyżej pm.max_children | większość serwerów VPS |
ondemand | procesy startują dopiero przy żądaniu i giną po pm.process_idle_timeout (domyślnie 10 s) | wiele małych pul na jednym serwerze |
W trybie dynamic liczba procesów startowych pm.start_servers domyślnie wynosi średnią z min_spare_servers i max_spare_servers.
Górną granicę pm.max_children wyznacza pamięć, nie ambicja. Przykładowy rachunek: serwer ma 8 GB RAM, z czego MySQL, Redis i system zajmują 3 GB. Jeśli pojedynczy proces PHP-FPM z WordPressem i WooCommerce zajmuje w szczycie około 100 MB (zmierz to na własnym serwerze, na przykład kolumną RSS w ps), to na PHP zostaje 5 GB, czyli około 50 procesów. Ustawienie 200 workerów w tej sytuacji nie zwiększy przepustowości, tylko wepchnie serwer w swap, gdy przyjdzie fala ruchu.
Dwie dyrektywy przydają się przy wyciekach pamięci i zawieszonych skryptach:
pm.max_requestsrestartuje proces po zadanej liczbie żądań. Dokumentacja PHP podaje to jako obejście wycieków pamięci w zewnętrznych bibliotekach. Domyślnie 0, czyli bez limitu.request_terminate_timeoutzabija proces, który obsługuje jedno żądanie dłużej niż zadany czas, gdymax_execution_timez jakiegoś powodu nie zatrzymuje skryptu. Domyślnie 0, czyli wyłączone.
Jak sprawdzić, czy PHP-FPM ma za mało workerów
Zamiast zgadywać, włącz stronę statusu puli dyrektywą pm.status_path (na przykład /fpm-status) i wystaw ją przez Nginx tylko dla adresów administracyjnych. Pola, które dokumentacja strony statusu FPM opisuje, odpowiadają na pytanie o workery bezpośrednio:
listen queue: ile żądań w tej chwili czeka na wolny proces.max listen queue: największa kolejka od startu puli.active processesiidle processes: ile procesów pracuje, a ile czeka.max children reached: wartość 1 lub więcej oznacza, że pula przynajmniej raz uderzyła wpm.max_children.slow requests: liczba żądań, które przekroczyłyrequest_slowlog_timeout.
Wszystkie liczniki zerują się przy restarcie FPM. Jeśli max children reached rośnie, a kolejka nie jest pusta, brakuje workerów albo strony generują się za wolno. Żeby rozstrzygnąć, które z dwóch, ustaw request_slowlog_timeout (na przykład 5 s) i ścieżkę slowlog. PHP zapisze wtedy backtrace każdego wolnego żądania, czyli nazwę wtyczki i funkcji, która trzyma workera.
Jak full page cache omija PHP przy dużym ruchu
Najszybszy PHP to taki, który się nie uruchamia.
Jeśli Twoja strona jest statyczna (np. artykuł na blogu), nie ma sensu, by PHP generował ją za każdym razem od nowa. Powinieneś serwować gotowy plik HTML.
Rodzaje full page cache w WordPressie
- Wtyczki (Podstawowe): WP Rocket, W3 Total Cache. Tworzą pliki HTML na dysku. Wystarczają małym stronom, ale gdy żądanie i tak przechodzi przez PHP, worker jest zajęty mimo cache.
- Server-Side Cache (Profesjonalne):
- Varnish: Serwer proxy, który trzyma gotowe strony w pamięci RAM przed Apache/Nginx.
- Nginx FastCGI Cache: Wbudowane w Nginx. Bardzo wydajne.
- Cloudflare (Edge Cache): Serwowanie strony z serwerów Cloudflare, zanim ruch w ogóle dotrze do Twojego hostingu.
Zasada: ruch anonimowy (niezalogowany) nie powinien w ogóle dotykać PHP. Każda odsłona obsłużona z cache to wolny worker dla koszyka, panelu i zalogowanych użytkowników.
Jak skonfigurować Varnish dla WordPressa i ciasteczek
Varnish po instalacji często prawie nic nie cache’uje na WordPressie, i to jest zgodne z dokumentacją. Według poradnika Varnish o współczynniku trafień domyślna konfiguracja nie zapisuje w cache odpowiedzi z nagłówkiem Set-Cookie, a gdy klient wysyła nagłówek Cookie, Varnish omija cache i idzie prosto do backendu. Na stronie z Google Analytics albo z banerem zgód prawie każdy gość ma jakieś ciasteczko.
Rozwiązanie z dokumentacji to usuwanie ciasteczek w vcl_recv wszędzie tam, gdzie backend ich nie potrzebuje. WordPress rozpoznaje zalogowanego użytkownika po ciasteczkach z prefiksami zdefiniowanymi w rdzeniu (wordpress_, wordpress_sec_, wordpress_logged_in_), a komentującego po comment_author_. Te muszą przejść do PHP, reszta może zostać usunięta:
sub vcl_recv {
if (req.url ~ "^/(wp-admin|wp-login\.php|koszyk|zamowienie|moje-konto)") {
return (pass);
}
if (req.http.Cookie !~ "(wordpress_|wp-postpass_|comment_author_|woocommerce_items_in_cart|wp_woocommerce_session_)") {
unset req.http.Cookie;
}
}Adresy koszyka i zamówienia w przykładzie są przykładowe, dopasuj je do slugów swojego sklepu. Nowsze wersje Varnisha dzielą wbudowane vcl_recv na mniejsze procedury, a opis wbudowanego VCL pokazuje, że regułę ciasteczek można wyłączyć osobno przez vcl_req_cookie. Robi się to tylko wtedy, gdy backend zawsze sam określa, co wolno cache’ować.
Jak czyścić cache Varnisha po publikacji wpisu
Długi czas życia obiektów podnosi współczynnik trafień, ale po edycji wpisu czytelnik musi zobaczyć nową wersję. Dokumentacja Varnish o purge i ban opisuje dwa mechanizmy:
acl purge {
"localhost";
"192.168.55.0"/24;
}
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purge) {
return (synth(405, "Not allowed."));
}
return (purge);
}
}- Purge usuwa jeden obiekt razem ze wszystkimi wariantami wynikającymi z nagłówka
Vary. Wtyczka czyszcząca cache wysyła żądaniePURGEna adres wpisu. Listaacljest obowiązkowa, bo bez niej każdy w internecie może opróżnić Twój cache. - Ban działa jak filtr na obiekty już zapisane w cache, na przykład “wszystko pod
/kategoria/”. Bany sprawdzane wyłącznie naobj.*przetwarza w tle wątek “ban lurker”. Dokumentacja zaleca unikaniereq.*w banach, bo obiekt żądania nie jest dostępny w tym wątku.
Jak Redis object cache odciąża bazę WordPressa
Co ze sklepami WooCommerce lub panelami użytkownika, których nie da się w pełni skaszować w HTML? Tam PHP musi pracować.
WordPress przy każdym ładowaniu wykonuje dziesiątki tych samych zapytań SQL: “Pobierz opcje strony”, “Pobierz ID kategorii”, “Pobierz metadane usera”.
Redis (Object Cache) przechowuje wyniki tych zapytań w pamięci RAM. Zamiast pytać MySQL, PHP pyta Redis.
Ile zapytań odpada, zależy od motywu i wtyczek. Sprawdzisz to sam: Query Monitor pokazuje liczbę zapytań SQL na danej stronie przed i po włączeniu Redisa.
W 2026 roku uruchamianie WooCommerce bez Redisa to samobójstwo.
Co zmienia plik object-cache.php w WordPressie
Sam WordPress ma cache obiektów, tylko nietrwały. Dokumentacja klasy WP_Object_Cache mówi wprost, że domyślnie cache trzyma dane wyłącznie w pamięci i tylko do końca bieżącego żądania. Każda odsłona zaczyna od pustego cache i pyta bazę od nowa.
Trwałość daje drop-in. Funkcja wp_start_object_cache() ładuje plik wp-content/object-cache.php, jeśli istnieje, i od tej chwili funkcje wp_cache_get(), wp_cache_set() i wp_cache_delete() rozmawiają z Redisem zamiast z tablicą w pamięci procesu. Wtyczki w rodzaju Redis Object Cache robią w praktyce jedno: kopiują ten plik i podają mu dane połączenia.
Trzy konsekwencje, które łatwo przeoczyć:
- Transienty wychodzą z bazy. Gdy działa zewnętrzny cache obiektów,
set_transient()idelete_transient()zapisują dane przezwp_cache_*, a nie do tabeliwp_options(set_transient). Opróżnienie Redisa kasuje więc też wszystkie transienty. - Nie wszystko trafia do Redisa. Rdzeń oznacza grupy
counts,pluginsitheme_jsonjako nietrwałe przez wp_cache_add_non_persistent_groups(), więc nadal żyją tylko w pamięci jednego żądania. - Kiedy WordPress sam podpowie Redisa. Od wersji 6.1 Site Health proponuje trwały cache obiektów na każdej instalacji multisite oraz wtedy, gdy autoładowane opcje przekraczają 500 wpisów lub 100 000 bajtów albo gdy którakolwiek z tabel komentarzy, opcji, wpisów, terminów lub użytkowników ma co najmniej 1000 wierszy. Progi da się zmienić filtrem site_status_persistent_object_cache_thresholds.
Jak ustawić maxmemory i politykę wymiany w Redisie
Redis bez limitu pamięci rośnie, dopóki nie zabraknie RAM-u. Na systemach 64-bitowych maxmemory 0, czyli brak limitu, jest wartością domyślną (redis.io, eviction). Na serwerze, który dzieli pamięć z PHP-FPM i MySQL, ustaw limit jawnie:
maxmemory 512mb
maxmemory-policy allkeys-lruRozmiar 512 MB to przykład, nie zalecenie, dobierz go do wielkości danych. Ważniejsza jest polityka. Dokumentacja Redisa nazywa allkeys-lru dobrym wyborem domyślnym, gdy część kluczy jest odczytywana znacznie częściej niż reszta, a to typowy obraz cache WordPressa. Polityki volatile-* usuwają tylko klucze z ustawionym czasem wygaśnięcia, a gdy takich kluczy nie ma, zachowują się jak noeviction, czyli Redis zaczyna zwracać błędy przy zapisie. W WordPressie wp_cache_set() ma domyślnie $expire = 0, więc duża część kluczy może nie mieć TTL. Polityka volatile-lru na takim cache kończy się błędami zapisu zamiast wymiany starych danych.
Czy cache pracuje, sprawdzisz poleceniem INFO stats: współczynnik trafień to keyspace_hits / (keyspace_hits + keyspace_misses). Wysokie evicted_keys przy niskim współczynniku trafień oznacza, że limit pamięci jest za mały albo polityka usuwa nie te klucze.
Jak wykluczyć koszyk WooCommerce z cache
Dokumentacja WooCommerce o konfiguracji wtyczek cache wymienia trzy strony, które muszą zostać dynamiczne, bo pokazują dane konkretnego klienta: koszyk, zamówienie i moje konto. Wymienia też ciasteczka, przy których cache musi ustąpić: woocommerce_cart_hash i woocommerce_items_in_cart śledzą zmiany w koszyku, a wp_woocommerce_session_ identyfikuje koszyk klienta. Jeśli cache bazy danych na serwerze obejmuje sesje, trzeba z niego wyłączyć _wc_session_.
Ta sama dokumentacja ostrzega, że część hostingów z cache po stronie serwera cache’uje stronę moje konto, co kończy się pętlą przy resetowaniu hasła. To pierwszy objaw do sprawdzenia, gdy klienci zgłaszają, że “nie da się zmienić hasła” po włączeniu Varnisha.
Jak skonfigurować Nginx FastCGI cache zamiast Varnisha
Jeśli na serwerze i tak działa Nginx, osobny Varnish nie jest konieczny. Moduł ngx_http_fastcgi_module cache’uje odpowiedzi PHP-FPM na dysku, a klucze trzyma we współdzielonej pamięci. Dokumentacja Nginx podaje, że strefa o wielkości 1 MB mieści około 8 tysięcy kluczy.
fastcgi_cache_path /var/cache/nginx/wp levels=1:2 keys_zone=wp:10m inactive=60m;
server {
set $skip_cache 0;
if ($http_cookie ~* "wordpress_logged_in_|wp-postpass_|comment_author_|woocommerce_items_in_cart|wp_woocommerce_session_") {
set $skip_cache 1;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
include fastcgi.conf;
fastcgi_cache wp;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_lock on;
fastcgi_cache_use_stale updating error timeout;
fastcgi_cache_background_update on;
}
}Co robią kluczowe dyrektywy, według dokumentacji Nginx:
fastcgi_cache_bypassnie bierze odpowiedzi z cache, afastcgi_no_cachejej nie zapisuje, gdy któraś z wartości jest niepusta i różna od “0”. Dlatego w przykładzie obie dostają tę samą zmienną.fastcgi_cache_lock onprzepuszcza do PHP tylko jedno żądanie, które wypełnia nowy wpis cache. Pozostałe czekają. To właśnie chroni workery w chwili, gdy po publikacji linku setki osób otwierają ten sam, jeszcze niezapisany adres.fastcgi_cache_use_stale updatingrazem zfastcgi_cache_background_update onoddaje czytelnikowi starszą kopię, a odświeżenie robi w tle.- Parametr
inactiveusuwa wpisy, których nikt nie odczytał przez zadany czas, niezależnie od ich świeżości. Domyślnie to 10 minut.
Dlaczego WordPress padł w Black Friday
Oto historia jednej z naszych awarii, którą analizowaliśmy na spotkaniu WordUp.
Pacjent: Serwis informacyjny. Objawy: Nagły skok ruchu (Black Friday), serwer padł w 3 minuty. CPU 100%.
Przyczyny awarii
Zabójczy Ajax: Pewna wtyczka “Licznik Odwiedzin” wysyłała zapytanie AJAX (
admin-ajax.php) przy każdym wejściu użytkownika, by zliczyć wizytę.- Ajax w WordPressie zawsze uruchamia cały silnik PHP (wszystkie wtyczki). Plik admin-ajax.php ładuje
wp-load.php, a potem jeszcze pliki panelu administracyjnego. - To zapytanie NIE było cache’owane.
- 10,000 wejść = 10,000 pełnych uruchomień WordPressa. Limit PHP Workers wyczerpany w 10 sekund.
- Ajax w WordPressie zawsze uruchamia cały silnik PHP (wszystkie wtyczki). Plik admin-ajax.php ładuje
Brak Indeksów w Bazie: Tabela z postami miała 500,000 rekordów. Wtyczka do “Podobnych wpisów” robiła
ORDER BY RAND(). To zarżnęło bazę MySQL.
Jak naprawiono awarię
- Wyłączenie Ajaxa: Zastąpiliśmy licznik wtyczką Google Analytics (JavaScript po stronie klienta).
- Redis: Wdrożenie Redisa zmniejszyło obciążenie MySQL o 90%.
- Varnish: Skonfigurowaliśmy Varnish, by serwował stronę główną z RAMu.
Wynik w tej awarii: Serwer, który dławił się przy 100 userach, nagle obsłużył 5000 userów jednocześnie, a CPU spadło do 5%. To liczby z jednego serwisu, nie norma dla każdej instalacji.
Jak monitorować wydajność WordPressa
Nie zgaduj “czemu muli”.
- Query Monitor (Dev): Wtyczka, która pokazuje wolne zapytania SQL i zużycie pamięci na bieżącej stronie.
- New Relic (Pro): Zaawansowany monitoring APM. Pokazuje dokładnie, która funkcja PHP lub wtyczka zajmuje najwięcej czasu.
- Status PHP-FPM:
pm.status_pathi slowlog, opisane wyżej. - Redis:
INFO statsiINFO memory. - Logs:
tail -f /var/log/nginx/error.log(patrz nasz poradnik o SSH).
Jaka jest checklista na duży ruch dla WordPressa
Przygotowujesz się na duży ruch?
- Hosting: Wybierz taki z Redisem i konfigurowalnym limitem PHP Workers (np. dedykowany, cloud VPS).
- Workery: Policz
pm.max_childrenz pamięci RAM i włączpm.status_path. - Cache: Skonfiguruj Nginx FastCGI Cache lub Varnish, z listą ciasteczek, które omijają cache, i z ACL dla PURGE.
- Baza: Zainstaluj wtyczkę Object Cache (np. Redis Object Cache), ustaw
maxmemoryiallkeys-lru. - Sklep: Wyklucz koszyk, zamówienie i moje konto z cache.
- Sprzątanie: Wyłącz wtyczki używające
admin-ajax.phpna frontendzie. - CDN: Przenieś obrazki i CSS do Cloudflare.
Stabilny WordPress to nie magia. To matematyka. Zmniejsz czas generowania (Cache) i zwiększ przepustowość (Workers + Redis).
Potrzebujesz pomocy ze skalowaniem? Jako specjalista WordPress pomagam przygotować strony na duży ruch. Sprawdź cennik usług WordPress.






