MediaWiki with Nginx
I've recently needed to move a decade-old MediaWiki installation. It's been continually updated throughout its life, so no problem there, but it had to be shifted from shared Apache hosting to a dedicated server using Nginx. Plus, I had a few configuration requirements to make updates a touch easier and URLs a bit cleaner.
Backup
Nothing special to be done here, I just backed up the files and the database:
mysqldump -u mediawiki -p --no-tablespaces mediawiki > mediawiki.sql
tar zcvf mediawiki-archive.tgz mediawiki-root mediawiki.sql
The --no-tablespaces option was required to get the backup out of the shared hosting.
Setup Speed Run
Starting with a blank-ish Debian Trixie server, create a user:
sudo adduser --system --group --home /home/mediawiki mediawiki
Throw all the packages at the wall:
sudo apt update
sudo apt install ufw nginx nginx-extras mariadb-server php-fpm php-mysql php-xml php-mbstring php-intl php-apcu php-cli php-gd composer imagemagick
Do the firewall shuffle:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
sudo ufw status verbose
Set up a blank database:
sudo mysql
CREATE DATABASE mediawiki;
CREATE USER 'mediawiki'@'localhost' IDENTIFIED BY 'passpasspassword';
GRANT ALL PRIVILEGES ON mediawiki.* TO 'mediawiki'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Then I had some SSL certificates issued. You'll need to tread your own path a little on that, but I'm a convert to using the automatic DNS integrations provided by acme.sh. Previously I had to faff a little with Certbot and the .well-known directory. Using DNS is far easier, and acme.sh schedules itself to renew. Ultimately it looked like this:
acme.sh --issue --dns YOURDNSPROVIDERHERE -d example.com -d *.example.com
acme.sh --install-cert -d example.com -d *.example.com --key-file /etc/nginx/ssl/example.com.key --fullchain-file /etc/nginx/ssl/example.com.fullchain.pem --reloadcmd "systemctl reload nginx"
Finally, I like to have a separate PHP-FPM pool running as the mediawiki user to simplify permissions and provide a little more isolation. That's done with an extra /etc/php/8.4/fpm/pool.d/mediawiki.conf file containing:
[mediawiki]
user = mediawiki
group = mediawiki
listen = /run/php/php8.4-fpm-mediawiki.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
sudo systemctl restart php8.4-fpm
We'll configure the site to use that in a minute, once we've got the files and database in place.
Import
It all starts simply enough by untarring everything and populating the database:
sudo mysql mediawiki < mediawiki.sql
Then we deviate just a little. For my MediaWiki installations I like to follow this sort of file path for the installation location and web root:
/home/mediawiki/wiki.example.com/mediawiki-1.45.1
To prevent silly mistakes, I keep MediaWiki's LocalSettings.php and the images directory one level above the web root in /home/mediawiki/wiki.example.com/. This means I can grab the new version when it's released, untar it, copy over any custom extensions from the previous version, update the web root in the Nginx config and simply link the LocalSettings.php file with:
ln -s /home/mediawiki/wiki.example.com/LocalSettings.php /home/mediawiki/wiki.example.com/mediawiki-1.XX.X/LocalSettings.php
To make the images directory work does require a little fettling in both LocalSettings.php and the Nginx configuration for the site. For LocalSettings.php it's just:
$wgUploadDirectory = "/home/mediawiki/wiki.example.com/images";
$wgUploadPath = "/images";
In the Nginx site config it's this:
location ^~ /images/ {
alias /home/mediawiki/wiki.example.com/images/;
expires 30d;
add_header Cache-Control "public, immutable";
}
I'll include the whole Nginx configuration file towards the end, as order is important for URL processing.
Finally, I switched from the traditional wiki.example.com/w/Article scheme that had been used forever, to the simpler wiki.example.com/Article and provided a redirect for old links on external services. This first has to be configured in LocalSettings.php like this:
$wgScriptPath = "";
$wgArticlePath = "/$1";
Update links in the database with this:
php mediawiki-1.45.1/maintenance/run.php refreshLink
Then this works the magic in the Nginx config:
location ~ ^/w/(.*)$ {
return 301 /$1;
}
Here's the complete Nginx site configuration file:
server {
listen 80;
server_name wiki.example.com;
return 301 https://$server_name$request_uri;
}
server {
http2 on;
listen 443 ssl;
server_name wiki.example.com;
root /home/mediawiki/wiki.example.com/mediawiki-1.45.1;
index index.php;
ssl_certificate /etc/nginx/ssl/example.com.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
client_max_body_size 100M;
# Redirect old /w/ URLs to clean URLs
location ~ ^/w/(.*)$ {
return 301 /$1;
}
# Serve /images/ from outside MediaWiki
location ^~ /images/ {
alias /home/mediawiki/wiki.example.com/images/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Handle wiki pages with namespaces (anything with a colon)
location ~ : {
rewrite ^/?(.*)$ /index.php?title=$1&$args last;
}
location / {
try_files $uri @rewrite;
}
location @rewrite {
rewrite ^/?(.*)$ /index.php?title=$1&$args;
}
location ^~ /maintenance/ {
return 403;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.4-fpm-mediawiki.sock;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
location ~ /\.ht {
deny all;
}
}
Updating
With that little bit of abstraction it's fairly simple. Download the new tarball, extract it next to the currently running version, then link that settings file in:
ln -s /home/mediawiki/wiki.example.com/LocalSettings.php /home/mediawiki/wiki.example.com/mediawiki-1.XX.X/LocalSettings.php
Then the main trick is to copy over, check and update the custom extensions. I usually do this by reminding myself what's installed at the bottom of the LocalSettings.php file, hopping into each extension's directory and issuing a git pull, sometimes followed by:
composer install --no-dev
On one occasion I found it necessary to update components for MediaWiki itself using composer too. It fixed some errors I was seeing, likely due to using PHP 8.4, which is not-quite-supported-yet.
Once you're happy it's all done, back up the database, then issue:
php mediawiki-1.XX.X/maintenance/run.php update
Then update the root path version in the Nginx config and reload it:
sudo systemctl reload nginx
Success, I'm sure. :)