Den NGINX FastCGI Cache richtig nutzen am Beispiel von Kirby CMS

Die Tatsache, dass die Geschwindigkeit Ihrer Website das Ranking in Suchmaschinen erheblich beeinflusst, ist mittlerweile weit bekannt. Besucher erwarten schnelle Reaktionszeiten; dauert das Laden zu lange, verlassen sie die Seite oft, um nach Alternativen zu suchen – im schlimmsten Fall kommen sie nie wieder.

Um diesem Problem entgegenzuwirken, integriert nahezu jedes Content-Management-System (CMS) ein Caching-System, das bereits generierte Seiten schneller ausliefert und somit auch die Serverressourcen schont. Bei populären Systemen wie WordPress stehen sogar mehrere verschiedene Caching-Plugins zur Verfügung, welche ganz gute Ergebnisse erzielen. Es geht aber besser.

Was macht nun dieser NGINX FastCGI Cache anders?

NGINX ist bekannt für seine Effizienz und Leistungsfähigkeit, besonders wenn es um das Caching von Webinhalten geht. Im Gegensatz zu anderen Caching-Lösungen, die oft auf PHP basieren, nutzt der NGINX FastCGI oder Proxy Cache keine PHP-Prozesse für das Caching selbst. Dies führt zu einer signifikanten Reduzierung der Serverbelastung und zu schnelleren Antwortzeiten. Dies gilt auch für Projekte, die mit anderen Sprachen wie Perl, Python oder auch NodeJS erstellt wurden. Dort kommt aber in der Regel der NGINX Proxy Cache zum Einsatz.

Hier sind einige Schlüsselaspekte, die den NGINX Cache besonders machen:

  • Keine Scriptsprache involviert: Da NGINX das Caching unabhängig von PHP und anderen Schriftsprachen handhabt, werden PHP-Prozesse nicht für jede Anfrage gestartet. Das erhöht die Geschwindigkeit der Seitenaufrufe erheblich und reduziert die CPU-Last auf dem Server.
  • Schnelles Speicherbackend / RAM-Disk möglich: NGINX kann seine Cache-Daten auf schnellen SSD/NVMe Disks oder auch in der RAM-Disk speichern, wodurch die Zugriffszeiten auf den Cache extrem beschleunigt werden. Das Speichern von Cache-Daten im RAM reduziert zudem die Latenzzeiten beim Abrufen von gecachten Inhalten drastisch und sorgt für blitzschnelle Ladezeiten.
  • Konfigurierbar auf NGINX-Ebene: Die Flexibilität von NGINX erlaubt es Administratoren, detaillierte und spezifische Caching-Regeln direkt auf der Serverebene festzulegen. Dies bietet mehr Kontrolle über das, was gecacht wird und wie lange es im Cache bleibt. Die NGINX Konfiguration ist bei uns einfach über unser WHF-Panel möglich.
  • Keine weitere Software oder Serverinstallation: Da NGINX das Caching übernimmt, muss keine weiteres Plugin installiert und konfiguriert werden. Es wird kein weiterer Caching-Server, wie z. B. Varnish benötigt. Alles erledigt der NGINX Webserver.

Vorbereitung: Die NGINX Konfiguration für Kirby CMS

Die nachfolgende NGINX Konfiguration kann nach Anpassung von Domainname, IP-Adressen und Pfaden direkt verwendet werden und auch an andere CMS und Shop Systeme angepasst werden. Unsere Hosting-Kunden genießen hier die Vorteile durch unser WHF-Panel, da die Konfiguration dort mit wenigen Mausklicks intuitiv erledigt ist.

## Umleitung auf HTTPS und www/ohne www
server {
  listen 126.xx.xx.xx:80;
  listen [2a01:xx:xx:xx::1]:80;
  listen 126.xx.xx.xx:443 ssl;
  listen [2a01:xx:xx:xx::1]:443 ssl;
  http2 on;
  # server_name www.beispiel-domain.de;
  server_name beispiel-domain.de;


  ### SSL Configuration
  ssl_session_cache shared:SSL:50m;
  ssl_session_timeout 5m;
  ssl_certificate /etc/ssl/beispiel-domain.de.crt;
  ssl_certificate_key /etc/ssl/beispiel-domain.de.key;
  ssl_trusted_certificate /etc/ssl/beispiel-domain.de.ca;
  ssl_dhparam  ssl/dhparam.pem;
  # return 301 https://beispiel-domain.de$request_uri;
  return 301 https://www.beispiel-domain.de$request_uri;
}

## Umleitung auf HTTPS
server {
  listen 126.xx.xx.xx:80;
  listen [2a01:xx:xx:xx::1]:80;

  # server_name beispiel-domain.de;
  server_name www.beispiel-domain.de;

  # return 301 https://beispiel-domain.de$request_uri;
  return 301 https://www.beispiel-domain.de$request_uri;

}


