Hver nettstedeier drømmer om å gå “viralt”. Men for en serveradministrator blir dette øyeblikket ofte et mareritt. Siden begynner å laste i 30 sekunder, og brukerne ser “Error 500” eller “503 Service Unavailable”.
I denne artikkelen vil jeg vise deg hvordan du forbereder WordPress for høy trafikk, og hvorfor en cache-utvidelse alene ikke er nok.
PHP workers-grense i WordPress og feil 503
De fleste WordPress-krasj skyldes ikke mangel på RAM, men utmattelse av PHP Workers-grensen.
Hva er en PHP worker
Tenk på en PHP Worker som en kasserer i en butikk.
- Hvis du har 10 kasserere (10 arbeidere).
- Hver kunde trenger 1 sekund for å bli betjent (tiden det tar å generere siden).
- Du kan håndtere maksimalt 10 kunder i sekundet.
Hvis den 11. kunden kommer, må de vente i kø. Hvis køen fylles opp, svarer serveren med feil 503.
I PHP-FPM er denne “kassereren” en barneprosess i poolen. PHP-dokumentasjonen beskriver pm.max_children rett ut som grensen for hvor mange forespørsler som betjenes samtidig (php.net, FPM-konfigurasjon). Forespørsler over grensen venter i socket-køen, og lengden på den styres av listen.backlog (511 som standard på Linux).
Slik regner du ut kapasiteten til PHP workers
Maksimal trafikk (RPS) = antall workers / gjennomsnittlig genereringstid per side
Regneeksempel med 10 workers på delt hosting:
- Siden genereres på 0,5 s, så du tar imot 20 forespørsler i sekundet.
- Siden genereres på 2,0 s, så du tar imot bare 5 forespørsler i sekundet.
Den samme formelen viser hva 10 000 brukere i timen fra tittelen betyr. Det er i snitt knapt 3 sidevisninger i sekundet, altså noe som 10 workers klarer ved 0,5 s per side. Problemene starter når trafikken ikke fordeler seg jevnt, men kommer i løpet av noen minutter etter at en lenke er delt, eller når én sidevisning utløser flere PHP-forespørsler (AJAX, REST API, handlekurv).
Konklusjon: For å skalere må du enten ansette flere kasserere (dyrere VPS eller dedikerte servere) eller få kassererne til å jobbe raskere (kodeoptimalisering og cache).
Slik setter du pm.max_children i PHP-FPM
Antall workers settes i pool-filen til PHP-FPM (som regel www.conf). Direktivet pm har tre verdier (php.net):
Modus pm | Hvordan det virker | Når det passer |
|---|---|---|
static | fast antall prosesser lik pm.max_children | dedikert server med forutsigbar trafikk |
dynamic | antallet varierer mellom pm.min_spare_servers og pm.max_spare_servers, aldri over pm.max_children | de fleste VPS-er |
ondemand | prosesser starter først ved en forespørsel og avsluttes etter pm.process_idle_timeout (standard 10 s) | mange små pooler på én server |
I modusen dynamic er pm.start_servers som standard gjennomsnittet av min_spare_servers og max_spare_servers.
Det er minnet som setter taket for pm.max_children. Regneeksempel: serveren har 8 GB RAM, og MySQL, Redis og systemet bruker 3 GB. Hvis én PHP-FPM-prosess med WordPress og WooCommerce bruker rundt 100 MB på det meste (mål dette på din egen server, for eksempel med RSS-kolonnen i ps), blir det 5 GB igjen til PHP, altså rundt 50 prosesser. Med 200 workers i denne situasjonen får du ikke mer kapasitet, bare en server som går i swap når trafikktoppen kommer.
To direktiver hjelper mot minnelekkasjer og skript som henger:
pm.max_requestsstarter en prosess på nytt etter et gitt antall forespørsler. PHP-dokumentasjonen beskriver dette som en måte å omgå minnelekkasjer i tredjepartsbiblioteker. Standard er 0, altså ingen grense.request_terminate_timeoutdreper en prosess som bruker lenger enn angitt tid på én forespørsel, nårmax_execution_timeav en eller annen grunn ikke stopper skriptet. Standard er 0, altså av.
Slik ser du om PHP-FPM har for få workers
I stedet for å gjette slår du på statussiden til poolen med pm.status_path (for eksempel /fpm-status) og åpner den i Nginx bare for administrative adresser. Feltene som dokumentasjonen for FPM-statussiden beskriver, svarer direkte på spørsmålet om workers:
listen queue: hvor mange forespørsler som akkurat nå venter på en ledig prosess.max listen queue: den lengste køen siden poolen startet.active processesogidle processes: hvor mange prosesser som jobber, og hvor mange som venter.max children reached: verdien 1 eller høyere betyr at poolen minst én gang har nåddpm.max_children.slow requests: antall forespørsler som har passertrequest_slowlog_timeout.
Alle tellere nullstilles når FPM startes på nytt. Hvis max children reached øker og køen ikke er tom, mangler du workers eller sidene genereres for sakte. For å finne ut hva av det, setter du request_slowlog_timeout (for eksempel 5 s) og en sti for slowlog. PHP skriver da en backtrace for hver treg forespørsel, altså navnet på utvidelsen og funksjonen som holder workeren opptatt.
Full page cache i WordPress med Varnish og Nginx
Den raskeste PHP er den som ikke kjører.
Hvis siden din er statisk (f.eks. en bloggpost), bør du servere en ferdig HTML-fil.
Typer full page cache
- Utvidelser: WP Rocket, W3 Total Cache. De lagrer HTML-filer på disk. Det holder for små nettsteder, men når forespørselen likevel går gjennom PHP, er workeren opptatt selv med cache.
- Server-Side Cache (Pro):
- Varnish: En proxy-server som holder ferdige sider i RAM foran Apache eller Nginx.
- Nginx FastCGI Cache: Innebygd i Nginx.
- Cloudflare: Serverer siden før trafikken når webhotellet ditt.
Regel: Anonym trafikk (besøkende som ikke er logget inn) bør ikke nå PHP i det hele tatt. Hver visning fra cache er en ledig worker for handlekurv, kundekonto og innloggede brukere.
Slik konfigurerer du Varnish for WordPress og cookies
Varnish cacher ofte nesten ingenting på en nyoppsatt WordPress-side, og det er i tråd med dokumentasjonen. Ifølge Varnish-veiledningen om treffrate lagrer standardoppsettet ikke svar med Set-Cookie-header, og når klienten sender en Cookie-header, går Varnish forbi cachen og rett til backend. På en side med Google Analytics eller et samtykkebanner har nesten alle besøkende en eller annen cookie.
Løsningen fra dokumentasjonen er å fjerne cookies i vcl_recv der backend ikke trenger dem. WordPress kjenner igjen innloggede brukere på cookies med prefikser som er definert i kjernen (wordpress_, wordpress_sec_, wordpress_logged_in_), og kommentatorer på comment_author_. Disse må slippes gjennom til PHP, resten kan fjernes:
sub vcl_recv {
if (req.url ~ "^/(wp-admin|wp-login\.php|handlekurv|kasse|min-konto)") {
return (pass);
}
if (req.http.Cookie !~ "(wordpress_|wp-postpass_|comment_author_|woocommerce_items_in_cart|wp_woocommerce_session_)") {
unset req.http.Cookie;
}
}Stiene for handlekurv og kasse i eksempelet er bare eksempler, tilpass dem til slugene i butikken din. Nyere Varnish-versjoner deler den innebygde vcl_recv i mindre rutiner, og beskrivelsen av innebygd VCL viser at cookie-regelen kan slås av for seg via vcl_req_cookie. Det gir bare mening når backend alltid selv sier hva som kan caches.
Slik tømmer du Varnish-cachen etter publisering
Lang levetid på objektene gir høyere treffrate, men etter at et innlegg er redigert, må leseren se den nye versjonen. Varnish-dokumentasjonen om purge og ban beskriver to mekanismer:
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 fjerner ett objekt med alle varianter som følger av
Vary-headeren. Cache-utvidelsen sender enPURGE-forespørsel til adressen til innlegget.acl-listen er obligatorisk, ellers kan hvem som helst på internett tømme cachen din. - Ban virker som et filter på objekter som allerede ligger i cachen, for eksempel “alt under
/kategori/”. Bans som bare sjekkerobj.*, behandles i bakgrunnen av en tråd som heter “ban lurker”. Dokumentasjonen anbefaler å unngåreq.*i bans, fordi forespørselsobjektet ikke er tilgjengelig i den tråden.
Redis object cache for WordPress og WooCommerce
Hva med WooCommerce-butikker og kundeområder som ikke kan caches helt som HTML? Der må PHP jobbe.
WordPress kjører dusinvis av de samme SQL-spørringene ved hver lasting: hent innstillingene til siden, hent kategori-ID-er, hent metadata for brukeren.
Redis (Object Cache) lagrer resultatene av databaseforespørsler i RAM. I stedet for å spørre MySQL, spør PHP Redis.
Hvor mange spørringer som forsvinner, avhenger av tema og utvidelser. Du kan sjekke det selv: Query Monitor viser antall SQL-spørringer på en side før og etter at Redis er slått på.
Å kjøre WooCommerce uten Redis i 2026 er å be om problemer.
Hva filen object-cache.php endrer i WordPress
WordPress har en egen object cache, men den er ikke persistent. Dokumentasjonen for klassen WP_Object_Cache sier rett ut at cachen som standard bare holder data i minnet og bare til den gjeldende forespørselen er ferdig. Hver sidevisning starter med tom cache og spør databasen på nytt.
Persistens får du med en drop-in. Funksjonen wp_start_object_cache() laster filen wp-content/object-cache.php hvis den finnes, og fra da av snakker wp_cache_get(), wp_cache_set() og wp_cache_delete() med Redis i stedet for med en tabell i prosessminnet. Utvidelser som Redis Object Cache gjør i praksis én ting: de kopierer denne filen og gir den tilkoblingsdataene.
Tre følger som er lette å overse:
- Transients flytter ut av databasen. Med en ekstern object cache skriver
set_transient()ogdelete_transient()viawp_cache_*i stedet for til tabellenwp_options(set_transient). Tømmer du Redis, sletter du altså også alle transients. - Ikke alt havner i Redis. Kjernen merker gruppene
counts,pluginsogtheme_jsonsom ikke-persistente via wp_cache_add_non_persistent_groups(), så de lever fortsatt bare i minnet til én forespørsel. - Når WordPress selv foreslår Redis. Fra versjon 6.1 anbefaler Site Health persistent object cache på alle multisite-installasjoner, og ellers når de autoladede innstillingene passerer 500 oppføringer eller 100 000 byte, eller når en av tabellene for kommentarer, innstillinger, innlegg, termer eller brukere har minst 1000 rader. Tersklene kan endres med filteret site_status_persistent_object_cache_thresholds.
Slik setter du maxmemory og eviction-policy i Redis
Redis uten minnegrense vokser til RAM-en er brukt opp. På 64-bits systemer er maxmemory 0, altså ingen grense, standardverdien (redis.io, eviction). På en server som deler minne med PHP-FPM og MySQL setter du grensen eksplisitt:
maxmemory 512mb
maxmemory-policy allkeys-lru512 MB er et eksempel, ikke en anbefaling, velg verdien ut fra datamengden. Viktigere er policyen. Redis-dokumentasjonen kaller allkeys-lru et godt standardvalg når en del av nøklene leses mye oftere enn resten, og det er det typiske bildet av en WordPress-cache. volatile-*-policyene fjerner bare nøkler med utløpstid, og når det ikke finnes slike, oppfører de seg som noeviction: Redis begynner da å returnere feil ved skriving. I WordPress har wp_cache_set() som standard $expire = 0, så en stor del av nøklene kan mangle TTL. volatile-lru på en slik cache ender med skrivefeil i stedet for at gamle data byttes ut.
Om cachen gjør jobben, ser du med INFO stats: treffraten er keyspace_hits / (keyspace_hits + keyspace_misses). Høy evicted_keys sammen med lav treffrate betyr at minnegrensen er for liten eller at policyen fjerner feil nøkler.
Slik utelater du WooCommerce-handlekurven fra cache
WooCommerce-dokumentasjonen om oppsett av cache-utvidelser nevner tre sider som må forbli dynamiske fordi de viser data for den enkelte kunden: handlekurv, kasse og min konto. Den nevner også cookies der cachen må vike: woocommerce_cart_hash og woocommerce_items_in_cart følger endringer i handlekurven, og wp_woocommerce_session_ identifiserer kundens handlekurv. Hvis en databasecache på serveren også dekker sesjoner, må _wc_session_ holdes utenfor.
Den samme dokumentasjonen advarer om at enkelte verter med cache på serversiden cacher min konto-siden, og det gir en løkke ved tilbakestilling av passord. Det er det første symptomet å sjekke når kunder melder at de ikke får byttet passord etter at Varnish ble slått på.
Slik konfigurerer du Nginx FastCGI cache i stedet for Varnish
Hvis serveren uansett kjører Nginx, trenger du ikke en egen Varnish. Modulen ngx_http_fastcgi_module cacher svar fra PHP-FPM på disk og holder nøklene i delt minne. Nginx-dokumentasjonen oppgir at en sone på 1 MB rommer omtrent 8 000 nøkler.
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;
}
}Hva de viktigste direktivene gjør, ifølge Nginx-dokumentasjonen:
fastcgi_cache_bypasshenter ikke svaret fra cachen, ogfastcgi_no_cachelagrer det ikke, når en av verdiene er ikke-tom og ulik “0”. Derfor får begge samme variabel i eksempelet.fastcgi_cache_lock onslipper bare én forespørsel gjennom til PHP for å fylle en ny oppføring i cachen. De andre venter. Det er nettopp dette som beskytter workerne når hundrevis av mennesker åpner den samme, ennå ikke cachede adressen etter at en lenke er delt.fastcgi_cache_use_stale updatingsammen medfastcgi_cache_background_update ongir leseren en eldre kopi og oppdaterer i bakgrunnen.- Parameteren
inactivefjerner oppføringer som ingen har lest i løpet av den angitte tiden, uansett hvor ferske de er. Standard er 10 minutter.
Hvorfor WordPress krasjet på Black Friday
Prosjekt: Nyhetsportal på Black Friday. Plutselig trafikkøkning, serveren døde på 3 minutter, CPU på 100 %.
Årsaker til krasjet
- Killer Ajax: En “Besøkssteller”-utvidelse sendte en AJAX-forespørsel (
admin-ajax.php) ved hvert besøk, og den ble ikke cachet.- Ajax i WordPress starter alltid hele PHP-motoren med alle utvidelser. Filen admin-ajax.php laster
wp-load.phpog deretter filene til administrasjonspanelet. - 10 000 besøk betydde 10 000 fulle oppstarter av WordPress. Grensen for PHP workers var nådd på 10 sekunder.
- Ajax i WordPress starter alltid hele PHP-motoren med alle utvidelser. Filen admin-ajax.php laster
- Ingen indekser i DB: Innleggstabellen hadde 500 000 rader, og en utvidelse for “Relaterte innlegg” brukte
ORDER BY RAND(). Det drepte MySQL.
Hvordan krasjet ble løst
- Deaktiverte Ajax (brukte Google Analytics, altså JavaScript i nettleseren).
- Implementerte Redis, som senket belastningen på MySQL med 90 %.
- Konfigurerte Varnish til å servere forsiden fra RAM.
Resultat i denne saken: Serveren som knelte ved 100 brukere, betjente etterpå 5000 samtidig med 5 % CPU. Tallene gjelder én nyhetsportal, ikke en norm for alle installasjoner.
Slik overvåker du ytelsen til WordPress
- Query Monitor: Utvidelse for SQL-analyse og minnebruk på gjeldende side.
- New Relic: Avansert APM-overvåking som viser hvilken PHP-funksjon eller utvidelse som bruker mest tid.
- PHP-FPM-status:
pm.status_pathog slowlog, som beskrevet over. - Redis:
INFO statsogINFO memory. - Logger:
tail -fi SSH (se vår SSH-guide).
Sjekkliste for WordPress med høy trafikk
- Hosting: Cloud VPS med Redis og konfigurerbare PHP Workers.
- Workers: Regn ut
pm.max_childrenfra RAM og slå påpm.status_path. - Cache: Varnish eller Nginx FastCGI, med en liste over cookies som går forbi cachen og en ACL for PURGE.
- Database: Redis som object cache, med
maxmemoryogallkeys-lru. - Butikk: Utelat handlekurv, kasse og min konto fra cache.
- Opprydding: Deaktiver admin-ajax på frontend.
- CDN: Flytt bilder og CSS til Cloudflare.
Stabil WordPress er ikke magi, det er regning. Senk genereringstiden (cache) og øk kapasiteten (workers og Redis).






