How to setup a multi-site, multi-lingual, multi-user Drupal blog

From ao2Wiki
Jump to: navigation, search

The guide is based on Debian GNU/Linux, but the Drupal parts apply to any system.


The blog we have in mind has some (basic?) requirements:

  • Single CMS installation hosts multiple sites (on different domain names).
  • Stable and search engine friendly URLs
  • Content types: Blog posts, Pages, News
  • Multilingual features:
    • Nodes: a post can be in any of the defined languages, or even language Neutral (even if I can't see a real use for this type of content yet).
      The front page will show only one translation in a group of node translations, with this rule: the version in current interface language is shown if available, then the version in the default language if available, one of the available versions (the original, for example) otherwise. Language neutral nodes are shown always, independently from the current language.
      Language neutral nodes, or nodes in language other than current one, might be presented differently to the user.
  • Feeds:
    • Global feed, with all content.
    • Per language feed, with content filtered per language.
    • Global comments feed
    • Per language comments feed
    • Per node and per tag feeds.
  • Common blog blocks: recent posts and comments, popular contents, monthly archive, tag clouds, links to contents related to a post.
  • Pingback and Trackback functionalities (low priority)
  • Multi-user capability comes with Drupal for free.

Reading the specs one could figure out that we are trying to express a Wordpress-on-steroids concept in terms of Drupal. It could be considered over-design, but it is interesting nonetheless.

This guide focuses only on building the infrastructure and not on content editing tools.

Install MySQL

aptitude install mysql-server

Install Apache webserver

Install apache2

aptitude install apache2

Add ServerName (you can use the IP address of the server if you plan to use name-based Virtual Hosts):

$EDITOR /etc/apache2/httpd.conf

Fine tune ports settings:

$EDITOR /etc/apache2/ports.conf

Some more config options can be seen here:

Enable needed modules

a2enmod whatever # Enable userdir, rewrite or what else you need

Install the php5 apache module, and also the mysqli and gd ones:

aptitude install libapache2-mod-php5 php5-mysql php5-gd

Apache VirtualHost

In order to have a single Drupal installation for multiple sites we can put the directory with Drupal code inside the global webserver DocumentRoot (e.g /home/www-data/drupal) and link this location inside site-local DocumentRoot:

# put Drupal in /home/www-data/drupal and then:
mkdir -p /home/www-data/
ln -s /home/www-data/drupal /home/www-data/

Setup the virtual host for our domain in /etc/apache2/sites-availabe/example.com_site

<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	DocumentRoot /home/www-data/
	<Directory /home/www-data/>
		Options -Indexes +FollowSymLinks MultiViews
		AllowOverride None
		Order allow,deny
		allow from all
	ErrorLog /var/log/apache2/
	# Possible values include: debug, info, notice, warn, error, crit,
	# alert, emerg.
	LogLevel warn
	CustomLog /var/log/apache2/ combined

then enable it

a2ensite example.com_site

and restart apache

invoke-rc.d apache2 restart

Install Drupal

This method has been inspired by

Get the desired revision from git:

cd /home/www-data/
git clone --branch 6.x
git reset --hard 6.26
git checkout 6.26
cd drupal/sites
mkdir -p all/modules && \
mkdir -p all/themes/{engines,custom,drupal-contrib} && \
mkdir -p default/modules && \
mkdir -p default/themes/{custom,drupal-contrib}

When a new version is released you can upgrade with these commands:

chmod u+w sites/default
git checkout 6.x
git pull
git reset --hard 6.27
git checkout 6.27
chmod u-w sites/default

like told here.

If you use the Italian hosting provider Aruba, follow this guide.

Create the DB for and a database user for it.

mysqladmin -u root -p CREATE drupal_example_com
mysql -u root -p
mysql> USE drupal_example_com;
       ON drupal_example_com.*
       TO 'drupal_db_username'@'localhost' IDENTIFIED BY 'drupal_db_password';
mysql> flush privileges;
mysql> quit

Prepare for installation

mkdir -p default/files
sudo chown www-data:www-data default/files
cp default/default.settings.php default/settings.php
chmod a+w default
chmod a+w default/settings.php

Access the page from the browser and insert the DB data specified above, you will also be asked to choose the Administrator username and password, which of course can be different from the DB account.

Remove write permission to some files:

chmod go-w default/
sudo chown www-data:www-data default/settings.php
chmod 600 default/settings.php

If needed edit default/settings.php and add what you want, for instance you may want to set the maintenance theme and some multi language variables, see

When you want to add another site, repeat the steps above replacing default, example_com and with your domain name.

Do some basic setup

  • Setting the maintainance theme:
    • Edit settings.php and set maintenance_theme
    • Add a drupal/sites/default/theme/custom/YOURTHEME/maintenance-page.tpl.php
  • Set site information Home » Administer » Site configuration » Site information
  • Create a non-admin account for content submission, so you won't use the admin user when posting content from a non-safe place.
  • Setup Home » Administer » User management » Permissions so that users can administer contents and menu also in maintenance mode:
    • enable administer site configuration under system module for authenticated users.
  • Install and enable your preferred themes and modules
  • Specify an email address to send update notifications to, check the Settings tab in
    Home » Administer » Reports » Available updates

Remember to set also permissions in menu and node modules, such as:

  • administer menu
  • create page content
  • edit any page content
  • upload files
  • etc...

Configure Drupal

Content Types and Input formats

By using the Better formats module, we can set filter types according to user rights, we decided to use Filtered HTML input format for all content created by anonymous users (mainly comments), and a more permissive input format for authenticated users, see:

Home › Administer › Site configuration › Input formats › Defaults

Another interesting option for authenticated users is htmlpurifier, a way to have clean code without too much restrictions.

Enable the PHP filter module, you will need it in future.

Multilanguage features

Install the i18n module and optionally the languageicons module.

To enable basic multilingual support:

  • Set language prefix (also for default language):
     Home » Administer » Site configuration » Languages » List » English (Edit)

    and add the en prefix

  • Add your own language (English will still be the default language):
     Home » Administer » Site configuration » Languages  » Add language

    You can import interface string translation at a later stage.

  • Set Language negotiation, I picked up Path prefix with language fallback (Check also here):
     Home » Administer » Site configuration » Languages » Configure
  • Hide Content translation links if you handle them in your theme template:
     Home » Administer » Site configuration » Languages » Multilingual system
  • Enable Multilingual Support for your content types:
    Home » Administer » Content management » Blog entry » Workflow settings

    While at it you can change also comments settings as you like.

You might also want to install the Preserve Language module, so to have the language switcher to only change the interface language.


In Drupal they say: “No matter what the question is, the answer is Views”.

Showing nodes, feeds for nodes and comments

To show site content on frontpage according to our goals, use the Select Translation module, Enable the frontpage view, add the filter provided by this module to the Page display only. Then edit Site information and set the new frontpage path in Site Inforations page; you may want to adjust your robots.txt as well as suggested here.

To have language dependent feeds edit the Feed display (you may want to rename it to Feed by language) of the frontpage view, add an Override Filter to nodes by Node translation: Language, and use Current Language and No language. You can now filter the items in the feed according to the language prefix. Change the path to feed.

To have a global feed with any content in it, add a new feed display to the frontpage view and set its path to all/feed with no language filters set.

Two notes about feeds:

  1. You may want to remove the Sort Criterion: Node Sticky, it should not matter for feed users if a node is sticky.
  2. You might want to make sure that item links in feeds are prefixed with the content language, and not with the current interface language. With a patch like this one it could be done:
    Index: views/modules/node/
    --- views.orig/modules/node/       2009-04-18 10:25:29.000000000 +0200
    +++ views/modules/node/    2009-04-18 10:33:08.000000000 +0200
    @@ -92,7 +92,8 @@
         $item = new stdClass();
         $item->title = $node->title;
    -    $item->link = url("node/$row->nid", array('absolute' => TRUE));
    +    $__langs = language_list();
    +    $item->link = url("node/$row->nid", array('absolute' => TRUE, 'language' => $__langs[$node->language]));
         // Allow modules to add additional item fields and/or modify $item
         $extra = node_invoke_nodeapi($node, 'rss item');

    However, this is not desirable if you think about it; we eventually decided to separate the interface language from the content language, so we let Drupal handle the interface language selection automatically as a URL the prefix.

In setting up feeds for comments, Views comes to the rescue once again: create a new Comment feeds view, add a Feed display to it (maybe changing its name to Feed by language), set its path to comments/feed, add a relationship with Comment: Node and add an Override Filter to comments by “Node translation: Language”, and use Current Language and No language. Add a sort criterion “Comment: Post date” desc.

To have a global feed with any comments in it, add a new feed display (call it Global feed) to the Comment Feeds view and set its path to all/comments/feed with no filters set.

NOTE: you may want to take a look at the Link display in views settings, if you need them to be well separated consider using different views for different comment types.

Now that feeds are set up we can add a block to list them:

<h3><?php print(t(Feeds)); ?></h3>
<ul class="feed-list">
<li><a href="<?php print base_path(); ?>all/feed" title="All posts">All posts</a></li>
<li><a href="<?php print base_path(); ?>en/feed" title="Posts in English">Posts in English</a></li>
<li><a href="<?php print base_path(); ?>it/feed" title="Articoli in italiano">Articoli in Italiano</a></li>
<h3><?php print(t(Comments)); ?></h3>
<ul class="feed-list">
<li><a href="<?php print base_path(); ?>all/comments/feed" title="Comments to all posts">Comments of all posts</a></li>
<li><a href="<?php print base_path(); ?>en/comments/feed" title="Comments to posts in English">Comments in English</a></li>
<li><a href="<?php print base_path(); ?>it/comments/feed" title="Commenti degli articoli in italiano">Commenti in Italiano</a></li>

If you want also a per-node feed to track comments of that particular feed you can do it this way: create another view named node_feeds (maybe cloning comments_recent and editing it? Or adding a Relationship with Nodes anyway) add a Feed display (call it Node comments Feed) to the node_feeds view, set its path to node/%/feed to use the argument (Node) Node: Nid. Set the title as %1 to get the actual node title in the feed display. Now every node has a local feed, if node 1 is in English you get the feed at en/node/1/feed. Attach the feed to the page display with something like this in your theme:

  if (!$node->teaser and $node->comment != COMMENT_NODE_DISABLED) {
    $url = url('node/'. $node->nid . '/feed');
    $title = t('Comments for ') . $node->title;
    print theme('feed_icon', $url, $title);
    drupal_add_feed($url, $title);


Add a vocabulary named Tags, in Multilingual options choose Localize terms. Terms are common for all languages, but their name and description may be localized., this way we can use a tag for multiple languages, and check the Tags item in Settings.

Coming to taxonomy feeds: we want a per-vocabulary feed (not very important) and a per-term feed, the latter is important because we can use it to be added to “planets” or to other thematic aggregation mechanism without polluting them with out of context material.

Enable the taxonomy_term view.

For tags cloud use the tagadelic module.

Search Engine Friendly URLs

To have Clean URLs

Configure Apache for Drupal Clean URLs

a2enmod rewrite

And add "AllowOverwrite All" to the virtualhost config file in /etc/apache2/sites-enabled/

Restart apache

invoke-rc.d apache2 restart

And set the right RewriteBase in Drupal site .htaccess if needed.

Enable Clean URLs in Drupal Administer » Site Configuration » Clean URLs.

Even More Friendly URLs

For Search Engine Friendly URLs in Drupal, install the Token and PathAuto modules, setup transliteration in i18n-ascii.txt as explained in Pathauto INSTALL.txt and configure alias patters in Administer -> URLs aliases -> Automated alias settings

If you have the Path Redirect module you can choose Create a new alias. Redirect from old alias. as an Update Action in General Settings.

Node Path Settings for blogs, something like:


for stories (or news):


for pages:


for taxonomies:


Check Bulk Update for any content type and save. If you don't set language-specific patterns this patch would be handy to have alias generated for all languages:


Download the gravatar module and check

Home » Administer » Site building » Themes » Themes

and User settings to be sure you enabled user pictures.

Check also the “use gravatar” permission to grant access to the gravatar module functionality to anonymous and authenticated users.

Related or Similar content

Use the similarterms module.

Trackbacks and pingbacks

There are Drupal modules for trackback and pingback functionality, but they both have issues.

The trackback modules represents trackbacks as content separate from comments, and I like that very much, but it lacks automatic pingback functionality.

The pingback module offers the automatinc pingback as per pingback specification, but it it not very solid, you can easily break titles with special characters (see here) and it represents pingbacks as comments, which IMHO can interrupt the discussion between humans.

We should really complete and port the pingback functionality to the trackback module and be happy with it.

SEO checklist

Use the SEO checklist module to have a guide about how to improve your position in search engines, I recommend particularly the Meta tags and Page Title modules.