I recently had to set up a multi-site instance of Drupal from scratch; and now, I have to do so again. I recall it being fairly painful, due to somewhat scattered and/or out-of-date documentation. Therefore I have decided to document my procedures in excruciating detail, for my own future use and (hopefully) to help out others.
Preamble
I will only be discussing Linux, Apache and MySQL, because those are what I have access to. The steps below may vary if you are setting it up on Windows/IIS, or Linux/nginx, or with a different database backend. If you are familiar with those and would care to elaborate in a response, please do so.
This assumes that you have command-line access to the server that you’re working on, and that you are moderately comfortable using a Linux command prompt. It also assumes that you have root (or sudo) access to the server. It may be possible to finish this without root, but it’s considerably easier with it. I have prefaced any command that generally requires root access with “sudo”. If you are logged in directly as the root user (which is not generally recommended these days), you can omit the “sudo” portion of the command.
Apache Setup
Ensure you have a working copy of Apache installed on your computer. A complete guide to setting up Apache is well beyond the scope of this documentation. Once it is up and running, you will need to gather some information about the apache process: its user name, and group memberships (if any). These will be used later in configuring the permissions that Drupal needs to access files in its directories.
To find out what user name your instance of Apache uses, you can use the following command:
ps aux | egrep '(apache|httpd)'This will only work if Apache is currently running, as it searches the list of active processes for two common names for the Apache binary (“apache” for Debian- and Ubuntu-based distros, “httpd” for Red Hat and similar). It will display a list of results, something like this:
apache 36434 0.0 0.3 603032 14108 ? S 16:51 0:00 /usr/sbin/httpd -DFOREGROUNDThere may be many entries; each one represents a different Apache thread, but unless your server has a truly exotic configuration, they will still be the same instance. The word in the first column is what we are interested in; it’s Apache’s user name. In my case, I’m installing on a Red Hat system, and the user name is “apache”. On an Ubuntu system, the default Apache user name is “www-data”.
Next, find out what groups your apache user belongs to using this command:
groups apacheThis should yield something like this:
apache : apacheIndicating that the user “apache” is a member of its own group, “apache”, and no others. On Ubuntu, this is likely to be “www-data” for both the user and group.
If you are content with using the default group name, you do not need to do anything further. I have opted to make the Apache user a member of another group, called “web”, thus:
sudo usermod -a -G web apacheThis command adds the apache user to the web group (which I had already created). This bit of configuration has nothing to do with Drupal as such, and is completely optional. I use it because it makes it easier to set up permissions for some of the other developers at my workplace who will be sharing this server with me.
Regardless of whether you are using the default apache group or a custom one like mine, note down the name of the group that Apache belongs to.
Finally, make sure that you have the following line in the virtual host definition for your site:
AllowOverride AllThis will allow Apache to use the .htaccess file included with Drupal, which plays an important part in core functions like clean URLs. In my case, it was located in /etc/httpd/conf/httpd.conf, in a section for “/var/www/html”, which is the default path for the Apache root folder on Red Hat. Under Ubuntu, it’s likely to be located in /etc/apache2/sites-enabled, and there will probably be two .conf files that need to have this change made (one for regular HTTP, one for HTTPS).
If you changed your Apache’s group memberships or the host configuration, be sure to restart your copy of Apache to pick up those changes. For me, that was done with:
sudo service httpd restartOn Ubuntu that would be the same, only with apache2 instead of httpd. There are a variety of different commands for restarting Apache which vary from one flavor of Linux to the next. If you are in doubt, or if the above does not work, search Google for “apache restart” and your specific Linux distribution.
Folder setup
Create a new folder to host the files. Because this is a multi-site installation, you probably don’t want to put it directly into your usual Apache server root folder. In my case, Apache is configured to serve documents out of /var/www/html, which is a standard location for a Red Hat based server. In Ubuntu the default Apache root folder is /var/www, so on an Ubuntu server I might put Drupal someplace else, or possibly reconfigure Apache to use a different root folder.
Regardless, put it someplace that makes sense for your Linux distro and keeps things tidy. I have chosen to put Drupal in a folder called /var/www/drupal.
cd /var/www
sudo mkdir drupalNext up, we need to ensure that Apache will have read and write access to this folder. Let’s look at how the permissions came out using ls -hal:
drwxr-xr-x. 6 root root 60 Mar 4 10:13 .
drwxr-xr-x. 20 root root 278 Aug 23 2019 ..
drwxr-xr-x. 2 root root 6 Jun 9 2019 cgi-bin
drwxr-xr-x. 2 root root 6 Mar 4 10:13 drupal
drwxr-xr-x. 17 root root 4.0K Dec 6 05:29 htmlThe folder is owned by root, and part of the root group. The owner has full access; everyone else can read and execute files in this folder, but not write them. Since I opted to put the Apache user in a group called “web”, we’re going to put the folder into that group and grant it write access.
sudo chmod g+w drupal
sudo chgrp web drupalAnd if we check the permissions again, it should look something like this:
drwxr-xr-x. 6 root root 60 Mar 4 10:13 .
drwxr-xr-x. 20 root root 278 Aug 23 2019 ..
drwxr-xr-x. 2 root root 6 Jun 9 2019 cgi-bin
drwxrwxr-x. 2 root web 6 Mar 3 16:44 drupal
drwxr-xr-x. 17 root root 4.0K Dec 6 05:29 htmlNote that this has granted write access to the folder, but not necessarily to the files within it. Any member of the “web” group can create a new file in this folder, and newly created files will inherit that group. But they will not be able to modify any existing files unless they also have permissions to that specific file.
In my case there’s an additional wrinkle: the SE Linux security layer. This is a added layer of security found in Red Hat and related distros. The Ubuntu equivalent would be AppArmor, I believe, but I have never dealt with that and cannot tell you anything about it.
Although the system level permissions are correct to allow Apache read/write access to the folder, SE Linux has applied a “security context” label to the folder. We can see these with ls -Z:
drwxr-xr-x. root root system_u:object_r:httpd_sys_script_exec_t:s0 cgi-bin
drwxrwxr-x. root web unconfined_u:object_r:httpd_sys_content_t:s0 drupal
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 htmlThe operative bit is the “httpd_sys_content_t” part. This labels the folder as containing web content that is safe for Apache to serve up to people, but it does not allow for write access. In order to change that, we need to inform SE Linux that this folder will contain stuff that Apache needs to be able to write, such as the site settings.php file (during setup) and the files folder that stores uploaded files. The command to do that is:
sudo chcon -t httpd_sys_rw_content_t drupalSE Linux is specific to Red Hat and other distros that derive from it, so if you’re running Ubuntu or similar, this whole step is likely irrelevant to you.
PHP and Composer setup
Installing and configuring PHP is outside the scope of this post. Ensure that PHP is installed, functioning, and that you have an appropriate version for Drupal.
Now that we have our folder set up, we need to install Composer, the dependency manager. This is not 100% necessary, but having it will make updating Drupal considerably easier. Execute the scripts found at the Composer download page in your drupal directory. I will not copy them here, because they change with every version.
It’s convenient to have access to composer system-wide, so after installation, I like to put the composer.phar file someplace where it’s more universally available. In my Red Hat distro, that would be /usr/local/bin, and the series of commands would be thus:
sudo mv composer.phar /usr/local/bin
cd /usr/local/bin
sudo chown root composer.phar
sudo chgrp root composer.phar
sudo chcon -t bin_t composer.phar
sudo ln -s composer.phar composerThat moves composer to the folder, set it as owned by and grouped with root, gives it the bin_t SE Linux security context, and creates a symbolic link “composer” that points at it (for ease of typing).
If we go back to our Drupal folder now, we can run composer with no arguments and get a big long list of options that composer supports.
MySQL setup
You’ll need to make sure MySQL is installed and working correctly. The full details of that are beyond the scope of this post, but once it is working, we need to create a database for Drupal. Log into MySQL as the root mysql user:
mysql -u root -pOnce you are in, execute the following SQL commands:
CREATE DATABASE `your_db`;
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES ON your_db.* TO 'your_drupal_user'@'localhost' IDENTIFIED BY 'password_goes_here';That will do it in most cases, though if your instance of MySQL is hosted on a separate database server from your web server, you may need to replace “localhost” with the host name of the web server.
Make a note of your database name, the MySQL user, and the MySQL user’s password. (If your MySQL is on a different host, note that host name as well.) Then, try logging into the MySQl command line client from your web server to see if it works:
mysql -u your_drupal_user -p your_db
OR
mysql -u your_drupal_user -h your_db_host.example.org -p your_dbThe latter command is for when your database is running on a different server; the -h switch allows you to specify that host name. Either way, you’ll be prompted to type in the password that you specified for this MySQL user when you created it.
Once you’re in, if you want to be particularly thorough you can run the following commands to test that your user has all the MySQL permissions it needs:
CREATE TABLE `test` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`stuff` VARCHAR(255) NOT NULL DEFAULT ""
) CHARSET=UTF8;
DESCRIBE test;
INSERT INTO test (stuff) VALUES ("A little red dinosaur");
SELECT * FROM test;
ALTER TABLE test ADD `things` VARCHAR(255) NOT NULL DEFAULT "" AFTER stuff;
UPDATE test SET things = "balancing on a buttercup" WHERE id = 1;
SELECT * FROM test;
ALTER TABLE test ADD INDEX `goop` (stuff, things);
SHOW CREATE TABLE test;
SELECT * FROM test;
CREATE TEMPORARY TABLE bamf SELECT * FROM test;
SELECT * FROM bamf;
DELETE FROM test WHERE id = 1;
DROP TABLE test;If all of those commands succeed with the expected output, then your Drupal MySQL user should be good to go.
Drupal Itself
With all that out of the way, it’s time to actually do the install.
It’s time to get the Drupal code in place. I’m going to do that using Composer and the default recommended project for Drupal:
cd /var/www/drupal
composer create-project drupal/recommended-project /var/www/drupal
composer require drush/drushThat should set up all the standard files up to our desired directory. I also installed Drush, because it’s very helpful for managing multiple Drupal sites from the command line. However, it’s rather awkward to invoke out-of-the-box, so I’m going to set up the drush-launcher project:
wget -O drush.phar https://github.com/drush-ops/drush-launcher/releases/download/0.6.0/drush.phar
sudo mv drush.phar /usr/local/bin/drush
cd /usr/local/bin/drush
sudo chown root drush
sudo chgrp root drush
sudo chcon -t bin_t drush
sudo chmod +x drushThe first line there that retrieves the .phar file varies from one version to the next; check the installation docs at the drush-launcher project for the most up-to-date version. Once it’s in place, we can go check to see that it’s working:
cd /var/www/drupal
drush statusThis will display some information about your Drupal. At this point we haven’t actually configured a site, so much of it will be default boilerplate.
Next, we need to create the files folder where uploaded files will be stored:
cd /var/www/drupal/web/sites/default
mkdir files
chgrp web files
chmod g+w filesThat will create it, move it to our “web” group, and give the group write permission.
Next, we have to create the settings file from the default one:
cp default.settings.php settings.php
chgrp web settings.php
chmod g+w settings.phpSame procedure. In both cases, files and settings.php should have inherited the correct SE Linux security context when they were created, so there is nothing further that needs to be done for that.
In the default multi-site documentation, it talks about creating a virtual host definition for the master site. This allows you to map a specific instance of Drupal to a corresponding host name, so that you can run multiple sites with distinct domain names on a single server.
I will not be doing that, because in my case what I need is several different instances of Drupal running in sub-folder of the standard Apache document root. All of them will have an identical host name ("apps.example.org") and live in their own folder, such as “https://apps.example.org/green/” or “https://apps.example.org/blue/”.
In order for each instance of drupal to have its own effective sub-folder but still use the shared core Drupal code — which we installed outside the document root, you may recall — let’s put in a symbolic link in the document root that points at the Drupal install site.
cd /var/www/html
ln -s /var/www/drupal/web greenSubstitute your own Apache root path in the first line, and whatever folder name you want for “green” in the second line. This symbolic link will point Apache back to the Drupal install site when a visitor gets there. I do not plan to use the root instance as a live site; it exists solely to support the other instances that will be used for specific tasks.
Finally, we’re ready to use Drupal’s actual installer for the core instance of our multi-site install! Point your browser at the URL where your site will be living. In my example, that would be:
https://apps.example.org/green/That should bring up the Drupal installer. Go through the steps. When you come to the database setup screen, feed it the database name, user name, password (and host, if necessary) that you noted in the MySQL setup stage.
IMPORTANT: Once the installer has finished its work, you must remove the write permissions from settings.php! Write access to that file was necessary during installation but leaving it enabled would be a serious security hole. To remove them, navigate to the sites/default folder and:
chmod g-w settings.phpThat will remove the web group’s permission to write to that file.
At this point, you should have a functional root instance of Drupal.
Now that it’s up, we should set the trusted host patterns. These define which hosts Drupal recognizes as valid for certain types of HTTP requests. Failing to configure the trusted host patterns can, potentially, leave your site open to some security vulnerabilities. Open your settings.php, find the “trusted_host_patterns” section, and fill it in (being sure to uncomment it). Mine looks like this:
$settings['trusted_host_patterns'] = array(
'^vm21\.example\.org$',
'^apps\.example\.org$',
);Note that the trusted host patterns are regular expressions. The ^ character means “beginning of the line” and the $ character means “end of the line”. The . characters have been prefaced with a backslash to indicate that they are literal characters (by default, a . in a regualr expression means “any one character”). So the examples above mean “vm21.example.org” and “apps.example.org”, those exact strings with nothing before or after.
I have two listed here. The first is an internal name used by sysadmins at my organization. The other is the public-facing host name that the general public sees and uses. The two are functionally identical, but both need to be listed here so that Drupal knows they’re trustworthy. You may only have one, the public-facing host-name. Just put it in and that’ll be fine.
If you anticipate having many sites managed through this instance, and you are using subfolders for each one like I am, it may be worth adding your trusted host patterns to default.settings.php, so that they will already by set up on each new site as you create it. If your multisite install has separate host names for every site, though, you shouldn’t do so, because the trusted host pattern will be different for each new site.
Before we move on, we should set up sites.php:
cd /var/www/drupal/web/sites/
cp example.sites.php sites.phpThis file contains aliases for each site in your multi-site install. Go ahead and edit it. I tend to use nano on the command line, but any text editor you’re comfortable with will do. At the bottom, add a line like this:
$sites['apps.example.org.default'] = 'apps.example.org/green';The keys in this array correspond to the names of folders that we’ll be creating later, one for each site. Because I’m doing sub-folders instead of fully-qualified host names, I substituted a “.” for the “/” that would otherwise be there, because you can’t have a / in a folder name on Linux.
At this point, you should have a functional root instance of Drupal in a functioning multi-site environment. You can log in as your Drupal user if you like and begin fiddling with the Drupal settings — installing modules or themes you’ll need, and so on.
All of this has been one-time setup. From here on in it gets easier.
Rolling a new Drupal instance
Now lets look at how to add a new instance of Drupal. First, create a new sites folder:
cd /var/www/drupal/web/sites
mkdir apps.example.org.blue
cd apps.example.org.blue
cp ../default/default.settings.php settings.phpThat will create a home for the new Drupal site to live in, and copy in the default settings. Next, we need to set the correct permissions on our settings file and make this site’s “files” folder:
chgrp web settings.php
chmod g+w settings.php
mkdir files
chgrp web files
chmod g+w filesThat sets those files up with write permissions for setup. If you’re dealing with SE Linux, don’t forget to check their SE Linux security contexts with ls -halZ. They should have been inherited correctly, but if they’re wrong for some reason you can fix them with:
chcon -t httpd_sys_rw_content_t settings.php
chcon -t httpd_sys_rw_content_t filesNext, edit your settings.php to set up your trusted_host_patterns. In my case I stuck those in the default.settings.php because all my sites share a host name, but if you have unique host names for your sites, now is the time to add that in.
Next, we go back up to the sites folder and edit sites.php to add an alias for the new site:
$sites['apps.example.org.blue'] = 'apps.example.org/blue';The simplest way is to copy-paste the entry for the root site and then adjust the key name and the value to match the path of the new site.
Now we can add a symbolic link for the site:
cd /var/www/html
ln -s /var/www/drupal/web blueThat does it for the Drupal code setup, but we still need to add this site’s database.
mysql -u root -p
CREATE DATABASE `blue`;
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES ON blue.* TO 'blue_user'@'localhost' IDENTIFIED BY 'password_goes_here';Once that’s in place, we can point our browser at the new site’s URL, and it will let us go through the installer, just as we did with the root site. I recommend disabling update notifications for the secondary sites — if you’re managing a dozen different Drupal sites through a single multi-site instance, you only need one reminder email about updates, not a dozen each time.
Once it’s done, don’t forget to remove write permissions from settings.php:
chmod g-w settings.phpAs a quality-of-life improvement, it would be a good idea to add a site alias to drush for your new site. If you have never made a site alias before, you may need to create a couple of folders in the root of your drupal install path, thus:
cd /var/www/drupal/
mkdir drush
mkdir drush/sites
cd drush/sites
touch self.site.ymlThat’s one-time setup. Once it’s done, you can edit self.site.yml and add a few lines for your new site, thus:
blue:
user: www-admin
root: /var/www/drupal/web/sites/dev.library.und.edu.blue/
uri: https://dev.library.und.edu/blue/Bear in mind that whitespace is significant in yml files. Don’t use tabs. When you need to indent, you must use two spaces for each level of indentation, otherwise the YML parser will choke.
Once your site alias is in place, you can run drush commands that target a specific site by using “@your_site_alias” as part of the command. For example, if I wanted to force a cache rebuild on the site “blue”, I could run:
drush @blue crIndividual sites have their own database, their own files folder, and their own configuration, and so on.
Themes: If you want a theme to be accessible to every site, put it in /var/www/drupal/web/themes. Thereafter you can use it on every site if you wish, and updates to the theme should appear throughout the sites (though you may need to do a cache rebuild for changes to show up.) Each site can have a themes folder of its own, so that you can have a theme which is available in one site, but not to any other. For example, if the “blue” site had its very own theme, you would create a folder called /var/www/drupal/web/sites/apps.example.org.blue/themes and put your theme in there.
Modules: work just like themes, only shared modules should be put in /var/www/drupal/web/modules and site-specific modules would go in /var/www/drupal/web/sites/apps.example.org.blue/themes.
That’s all.
Updating Drupal
Updating Drupal via composer is fairly straightforward. In the Drupal install folder run:
composer outdated "drupal/*"
composer update --with-dependenciesThat will update the Drupal code for the entire multisite instance. However, we do need to update the the database and do a cache rebuild in case there have been changes to the database schema or the theme files:
drush updatedb
drush crThis will perform the database and theme updates for the default site, but you will need to repeat them for each other site, using the aliases we set up in drush. For example, for the site “blue” you would run:
drush @blue updatedb
drush @blue crOnce those have run, the database and theme cache for the “blue” site should be up-to-date.
If you have forgotten what aliases you set up, you can list them with:
drush saIf you have more than a handful of instances, it might be worth writing and maintaining a small script to run the updatedb and cr commands on each site so that you don’t have to do it manually every time.
Final Thoughts
These are notes arising from my own experience; and I’ve gotten my sites up and running as I needed them. Any errors or omissions are mine. If you notice any of those, please correct me in a response below so that everyone who comes across this can benefit.