It's better when it's simple

User Tools

Site Tools


Symlink farming with DokuWiki

In a farm, one single copy of wiki engine (the farmer) is used to run several individual wikis (the animals). For a generic introduction, and for different approaches to set up a DokuWiki farm, see farm.

This page describes a particularly simple approach, using symbolic links from the individual animals to the farmer.


This approach works since DokuWiki version 2009-02-14.
:-D Updated 2010/10/18, working!
:-D Partially updated 2011/05/19, still working!
:-D Still working as of 2017-02-19e
:-D Still working as of 2020-07-29 “Hogfather”

Features of this approach

  • Quite simple and works stable
  • Only the directories conf/ & data/ are copied, all other files are referenced through symlinks (only 332KB initial size per animal)
  • You have to upgrade only one DokuWiki instance and all templates and plugins are available for all instances.
  • Individual configuration per animal, in particular enabling and disabling plugins individually (in contrast to redirect farm).
  • Animals can be located in an arbitrary folder (in contrast to redirect farm).
  • Virtual host support

As the name implies, this approach relies on symbolic links; if you cannot use them (for example due to permission restrictions), use one of the alternative approaches.

Installation of the Farmer Wiki

This is the only complete instance of DokuWiki.

  • Run all commands in the following as root (under Debian sudo -i).
  • Adapt $farmerpath.
  • Adapt the link to the DokuWiki archive.
  • Adapt the web server username (www-data).
# path to the farmer (ends without /):
cd `dirname $farmerpath`
# adapt the link for an other version:
tar -xzvf dokuwiki-${farmerversion}.tgz
# rename the folder from dokuwiki-YYYY-MM-DD to your farmer folder name:
mv dokuwiki-${farmerversion}/ $farmerpath
cd $farmerpath
# give rights to the web server user
chown -R www-data:www-data conf/ data/
cat > inc/preload.php <<'EOF'
$dir = dirname($_SERVER['SCRIPT_FILENAME']);
if( is_dir($dir . '/conf') && !preg_match ('#lib/(exe|plugin.*)#', $dir) )
               define('DOKU_CONF', $dir . '/conf/');