## FastCGI Cache initialisieren
fastcgi_cache_path /mnt/ram-disk/cache/beispiel-domain.de levels=1:2 keys_zone=beispiel-domain.de:1000m inactive=600m;

server {
  listen 126.xx.xx.xx:443 ssl;
  listen [2a01:xx:xx:xx::1]:443 ssl;
  http2 on;

  server_name www.beispiel-domain.de;
  index index.php index.html index.htm;

  root "/var/www/beispiel-domain.de/";

  error_log /var/www/logs/beispiel-domain.de.log;
  access_log /var/www/logs/beispiel-domain.de.log;


  ## Kirby Setup
  # Urls umschreiben
  location / {
      try_files $uri $uri/ /index.php$is_args$args;
  }

  # Zugriffsschutz
  rewrite ^/(content|site|kirby)$ /error last;
  rewrite ^/content/(.*).(txt|md|mdown)$ /error last;
  rewrite ^/(site|kirby)/(.*)$ /error last;

  # Entferne alle Slashes am ende der URL, damit kein doppelter Content entsteht.
  if (!-d $request_filename) {
      rewrite ^/(.+)/$ /$1 permanent;
  }


  # backup/config/source für Zugriff sperren
  location ~ (?:\.(?:bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)|~)$ {
      deny all;
  }

  # Browser Caching für js|css|png|jpg|jpeg|gif|webp|ico|svg 1 Jahr
  location ~* \.(js|css|png|jpg|jpeg|gif|webp|ico|svg)$ {
      expires 1y;
      add_header Pragma public;
      add_header Cache-Control "public";
      try_files $uri $uri/ /index.php$is_args$args;
  }
  ## Kirby Setup Ende



  ### Kirby FastCGI Cache Regeln
  # Cache für alles aktivieren
  set $no_cache 0;

  # Keine Cache bei POST Requests (Formulare)
  if ($request_method = POST) { set $no_cache 1; }

  # Kein Cache wenn in URLs ein Query String enthalten ist (z. B. ?no_cache=1)
  if ($query_string != "") { set $no_cache 1; }

  # Folgede URLs nicht cachen (Kirby Panel)
  if ($request_uri ~* "/panel") { set $no_cache 1; }

  # Folgede URLs nicht cachen (Kirby API)
  if ($request_uri ~* "/api") { set $no_cache 1; }


  # Kein Cache wenn ein Session Cookie gesetzt ist -> PHPSESSID
  if ($http_cookie = "PHPSESSID") { set $no_cache 1; }

  ### Kirby FastCGI Cache Regeln Ende



  ## Purge URL definieren zum leere eines Cache Eintrags
  location ~ /purge(/.*) { fastcgi_cache_purge beispiel-domain.de "$scheme$request_method$host$1"; }

  ## Cache Status Header (HIT, BYPASS, MISS)
  add_header X-NGINX-FastCGI-Cache $upstream_cache_status;

  ## Datum / Version der Konfiguration. Optional.
  add_header X-WHF-NGINX-CONFIG-DATE "16-04-2024 - 10:34:01";

  ## PHP Setup und Cache Setup
  location ~ \.php$ {
       fastcgi_cache beispiel-domain.de;
     
       # Cache für 5 Sekunden
       fastcgi_cache_valid 502 503 404 5s; 

       # Cache für 24 Stunden
       fastcgi_cache_valid 200 301 302 24h;

       fastcgi_cache_bypass $no_cache;
       fastcgi_no_cache $no_cache;
       include fastcgi_params;
       include snippets/fastcgi-php.conf;
      
       fastcgi_pass unix:/var/run/beispiel-domain.de.php82-fpm.socket;
  }

  location ~ /logs { deny  all; access_log off; log_not_found off; } 
  location ~ /\. { deny  all; access_log off; log_not_found off; }




  ## Security Header
  add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
  add_header X-XSS-Protection "1; mode=block";
  add_header X-Frame-Options "SAMEORIGIN";
  add_header X-Content-Type-Options nosniff;
  add_header Referrer-Policy "strict-origin";
  add_header Permissions-Policy "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()";



  ### SSL Configuration
  ssl_session_cache shared:SSL:50m;
  ssl_session_timeout 5m;
  ssl_certificate /etc/ssl/beispliel-domain.de.crt;
  ssl_certificate_key /etc/ssl/beispliel-domain.de.key;
  ssl_trusted_certificate /etc/ssl/beispliel-domain.de.ca;
  ssl_dhparam  ssl/dhparam.pem;
}

Nachdem die Konfiguration angelegt und der Webserver diese neu geladen hat, sollte der Cache aktiv sein.

