Skalering av WordPress for 10k brukere i timen

Skalering av WordPress for 10k brukere i timen

Sist verifisert: 12. oktober 2026
13 min lesetid
Guide
Core Web Vitals

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 pmHvordan det virkerNår det passer
staticfast antall prosesser lik pm.max_childrendedikert server med forutsigbar trafikk
dynamicantallet varierer mellom pm.min_spare_servers og pm.max_spare_servers, aldri over pm.max_childrende fleste VPS-er
ondemandprosesser 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_requests starter 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_timeout dreper en prosess som bruker lenger enn angitt tid på én forespørsel, når max_execution_time av 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 processes og idle 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ådd pm.max_children.
  • slow requests: antall forespørsler som har passert request_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

  1. 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.
  2. 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 en PURGE-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 sjekker obj.*, 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() og delete_transient() via wp_cache_* i stedet for til tabellen wp_options (set_transient). Tømmer du Redis, sletter du altså også alle transients.
  • Ikke alt havner i Redis. Kjernen merker gruppene counts, plugins og theme_json som 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-lru

512 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_bypass henter ikke svaret fra cachen, og fastcgi_no_cache lagrer det ikke, når en av verdiene er ikke-tom og ulik “0”. Derfor får begge samme variabel i eksempelet.
  • fastcgi_cache_lock on slipper 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 updating sammen med fastcgi_cache_background_update on gir leseren en eldre kopi og oppdaterer i bakgrunnen.
  • Parameteren inactive fjerner 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

  1. 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.php og 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.
  2. 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

  1. Deaktiverte Ajax (brukte Google Analytics, altså JavaScript i nettleseren).
  2. Implementerte Redis, som senket belastningen på MySQL med 90 %.
  3. 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

  1. Query Monitor: Utvidelse for SQL-analyse og minnebruk på gjeldende side.
  2. New Relic: Avansert APM-overvåking som viser hvilken PHP-funksjon eller utvidelse som bruker mest tid.
  3. PHP-FPM-status: pm.status_path og slowlog, som beskrevet over.
  4. Redis: INFO stats og INFO memory.
  5. Logger: tail -f i SSH (se vår SSH-guide).

#Sjekkliste for WordPress med høy trafikk

  1. Hosting: Cloud VPS med Redis og konfigurerbare PHP Workers.
  2. Workers: Regn ut pm.max_children fra RAM og slå på pm.status_path.
  3. Cache: Varnish eller Nginx FastCGI, med en liste over cookies som går forbi cachen og en ACL for PURGE.
  4. Database: Redis som object cache, med maxmemory og allkeys-lru.
  5. Butikk: Utelat handlekurv, kasse og min konto fra cache.
  6. Opprydding: Deaktiver admin-ajax på frontend.
  7. CDN: Flytt bilder og CSS til Cloudflare.

Stabil WordPress er ikke magi, det er regning. Senk genereringstiden (cache) og øk kapasiteten (workers og Redis).

Se vår WordPress-hastighetsoptimalisering.

Neste steg

Gjør artikkelen om til faktisk implementering

Denne blokken styrker intern lenking og sender leseren videre til de mest relevante tjenestene og innholdet.

Vil du få dette implementert på nettstedet ditt?

Hvis problemet er Core Web Vitals, treg rendering eller tung WordPress-kjoring, kan jeg definere og gjennomfore optimaliseringen.

Artikkel-FAQ

Ofte stilte spørsmål

Praktiske svar for å bruke temaet i faktisk arbeid.

SEO-readyGEO-readyAEO-ready1 Q&A

Populære spørsmål

Hva er WordPress-skalering?#
Det handler om å holde WordPress raskt og stabilt under høy trafikk ved hjelp av caching, riktig serveroppsett og kontroll på PHP workers.

Trenger du FAQ tilpasset bransje og marked? Vi lager en versjon som støtter dine forretningsmål.

Ta kontakt

Relaterte artikler