else {
     $dir = preg_replace('#lib/(exe|plugin.*)#', 'conf/', $dir);
        define('DOKU_CONF', $dir);

Now configure the farmer (conf/) to match your needs (template, authentication, users, ACLs, wiki options, etc). Install all plugins and templates you will want to use inside your farmer, as they are common to all wikis installation of your farm. The configurations of the farmer-wiki will be copied to the new animal-wikis.

Creation of an Animal Wiki

For each animal wiki run the following as root.

  • Adapt $farmerpath (same as above).
  • Adapt $animalpath.
# path to the farmer (ends without /):
# path to the animal, that should be created (ends without /):
mkdir -p $animalpath && cd $animalpath
cp -pr $farmerpath/{data,conf} .
# reference some additional files (msg for correct handling of upgrade notifications and the standard config files)
ln -sf $farmerpath/conf/{dokuwiki.php,{acl.auth,local,users.auth}.php.dist} $animalpath/conf/.
# www-data is the web server user:
chown -R www-data:www-data data/ conf/
ln -s $farmerpath/{index.php,doku.php,feed.php,inc,lib} .
cat >> conf/local.php <<EOF
\$conf['savedir'] = '$animalpath/data';

Now configure the animal1 (animal1/conf/) to match your needs (themes, authentication, users, ACLs, wiki options, etc). Apply this procedure to create as many animal wikis (animal2, animal3, and so on) you want.

If you want to ease the configuration of an animal, you can also copy over the install.php script from the top level of your DokuWiki installation to your freshly created animal directory.

Now the animal1 looks like this:

|-- conf
|   |-- acl.auth.php
|   |-- acl.auth.php.dist -> $farmerpath/conf/acl.auth.php.dist
|   |-- acronyms.conf
|   |-- dokuwiki.php -> $farmerpath/conf/dokuwiki.php
|   |-- entities.conf
|   |-- interwiki.conf
|   |-- local.php
|   |-- local.php.dist -> $farmerpath/conf/local.php.dist
|   |-- mediameta.php
|   |-- mime.conf
|   |-- mysql.conf.php.example
|   |-- smileys.conf
|   |-- users.auth.php
|   |-- users.auth.php.dist -> $farmerpath/conf/users.auth.php.dist
|   |-- wordblock.conf
|   `-- words.aspell.dist
|-- data
|   |-- ...
|-- doku.php -> $farmerpath/doku.php
|-- index.php -> $farmerpath/doku.php
|-- feed.php -> $farmerpath/feed.php
|-- inc -> $farmerpath/inc
`-- lib -> $farmerpath/lib

Configuration of the Apache Virtual Host for the Animal and mod_rewrite

First, we enable URL rewriting with .htaccess file.

  • Adapt $farmerpath (same as above).
  • Adapt $animalpath (same as above).
  • Adapt $animalbasedir.
# This is the path after the domain name (starts with and ends without /):
# Create the .htaccess file and set the RewriteBase
cat > $animalpath/.htaccess <<EOF
RewriteEngine on
RewriteBase $animalbasedir
cat >> $animalpath/.htaccess <<'EOF'
RewriteRule ^_media/(.*)              lib/exe/fetch.php?media=$1  [QSA,L]
RewriteRule ^_detail/(.*)             lib/exe/detail.php?media=$1  [QSA,L]
RewriteRule ^_export/([^/]+)/(.*)     doku.php?do=export_$1&id=$2  [QSA,L]
RewriteRule ^$                        doku.php  [L]
RewriteCond %{REQUEST_FILENAME}       !-f
RewriteCond %{REQUEST_FILENAME}       !-d
RewriteRule (.*)                      doku.php?id=$1  [QSA,L]
RewriteRule ^index.php$               doku.php

Now we configure the virtual host for the animal.

  • Adapt $animalpath (same as above).
  • Adapt $animalhostname.
cd /etc/apache2/sites-available
cat > $animalhostname <<EOF
<VirtualHost *:80>
    ServerName $animalhostname
    DocumentRoot $animalpath/
    <Directory $animalpath>
        Options FollowSymLinks
        AllowOverride All
cd ../sites-enabled && ln -s ../sites-available/$animalhostname
# reload config to enable the virtual host
/etc/init.d/apache2 reload

Enable userewrite and set basedir appropriately in the conf/local.php.

  • Adapt $animalpath (same as above).
  • Adapt $animalbasedir (same as above).
cd $animalpath
cat >> conf/local.php <<EOF
\$conf['basedir'] = '$animalbasedir/';
\$conf['userewrite'] = '1';

Plugin support

One easy way to deal with plugins inside each animal wiki is to completely disable the use of the plugin manager by creating an empty file lib/plugins/plugin/disabled in the farmer wiki.

touch dokuwiki-farmer/lib/plugins/plugin/disabled

Then, all plugins installed and enabled in the farmer wiki will also be available and enabled in all the animal wikis.

Disabling Plugins for each Animal individually

With this patch, the plugins are disabled based on the existence of the file conf/plugins/plugin.{$pluginname}.disabled. Note, that all previously disabled plugins get enabled by application of the patch, since the former mechanism is ignored.

  • Adapt $farmerpath (same as above).
cd $farmerpath/inc
cp -a plugincontroller.class.php plugincontroller.class.php.dist
patch -p0 plugincontroller.class.php <<'EOF'
--- plugincontroller.class.php.dist	2011-05-08 21:01:20.000000000 +0200
+++ plugincontroller.class.php	2011-05-19 22:30:46.000000000 +0200
@@ -116,14 +116,14 @@
     function enable($plugin) {
         if (array_search($plugin, $this->list_disabled) !== false) {
-            return @unlink(DOKU_PLUGIN.$plugin.'/disabled');
+            return @unlink(DOKU_CONF.'plugin.'.$plugin.'.disabled');
         return false;
     function disable($plugin) {
         if (array_search($plugin, $this->list_enabled) !== false) {
-            return @touch(DOKU_PLUGIN.$plugin.'/disabled');
+            return @touch(DOKU_CONF.'plugin.'.$plugin.'.disabled');
         return false;
@@ -138,12 +138,12 @@
             while (false !== ($plugin = readdir($dh))) {
                 if ($plugin[0] == '.') continue;               // skip hidden entries
                 if (is_file(DOKU_PLUGIN.$plugin)) continue;    // skip files, we're only interested in directories
                 if (substr($plugin,-9) == '.disabled') {
                     // the plugin was disabled by rc2009-01-26
                     // disabling mechanism was changed back very soon again
                     // to keep everything simple we just skip the plugin completely
-                }elseif(@file_exists(DOKU_PLUGIN.$plugin.'/disabled') ||
+                }elseif(@file_exists(DOKU_CONF.'plugin.'.$plugin.'.disabled') ||
+                        @file_exists(DOKU_PLUGIN.$plugin.'/disabled') ||
                         ($plugin === 'plugin' && isset($conf['pluginmanager']) &&
                     $this->list_disabled[] = $plugin;

Old patch (about 2010-11-07)

patch -p0 plugincontroller.class.php <<'EOF'
--- plugincontroller.class.php.dist	2010-01-17 11:35:46.000000000 +0100
+++ plugincontroller.class.php	2010-04-01 14:08:21.000000000 +0200
@@ -104 +104 @@
-            return @unlink(DOKU_PLUGIN.$plugin.'/disabled');
+            return @unlink(DOKU_CONF.'plugin.'.$plugin.'.disabled');
@@ -111 +111 @@
-            return @touch(DOKU_PLUGIN.$plugin.'/disabled');
+            return @touch(DOKU_CONF.'plugin.'.$plugin.'.disabled');
@@ -125 +124,0 @@
@@ -130 +129 @@
-                }elseif(@file_exists(DOKU_PLUGIN.$plugin.'/disabled')){
+                }elseif(@file_exists(DOKU_CONF.'plugin.'.$plugin.'.disabled')){

Upgrading the farm

To upgrade the farm, upgrade the farmer the same way you usually upgrade a single wiki installation.

I now adapted the instructions to create an animal to reference some additional files from the $farmerpath/conf/ directory. This is needed to ease the upgrading to rc2010-10-07.
# for each animal reference some of the default configuration files (will be overwritten in the animals)
for animalpath in /var/www/{,,}; do
ln -sf $farmerpath/conf/{dokuwiki.php,{acl.auth,local,users.auth}.php.dist} $animalpath/conf/.
# invalidate cache
touch $animalpath/conf/local.php
# remove file (not used anymore)
rm $animalpath/conf/msg
# alternatively you can find each folder containing a conf/ directory using this:
for animalconf in /var/www/*/conf; do echo $animalconf; done
# if this looks ok (dokuwiki-farm/conf/* cant be overwritten anyway by itself), do
for animalconf in /var/www/*/conf; do
ln -sf $farmerpath/conf/{dokuwiki.php,{acl.auth,local,users.auth}.php.dist} $animalconf/.
# invalidate cache
touch $animalconf/local.php
# remove file (not used anymore)
rm $animalconf/conf/msg

To invalidate the cache after upgrading to rc2010-10-07, you need to touch conf/dokuwiki.php (in the dokuwiki-farm, since it is now referenced in each animal).

Notes and comments

:!: Beware of conflicting plugins, which would affect all animals: if you install or upgrade a new plugin, test it!

Fixed now an issue with sitemaps beeing created only in the farmers path. See the patch of the lib/exe/indexer.php in the farmer installation script. — Mirko 2010/09/05 15:36

Updated instructions to the newest release candidate (rc2010-10-07 “lazy sunday”). Now dokuwiki has even better out-of-the-box support for this approach. I also moved old stuff to the archive, to not confuse the reader. — Mirko 2010/10/18 11:15

danitxu, 27-09-2011 :!: Beware: preload.php must check if $dir . '/conf' is related to a plugin. The correct code is:

$dir = dirname($_SERVER['SCRIPT_FILENAME']);
if( is_dir($dir . '/conf') && !preg_match ('#lib/(exe|plugin.*)#', $dir) )
               define('DOKU_CONF', $dir . '/conf/');
else {
     $dir = preg_replace('#lib/(exe|plugin.*)#', 'conf/', $dir);
        define('DOKU_CONF', $dir);

As an example, if you try “tag” plugin, if not included preg_match it throws an error when trying to rebuild tagindex

tips/symlink_farm.txt · Last modified: 2021-04-30 20:44 by chrisz

Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 4.0 International
CC Attribution-Share Alike 4.0 International Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki