MediaWiki on Nginx

This is an old post. It may contain broken links and outdated information.

I host a small Minecraft server with maybe a couple dozen players total, and for the past several months we’ve been using a wiki to catalog our achievements. I started with DokuWiki, a flat-file wiki, because I was reluctant to weigh down a webserver with a database just to host a small wiki, but now that Bigdinosaur.org is hosting some more things and needs a database, it seemed time to switch over to MediaWiki, the wiki engine that powers Wikipedia and the whole MedkaWiki foundation network of sites.

There are lots of MediaWiki-on-Nginx guides out there, but I didn’t find anything approaching the completeness of the much more common MediaWiki-on-Apache guides. The configuration I settled on was a mix of things from around the web, including the MediaWiki site and the Nginx Wiki, and my own ideas, with an eye toward closing off access to as much of the internals as possible and pulling the main configuration components out of the web root.

Prerequisites

We’re not going to go through how to set up a MySQL or postgresql database for MediaWiki. The official documentation can take you through that.

We’re also not going to go through PHP installation and configuration. However, this guide is going to assume that you’ve got PHP5-FPM installed and working, along with Alternative PHP Cache (APC). Having memcached and the PHP5-memcache extension installed to handle PHP session caching wouldn’t hurt, either.

MediaWiki will happily use ImageMagick for image resizing and manipulation if you’ve installed it, so that’s a good idea.

MediaWiki prefers to live in and be served from a subdirectory off of your web root rather than directly. The reasons are long and varied and are [described here](https://www. mediawiki.org/wiki/Manual:Wiki_in_site_root_directory), and rather than deal with the additional complexities of serving from root, I’ve got mine in a subdirectory named wiki/, and that’s going to be the assumed location for the rest of this guide.

Download the latest version of MediaWiki and decompress it directly into your web root. The files are contained within a subdirectory inside the archive, so the easiest thing to do is to use mv to rename the decompressed target directory to the name of your choice.

Configuring Nginx

Head to your Nginx directory. We’re going to create a new host (or vhost, if you favor the Apache terminology) for MediaWiki, so assuming you’re following an Apache-like configuration scheme with site definitions stored in a sites-available directory and symlinked to a sites-enabled directory (which is then included into a main nginx.conf file), you’d want to navigate to sites-available and crete a new file named wiki. I’m going to put the entire file below, then walk through it piece by piece.

server {
    server_name wiki.bigdinosaur.org;
    root /var/www-wiki/wiki;
    index index.html index.php;
    autoindex off;
    include conf.sites/wiki-both.conf;

#	Uncomment after installation!
#	location / {
#		index index.php5;
#		rewrite ^/([^?]*)(?:\?(.*))? /index.php5?title=$1&$2 last;
#	}

    location ~ \.php5?$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors on;
    }

    location ~ \.php?$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors on;
    }
}

server {
    listen 443 ssl;
    server_name wiki.bigdinosaur.org;
    root /var/www-wiki/wiki;
    index index.php;
    autoindex off;
    include conf.sites/wiki-both.conf;

    ssl on;
    ssl_certificate /path/to/your/ssl/cert.crt;
    ssl_certificate_key /path/to/your/ssl/private.key; 
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!MD5:
        !aNULL:!EDH:!AESGCM;
    ssl_prefer_server_ciphers on;

#	Uncomment after installation!
#	location / {
#		index index.php5;
#		rewrite ^/([^?]*)(?:\?(.*))? /index.php5?title=$1&$2 last;
#	}

    location ~ \.php5?$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param HTTPS on;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors on; 
    }

    location ~ \.php?$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param HTTPS on;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors on;
    }
}

The first thing to note is that this is a dual configuration, covering both HTTP and HTTPS access in one. If you’re not using HTTPS on your site, you can omit the entire second server block. The second thing to note is that we’re including a separate config file with include conf.sites/wiki-both.conf;—this file contains common locations and configuration options for both the HTTP and HTTPS versions of the site, so I don’t have to maintain them separately in each server block.

Note also that there are two location directives that are commented out. These contain the rewrite rules which will prettify the URLs and make them search-engine friendly. We need these disabled at first because MediaWiki needs to be told how to understand pretty URLs before they’ll work. Once MediaWiki has been set up, we’ll come back in and un-comment these lines.

HTTP

Now the breakdown:

server {
    server_name wiki.bigdinosaur.org;
    root /var/www-wiki/wiki;
    index index.php;
    autoindex off;
    include conf.sites/wiki-both.conf;

This is the base server configuration block. It sets the hostname for Nginx to listen to so that it knows to use this config file for all requests to that hostname. The root directive sets the web root for this host; we’re setting it to the wiki/ subdirectory where our MediaWiki install lives. index sets the default file that will be served up when the directory is accessed without a specific file in mind, which we want set to the index.php file. Autoindex off disables the serving of directory listings for directories which lack index files; turning this off is always a good idea to ensure your site’s internals are kept hidden in case you screw something up. Finally, the include statement calls another config file, which we’ll walk through shortly.

#	location / {
#		index index.php5;
#		rewrite ^/([^?]*)(?:\?(.*))? /index.php5?title=$1&$2 last;
#	}

As noted above, this is the URL rewrite rule to hide all the ugly PHP arguments in the address bad. It’s commented out to start with because we need to set an option in MediaWiki’s config files before it will work. We’ll come back and uncomment this in a bit.

    location ~ \.php5?$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors on;
    }

    location ~ \.php?$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass php5-fpm-sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors on;
    }

These are the PHP handler lines, which tell Nginx what to do with all files ending in php and php5. The first try_files line is particularly important, as it works around a known configuration pitfall with Nginx and php which allows potentially malicious files to be passed to the PHP interpreter and executed. The include statement pulls in the standard list of FastCGI parameters that come prepackaged with Nginx; after that, the fastcgi_pass line tells Nginx to use a Unix socket to communicate with php-fpm (which is what you should be doing if php-fpm lives on the same server—don’t use a TCP port unless you’re passing PHP traffic to a separate PHP server), and the last two lines add a couple of additional parameters that aren’t in the default include file.

The php5-fpm-sock variable above points to an upstream location defined in /etc/nginx/conf.d/php5-fpm.conf:

upstream php5-fpm-sock {
    server unix:/var/run/php5-fpm.soc;
}

Defining it there and referencing it in different sites lets you make changes to it as needed without having to touch all the site definitions that use it.

HTTPS differences

The HTTPS section is mostly the same, except that it includes some additional lines. The server block is modified slightly to add a listening port; there is an SSL-specific block of code to turn on HTTPS and specify all the necessary parameters to make it work, incuding where your certificate and key are located; and finally each of the PHP handler locations have a parameter added (fastcgi_param HTTPS on;) to deal with being run under HTTPS.

The separate included config file

Both HTTP and HTTPS configs reference another file, conf.sites/wiki-both.conf. This file contains configuration directives that need to be included in both server blocks; I find it convenient to keep them in a separate file and include them, rather than redundantly typing them out in both the HTTP and HTTP sections. If you’re only using HTTP, feel free to forgo keeping these directives in a separate file and instead just toss them under the HTTP server block.

#	Common deny, drop, or internal locations

#	Exclude all access from the cache directory
    location ^~ /cache/ { deny all; }

#	Prevent access to any files starting with a dot, like .htaccess
#	or text editor temp files
    location ~ /\. { access_log off; log_not_found off; deny all; }

#	Prevent access to any files starting with a $ (usually temp files)
    location ~ ~$ { access_log off; log_not_found off; deny all; }

#	Do not log access to robots.txt, to keep the logs cleaner
    location = /robots.txt { access_log off; log_not_found off; }

#	Do not log access to the favicon, to keep the logs cleaner
    location = /favicon.ico { access_log off; log_not_found off; }

#	Keep images and CSS around in browser cache for as long as possible,
#	to cut down on server load
    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
        try_files $uri /index.php;
        expires max;
        log_not_found off;
    }

#	Mark all of these directories as "internal", which means that they cannot
#	be explicitly accessed by clients. However, the web server can still use
#	and serve the files inside of them. This keeps people from poking around
#	in the wiki's internals.
    location ^~ /bin/ { internal; }
    location ^~ /docs/ { internal; }
    location ^~ /extensions/ { internal; }
    location ^~ /includes/ { internal; }
    location ^~ /maintenance/ { internal; }
#	location ^~ /mw-config/ { internal; } #Uncomment after installation
    location ^~ /resources/ { internal; }
    location ^~ /serialized/ { internal; }
    location ^~ /tests/ { internal; }

#	Force potentially-malicious files in the /images directory to be served
#	with a text/plain mime type, to prevent them from being executed by
#	the PHP handler
    location ~* ^/images/.*.(html|htm|shtml|php)$ {
        types { }
        default_type text/plain;
    }

#	Redirect all requests for unknown URLs out of images and back to the
#	root index.php file
    location ^~ /images/ {
        try_files $uri /index.php;
    }

Most of these are easy to understand, but I want to comment on a few. The large block of internal directives takes the place of a lot of recommended .htaccess trickery, since Nginx doesn’t use htaccess files. Internal is an Nginx-specific directive that prevents access to a location from any requests that don’t come from the web server itself; consequently, files inside an internal-marked location are inaccessible to users, but accessible to the web server. Since none of those locations above contain anything that the user needs to be directly requesting, we can mark them all as internal locations. One exception is the mw-config directory, which we need to be able to access during setup (but which we can hide after setup is complete).

