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.
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:
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!!