Manual:Nginx caching
This page describes an alternative to Manual:Varnish caching for sites using Nginx. It supports multiple wikis on one server, and serving cached mobile pages from the same domain using Extension:MobileFrontend. Cache purging is supported, so anonymous visitors always get up to date content.
Note: The strategy used forces the Accept-Language header to be en-US, so the UI language for anonymous visitors will always be English. You can change this to any other language, if your wiki is primarily targeted at another demographic. Caching different versions of pages depending on the Accept-Language header sent by the visitor's browser is not supported.
This article assumes some basic familiarity with the configuration of Nginx, PHP, and MediaWiki.
Prelude
[edit]When serving MediaWiki from Nginx using PHP-FPM, it's possible to use the caching capabilities of the Nginx FastCGI module to cache responses generated by MediaWiki's PHP code. This allows Nginx to serve anonymous visitors directly from memory, without needing to ask the PHP-FPM process to execute any code at all.
Visitors who are logged in, or have made edits (and thus receive a session token), should not be served from this cache, since MediaWiki's PHP code needs to customize the page for them, such as to display their user name, load user CSS/JS, display talk page notifications, and so on.
This caching strategy should be used in addition to, and not as a replacement for, the parser cache and other internal caching mechanisms of MediaWiki. Even if you use this strategy, you should still also use the various internal caching mechanisms supported by MediaWiki, such as with APCu, memcached, and so on.
However, a traditional "front-end HTTP cache" such as Varnish is essentially rendered obsolete when using this strategy. That being said, Varnish may be better suited for setups in which there are multiple back-end servers for load-balancing purposes. This strategy instead assumes a single Nginx server hosting one or more wikis.
On-disk or in-memory
[edit]Although the Nginx FastCGI cache uses the filesystem, one can arrange for it to reside in a RAM filesystem such as /dev/shm to make sure it's entirely in-memory. If a persistent cache is desired, an SSD should also work well, especially since modern operating systems cache accessed files in memory anyway.
Packages needed
[edit]The strategy detailed below uses the libnginx-mod-http-cache-purge package, available on Debian 13, to purge pages from the cache when they're edited, so anonymous visitors get up to date content.
For Nginx itself, the author of this article uses the nginx-full package, although the smaller packages such as nginx-core or even nginx-light may work as well. (FastCGI seems available in all.)
Installing nginx-extras will pull in the required libnginx-mod-http-cache-purge package automatically as a dependency.
The cache purge package may be available on older Debian versions, or other operating systems, as well. However, make sure it's based on the newer fork found here rather than the very old original which doesn't seem to support wildcard keys for purging (this will be explained later).
Note: As of October 2025, a bug in the aforementioned purge module leads to memory leaks when using wildcard purging. A patch to solve this has been submitted but waiting approval, and may require some time until propagated into various GNU/Linux package repositories. You can compile and deploy the fix manually by following these instructions if you're comfortable doing so.
If no purge module is available, you can still use the caching strategy on this page, but will have to live with the fact that anonymous visitors may get outdated wiki content until the cached version of a page expires on its own.
Nginx configuration
[edit]Debian ships an /etc/nginx/nginx.conf file that automatically includes further configuration files from /etc/nginx/conf.d, and virtual-host configuration files from /etc/nginx/sites-enabled (a symlink farm to /etc/nginx/sites-available). It also ships /etc/nginx/fastcgi.conf which contains a standard set of FastCGI configuration parameters.
If you don't already have such a core structure under /etc/nginx, please make sure to create one first. The rest of this section assumes such a base setup and won't teach you how to create it. If in doubt, copy what Debian 13 does, because this article is written
The configuration is split up as follows:
/etc/nginx/conf.d/php-fpm.conf: Generic PHP-FPM settings./etc/nginx/conf.d/mediawiki-fcgi.conf: The "MediaWiki FastCGI / Cached" core setup./etc/nginx/snippets/run-php-direct: Snippet to include to run PHP code bypassing the cache./etc/nginx/snippets/run-php-mwfc: Snippet to include to run PHP code using the "MediaWiki FastCGI / Cached" setup./etc/nginx/sites-enabled/mwfc-purge: The purge request handler, so to speak./etc/nginx/sites-enabled/wiki1: Virtual-host configuration for wiki 1. (example)/etc/nginx/sites-enabled/wiki2: Virtual-host configuration for wiki 2. (example)
You can have multiple wikis under sites-enabled and the caching configuration will ensure that their pages aren't confused for each other even if they happen to have identical titles.
php-fpm.conf
[edit]This file is very simple and merely defines the location of the PHP-FPM socket as an "upstream" in Nginx terms.
upstream php_fpm {
server unix:/run/php/php8.4-fpm.sock;
}
mediawiki-fcgi.conf
[edit]This is the most complex file, and contains the majority of the caching setup.
Please tweak values in this file to suit your needs. This one uses up to 16 GB of RAM!
This is also where you define the location of the cache; this example uses /dev/shm/ngx-mwcache which means it resides on an in-memory filesystem whose contents will be gone after a reboot.
For details on how the cache purging works, read the comments in this file under NEW PURGE STRATEGY. It explains how we arrange for the mobile version of a page to be purged as well when a purge for the URL is requested. It also makes sure that responses for HTTP requests other than GET, such as HEAD, are also purged.
#
# MediaWiki FastCGI with Response Caching (Setup)
#
# This file is meant to be included from within an http { } block.
# See 'snippets/run-php-mwfc' for usage within location { } blocks.
#
# Variables defined with map are prefixed to prevent clashes with other
# parts of your Nginx configuration in case you have a complex config.
#
# The prefix "mwfc" stands for "MediaWiki FastCGI / Cached".
#
# The cache is held in RAM and may use up to 16 GB of memory.
# The key zone may use an additional 250 MB of memory.
fastcgi_cache_path
/dev/shm/ngx-mwcache
levels=1:2
keys_zone=MW:250m
max_size=16000m
inactive=2d;
# Check whether user is visiting from mobile.
# Use a simple 1/0 to be used as part of the cache key.
map $http_user_agent $mwfc_mobile {
default 0;
# Regexes based on:
# https://gerrit.wikimedia.org/r/plugins/gitiles/operations/puppet/+/refs/heads/production/modules/varnish/templates/text-frontend.inc.vcl.erb
"~(SMART-TV.*SamsungBrowser)" 0;
"~*^(lge?|sie|nec|sgh|pg)-" 1;
"~*(mobi|240x240|240x320|320x320|alcatel|android|audiovox|bada|benq|blackberry|cdm-|compal-|docomo|ericsson|hiptop|htc[-_]|huawei|ipod|kddi-|kindle|meego|midp|mitsu|mmp\/|mot-|motor|ngm_|nintendo|opera.m|palm|panasonic|philips|phone|playstation|portalmmm|sagem-|samsung|sanyo|sec-|semc-browser|sendo|sharp|silk|softbank|symbian|teleca|up.browser|vodafone|webos)"
1;
}
### OLD PURGE STRATEGY ###
#
# PURGE request purges cached responses for GET requests.
# Responses for other requests, like HEAD, can't be purged.
# We also can't purge mobile from desktop, and vice versa.
#
#map $request_method $mwfc_req_key {
# default $request_method;
# PURGE GET;
#}
#
#fastcgi_cache_key "$host$request_uri$mwfc_req_key$mwfc_mobile";
#
### END OLD PURGE STRATEGY ###
### NEW PURGE STRATEGY ###
#
# Use a wildcard to purge responses for all HTTP methods, mobile and desktop.
#
# However, we don't want a PURGE for the URI /foo to also purge /foo/bar.
# Worst case would be "/" purging "/*" i.e. all URIs.
#
# For this reason, we terminate the URI with a pipe. This should be safe,
# because the pipe should be percent-encoded in the received URI.
#
map $request_method $mwfc_cache_key {
default "$host$request_uri|$request_method$mwfc_mobile";
PURGE "$host$request_uri|*";
}
fastcgi_cache_key "$mwfc_cache_key";
### END NEW PURGE STRATEGY ###
# We will imitate Apache's AMF headers, which use true/false.
map $mwfc_mobile $mwfc_mobile_amf {
0 false;
1 true;
}
# This is the simplest way to make Extension:MobileFrontend understand
# that we do our own UA detection so it doesn't attempt to do its own.
fastcgi_param AMF_DEVICE_IS_MOBILE $mwfc_mobile_amf;
# MediaWiki doesn't set Cache-Control for some Special:MyXyz pages.
# https://phabricator.wikimedia.org/T272431
map $request_uri $mwfc_bypass_uri {
default 0;
"~*/Special:My" 1;
"~*/Special:AllMy" 1;
}
# Does the URI represent a resource that varies on cookies?
# To be safe, assume all URIs do, except those specified.
map $request_uri $mwfc_bypass_cookie_uri {
default 1;
"~^/w/(load|opensearch_desc|thumb)\.php" 0;
"~^/php/" 0;
}
# Do we actually have a cookie that causes response variance?
map $http_cookie $mwfc_bypass_cookie_value {
default 0;
"~([sS]ession|Token)=" 1;
"~mf_useformat" 1;
}
# Perform an AND on the previous two variables.
map $mwfc_bypass_cookie_uri$mwfc_bypass_cookie_value $mwfc_bypass_cookie {
default 0;
"11" 1;
}
log_format mwfc_stats
"$upstream_cache_status $status $request_method $host$request_uri";
run-php-direct
[edit]This is a snippet file to be included from location { } blocks when PHP code should be executed unconditionally, without consulting the cache or attempting to store the result.
include /etc/nginx/fastcgi.conf;
fastcgi_pass php_fpm;
run-php-mwfc
[edit]This snippet file is included from location { } blocks when you want PHP code execution with caching.
#
# MediaWiki FastCGI with Response Caching (Execution)
#
# This file is meant to be included from within location { } blocks,
# to actually pass a request to PHP-FPM.
#
# See conf.d/mediawiki-fcgi.conf for the setup this depends on.
#
include /etc/nginx/fastcgi.conf;
#access_log /var/log/nginx/mwcache-stats.log mwfc_stats gzip;
fastcgi_pass php_fpm;
fastcgi_cache MW;
fastcgi_cache_valid 1h;
fastcgi_cache_use_stale error timeout http_500 http_503;
# Ignore Vary because we took care of everything ourselves.
fastcgi_ignore_headers Vary;
# Anon visitors only get English.
fastcgi_param HTTP_ACCEPT_LANGUAGE en-US;
# Variable $mwfc_bypass_cookie defined in MWFC setup file.
fastcgi_cache_bypass $mwfc_bypass_uri $mwfc_bypass_cookie;
# Note: This doesn't correspond to the upstream FastCGI docs!
#
# The functionality is provided by this package:
# libnginx-mod-http-cache-purge
#
# Whose documentation can be found here:
# https://github.com/nginx-modules/ngx_cache_purge
#
fastcgi_cache_purge PURGE from 127.0.0.0/8;
add_header X-Cache $upstream_cache_status always;
mwfc-purge
[edit]This virtual-host configuration file is just to handle the PURGE requests coming from localhost, sent by MediaWiki itself.
Use of TCP port 1080 seems to be hardcoded into MediaWiki, so let's hope you don't use it for something else already!
server {
listen 1080 default_server;
access_log /var/log/nginx/mwfc-purge-access.log;
error_log /var/log/nginx/mwfc-purge-error.log;
location / {
if ($request_method != "PURGE") {
return 405;
}
include snippets/run-php-mwfc;
}
}
wiki1 / wiki2 / etc.
[edit]Here's an example wiki virtual-host configuration. This one is taken from bg3.wiki and simplified down a bit.
Note that the Cache-Control headers set in some cases have nothing to do with our PHP caching strategy. These just tell the visitor's browser that it can cache certain files, like images or CSS code files, for a certain duration.
# Easily toggle logging on/off here
map "" $bg3log {
default 0;
}
map $request_uri $bg3log_pagehits {
default 0;
"~^/wiki/" 1; # toggle page-hit logging
}
log_format bg3wiki_pagehits
'$time_iso8601 $remote_addr $status '
'$request_uri $http_referer "$http_user_agent"';
server {
listen 443 ssl;
server_name bg3.wiki;
ssl_certificate /etc/letsencrypt/live/bg3.wiki/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bg3.wiki/privkey.pem;
access_log /var/log/nginx/bg3wiki-access.log combined if=$bg3log;
error_log /var/log/nginx/bg3wiki-error.log;
access_log /var/log/nginx/bg3wiki-pagehits.log
bg3wiki_pagehits if=$bg3log_pagehits;
log_not_found off;
root /var/www/bg3;
# No cache for robots, sitemaps
location ~ ^/(robots|sitemap) {
}
# Static assets outside of MediaWiki
location ~ ^/(favicon|static) {
# Cache 30 days
add_header Cache-Control "max-age=2592000, public";
}
# Custom CSS and JS code
location ~ ^/(css|js)/ {
# Cache 5 min
add_header Cache-Control "max-age=300, public";
}
# Custom PHP scripts
location /php/ {
include snippets/run-php-mwfc;
}
# Root URL is main page
location = / {
fastcgi_index w/index.php;
include snippets/run-php-mwfc;
}
location = /wiki/Main_Page {
return 301 /;
}
# Location for wiki's entry points
location ~ ^/w/(index|load|api|thumb|opensearch_desc|rest)\.php$ {
include snippets/run-php-mwfc;
}
# Block Discord's broken page preview tool
location = /w/api.php {
if ($http_user_agent ~ "Discordbot") {
return 403;
}
include snippets/run-php-mwfc;
}
# Images
location /w/images {
# Cache 24 h
add_header Cache-Control "max-age=86400, public";
}
location /w/images/deleted {
deny all;
}
# MediaWiki assets (usually images)
location ~ ^/w/resources/(assets|lib|src) {
# Cache 30 days
add_header Cache-Control "max-age=2592000, public";
}
# Assets, scripts and styles from skins and extensions
location ~ ^/w/(skins|extensions)/.+\.(css|js|gif|ico|jpg|jpeg|png|svg|webp|wasm|ttf|woff|woff2)$ {
# Cache 30 days
add_header Cache-Control "max-age=2592000, public";
}
# Handling for Mediawiki REST API, see [[mw:API:REST_API]]
location /w/rest.php/ {
try_files $uri $uri/ /w/rest.php?$query_string;
}
# Handling for the article path (pretty URLs)
location /wiki/ {
rewrite ^/wiki/(?<pagename>.*)$ /w/index.php;
}
# Every other entry point will be disallowed.
# Add specific rules for other entry points/images as needed above this
location / {
return 404;
}
}
# On port 80, handle LetsEncrypt requests, otherwise redirect to HTTPS.
server {
listen 80;
server_name bg3.wiki www.bg3.wiki;
access_log /var/log/nginx/bg3wiki-redirect.log combined if=$bg3log;
error_log /var/log/nginx/bg3wiki-error.log;
location /.well-known/acme-challenge {
root /var/www/html;
try_files $uri =404;
}
location / {
return 301 https://bg3.wiki$request_uri;
}
}
# Redirect erroneous www. subdomain requests to main domain.
server {
listen 443 ssl;
server_name www.bg3.wiki;
ssl_certificate /etc/letsencrypt/live/bg3.wiki/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bg3.wiki/privkey.pem;
access_log /var/log/nginx/bg3wiki-redirect.log combined if=$bg3log;
error_log /var/log/nginx/bg3wiki-error.log;
return 301 https://bg3.wiki$request_uri;
}
MediaWiki configuration
[edit]Finally, to make sure MediaWiki's PHP code sets the correct response headers for Nginx to actually cache the responses, and to make MediaWiki sent PURGE requests for edited pages, we need to set some options in LocalSettings.php:
# Allow caching via reverse proxy; in our case the Nginx FastCGI cache.
$wgUseCdn = true;
$wgCdnMaxAge = 3600;
# Make MediaWiki send PURGE requests to Nginx
# Note that this implicitly uses port 1080
$wgCdnServers = [ '127.0.0.1' ];
$wgInternalServer = 'http://bg3.wiki';