The images directory is the default upload location for MediaWiki, which means it warrants special attention because external users can put files there. MediaWiki contains some inbuilt protection to prevent anything except image files from being uploaded, but it does’t hurt to have the web server provide some additional help. Here, we force any potentially-executable files to be served with a text/plain mime type, so that it’s impossible for them to be actually run. This is an adaptation of another .htaccess-driven security suggestion on the main MediaWiki documentation site.

After this has all been added, symlink the sites-available/wiki.conf file into the sites-enabled directory with this command:

$ sudo ln -s /etc/nginx/sites-available/wiki /etc/nginx/sites-enabled/wiki

Then, go ahead make it all live by reloading Nginx’s configuration with a quick /etc/init.d/nginx reload.

PHP specifics

I’m not going to dive into the PHP side of the configuration, except to say that you’ll want to make sure you’re following all the recommendations in the MediaWiki security manual, particularly the bit about disabling register_globals unless you really, really, really need it.

Configuring MediaWiki

Now that our Nginx configuration is live, we can follow the manual for a bit. Navigate in a web browser to the MediaWiki install location and you’ll see a link to run the setup script. Follow the directions and plug in the appropriate options, and when the script is complete it will generate a file named LocalSettings.php for you to download.

This file contains the configuration of your MediaWiki installation. By default it also contains sensitive information about your database, including the database user and password, so it’s best to make sure this file is properly protected and located somewhere outside of the web root. MediaWiki requires a LocalSettings.php file in its root directory to run, so we’ll set up a dummy file there and move the real file somewhere safe.

Since we’re serving our wiki in this example out of /var/www-wiki/wiki/, place the real file at /var/www-wiki/ (one level above the web root). Then, create a new LocalSettings.php file in the wiki directory, and edit it so that it looks like this:

<?php
# Protect against web entry
if ( !defined( 'MEDIAWIKI' ) ) {
    exit;
     }

     include("/var/www-wiki/OrigLocalSettings.php");

(Yes, the <?php tag should be left open.)

Then, rename the LocalSettings.php file level up to OrigLocalSettings.php. Per the documentation, it’s also a good idea to chmod the file to 600 so that only the owner (which should be the Nginx service account) can access it, since it has a database user name and password inside of it.

Additional MediaWiki Settings

There are a few additions we need to make to the OrigLocalSettings.php file; specifically, we want to ensure object caching with APC is enabled and we want to enable pretty URL rewriting.

For the ACP caching bit, add or modify the following line:

$wgMainCacheType    = CACHE_ACCEL;

The URL rewrite rules require four lines to be added or modified:

$wgScriptPath       = "";
$wgArticlePath      = "/wiki/$1";
$wgScriptExtension  = ".php";
$wgUsePathInfo      = true;

This modification sets the script path to be the root directory and appends /wiki/ onto article pages. This will mesh with the rewrite rules we’ve written.

Uncommenting URL rewrite and security rules

The last step in all of this is to uncomment the rewrite rules and the single internal location referenced a few steps back. As a reminder, the rewrite rule is in /etc/ngins/sites-available/wiki.conf and looks like this:

    location / {
        index index.php5;
        rewrite ^/([^?]*)(?:\?(.*))? /index.php5?title=$1&$2 last;
}  

The security rule is in /etc/nginx/conf.sites/wiki-both.conf:

    location ^~ /mw-config/ { internal; } #Uncomment after installation

Restart or reload Nginx to make these changes active. You should now be able to visit your wiki and see the main page!

Wrapping up

This guide will get you a functional and secure installation of MediaWiki under Nginx, which you can then proceed to customze to taste. There are lots of other MediaWiki configuration changes you can make to get your wiki set up the way you want it. You can change the Creative Commons license for the site, or modify the default group permissions, or change e-mail settings, or switch the site’s theme, or any number of other things that are beyond the scope of this guide. If you’re interested in the modifications that have been made to the Bigdinosaur.org Minecraft wiki, then send me an email and I’ll happily discuss them at length.

If it looks like I’ve missed anything or if anyone has any suggestions on how to improve this article, please comment below—I’d love to hear about any improvements that could be made!

Addendum for PHP 5.3.9 and higher

As of mid-2012 I’m upgrading most of my boxes to PHP 5.3.9 and 5.3.10, and there is an additional setting that might be necessary in order to make MediaWiki not toss “Access denied” errors. Many of the php files in MediaWiki’s main distribution end with the extension “php5” instead of just “php”, and so php-fpm must be told explicitly that those files are okay to process.

To do this, edit your php-fpm.conf file and add the following at the bottom:

; Added to ensure php, html, and php5 files can be processed without error
security.limit_extensions = .php .html .php5

Then restart php-fpm and you’ll be all set.