Da wir hier zwei neuen Header erzeugt haben (X-NGINX-CONFIG-DATE und X-NGINX-FastCGI-Cache), können wir mit den Entwicklertools des Browsers den Status des Cache kontrollieren und ob die Konfiguration auch übernommen wurde.

NGINX Cache Header
NGINX Cache Header

In diesem Fall ist der Status des NGINX FastCGI Caches "HIT". Es wurde ein Cache Eintrag gefunden und aus dem Cache ausgeliefert, was auch sicherlich durch den Zuwachs der Performance bemerkbar ist.

Zeigt der Status den Wert "MISS" an, so wurde die Seite das erste Mal aufgerufen, was dann nach einem Reload in der Regel auf "HIT" wechselt.

Wenn der Wert "BYPASS" enthält, wird kein Cache der Seite erzeugt. Die ist der Fall, wenn eine der folgenden Regeln zutrifft:

# Keine Cache bei POST Requests (Formulare)
 if ($request_method = POST) { set $no_cache 1; }

 # Kein Cache wenn in URLs ein Query String enthalten ist (z. B. ?no_cache=1)
 if ($query_string != "") { set $no_cache 1; }

 # Folgede URLs nicht cachen (Kirby Panel)
 if ($request_uri ~* "/panel") { set $no_cache 1; }

 # Folgede URLs nicht cachen (Kirby API)
 if ($request_uri ~* "/api") { set $no_cache 1; }

 # Kein Cache wenn ein Session Cookie gesetzt ist -> PHPSESSID
 if ($http_cookie = "PHPSESSID") { set $no_cache 1; }

Diese Regeln können Sie Ihren Bedürfnissen anpassen und um weitere Regeln erweitert. Es können auch noch andere Variablen von NGINX ausgewertet werden. Eine Liste der vorhanden Variablen finden Sie in der NGINX Dokumentation.

Eine derartige Flexibilität ist mit herkömmlichen Caching-Plugins nur sehr schwer bis gar nicht möglich.

Den NGINX FastCGI Cache leeren

Oft muss ein Cache-Eintrag vor dem Ablauf erneuert werden, beispielsweise beim Ändern eines Blog-Beitrags. Beim integrierten Cache eines CMS oder Shop Systems muss man sich in der Regel nicht darum kümmern, da der Cache bei Änderungen automatisch gelöscht und bei erneutem Aufruf der Seite ein neuer Cache Eintrag angelegt wird.

Da der NGINX Cache von Ihrem Projekt entkoppelt ist, gibt es unter anderem die Möglichkeit einer sogenannten Purge-URL. Diese URL haben wir in der NGINX Konfiguration wie folgt definiert:

## Purge URL definieren zum leere eines Cache Eintrags
location ~ /purge(/.*) { fastcgi_cache_purge beispiel-domain.de "$scheme$request_method$host$1"; }

Der Aufruf einer URL der Seite mit eingefügtem "purge/" nach dem Domainnamen sogt für die Löschung des jeweiligen Cache Eintrags. Der Aufruf der Seite mit der URL:

"https://www.beispiel-domain.de/purge/category/seite"

führt zu folgender Ausgabe:

NGINX Cache Purge

Die Integration in das Kirby CMS

Das Aufrufen jeder einzelnen URL ist natürlich keine Lösung bei der täglichen Arbeit. Es muss also eine Art Integration in das CMS System erfolgen. Jedes ernstzunehmende CMS oder Shop System besitzt eine API oder eine andere Methode, um auf die Abläufe, wie beispielsweise das Speichern von Beiträgen, zu reagieren.

Im Falle von Kirby gib es ein Hook-System, welches wir sehr einfach benutzen können, um bestimmte Aktionen auszuführen bei entsprechenden Events. Folgender Eintrag kommt in die config.php und bewirkt den Aufruf der Purge-Site-URL mittels exec() und curl, um das Löschen des Cache Eintrages auszulösen, sobald eine Seite gespeichert wird.

......

  'hooks' => [
    'page.update:after' => function (Kirby\Cms\Page $newPage, Kirby\Cms\Page $oldPage) {
          $parsedUrl = parse_url($newPage->url());
          $domainWithScheme = $parsedUrl['scheme'].'://'.$parsedUrl['host'];
          exec('curl '.$domainWithScheme.'/purge/'.$newPage->uri());
    }
  ]

......

Die grundlegende Integration des NGINX Cache in das Kirby CMS ist nun vollzogen und das Ergebnis wird für sich sprechen. Auch ist eine tiefgreifendere Anpassung jederzeit sehr unkompliziert und flexibel realisierbar.

Happy Caching!! Kirby rules!!



Zum Thema passende Artikel
© Webhosting Franken

Diese Webseite verwendet Cookies nur zu absolut notwendigen Zwecken. Weitere Informationen finden Sie unter Datenschutz