Install Drupal Prerequisites on Ubuntu 20.04

For websites that don't have a huge amount of visitors I am usually deploying both the database and web server on the same machine, and this is what I will do here. But it will be easy to adapt this procedure for a multi-server environment.

We will start with the clean Ubuntu 20.04.1 machine. Let's first update it by executing:

$ sudo apt update && sudo apt upgrade

MySQL Server

To install MySQL Server execute:

$ sudo apt install mysql-server

Configure MySQL Server

For fresh MySQL installations, it's a good idea to execute the included security script which will change some less secure default options. So let's execute the security script:

$ sudo mysql_secure_installation

The script will guide you through the configuration by asking several questions that are easy to understand and answer. Here I'll provide my answers only as a reference, but your's may be different, of course:

  • "Would you like to setup VALIDATE PASSWORD component?" - No
  • "Please set the password for root here." - my_super_secure_password
  • "Remove anonymous users?" - Yes
  • "Disallow root login remotely?" - No
  • "Remove test database and access to it?" - Yes
  • "Reload privilege tables now?" - Yes

Create Website Database

Enter the mysql shell by executing:

$ sudo mysql

Now we can create a new database for the new Drupal site by executing:

mysql> CREATE DATABASE drupal_db;

Create Website DB User

We definitely don't want to use root user to access the database, so we will create a new one by executing something like:

mysql> CREATE USER 'drupal_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'some_strong_password';

Note that here I've used WITH mysql_native_password option, which is a bit older, but still very secure authentication mechanism. Some other options are also available, like WITH authentication_plugin and WITH caching_sha2_password (which is the default one, and used if WITH part is omitted completely), but mysql_native_password is probably the best choice for PHP applications, including Drupal.

Also, note that I've specified the user with suffix @'localhost'. It means that the user will access the database from the local machine, of course. Alternatively, you can specify some other specific host (i.e. 'drupal_user'@''), or you can specify any host by using a wildcard (i.e. 'drupal_user'@'%').

Finally, we need to grant permissions on the new database to the new user by executing something like:



mysql> GRANT ALL PRIVILEGES ON drupal_db.* TO 'drupal_user'@'localhost' WITH GRANT OPTION;

Honestly speaking, I usually use the latter option, but many DB admins don't like to see GRANT ALL... We're granting permissions only to a specific database, so I don't think that granting all privileges is a big deal, but I'll leave the decision to you.

Create DB Dump User (Optional)

I usually create another user that will be used for executing DB dumps. DB dumps are used not only for backups but also in staging environments to copy the data from one stage to another (from production to test/dev server), meaning that this user should be able to access the DB from other machines, so we should use @'%' suffix. So we can create this user by executing something like:

mysql> CREATE USER 'db_dump_user'@'%' IDENTIFIED WITH mysql_native_password BY 'other_strong_password';

This user needs different privileges:

mysql> GRANT PROCESS, SELECT, LOCK TABLES ON *.* TO 'db_dump_user'@'%';

Since I will use the same DB dump user for all my databases (websites), I've granted permissions for all the databases (ON *.*), not only for drupal_db database as I've did previously (ON drupal_db.*).


To install apache web server execute:

$ sudo apt install apache2

Serve From Custom Directories (Optional)

By default, Apache serves only websites which reside in /var/www/html directory, and refuses to serve from any other location. But I like to have my websites in another location (i.e. /opt/websites), so I need to perform some additional configuration. For this reason, I create a new configuration file /etc/apache2/conf-available/allow-opt-websites.conf (you can change the file name, of course), with the following content (again you can change the actual directory):

# Added to allow websites from /opt/websites
<Directory /opt/websites>
	AllowOverride None
	Require all granted

It's probably a good idea to configure the security attributes of the newly created file to match other files from the same directory.

Once the file is in place we can activate the new configuration by executing:

$ sudo a2enconf allow-opt-websites
$ sudo systemctl reload apache2

Note that the configuration name in the first line (allow-opt-websites) needs to be the same as the name of the configuration file we've created previously, with or without .conf extension.


At the moment of this writing, Drupal requires PHP version 7.3 or newer, so the installation steps are different for different Ubuntu versions.

Ubuntu 20.04

Luckily, Ubuntu 20.04 repositories include PHP 7.4, so we can install it simply by executing:

$ sudo apt install php libapache2-mod-php php-common php-mbstring php-xmlrpc php-soap php-gd php-xml php-intl php-mysql php-cli php-zip php-curl

Older Ubuntu Versions

For Ubuntu versions older than 20.04, we first need to add PPA repository, and only then install PHP:

$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:ondrej/php
$ sudo apt update
$ sudo apt install php7.4 libapache2-mod-php7.4 php7.4-common php7.4-mbstring php7.4-xmlrpc php7.4-soap php7.4-gd php7.4-xml php7.4-intl php7.4-mysql php7.4-cli php7.4-zip php7.4-curl

Test PHP Installation (Optional)

To test PHP installation you can create a very simple PHP file /var/www/html/info.php by executing:

$ sudo bash -c 'cat << EOF > /var/www/html/info.php

Then restart Apache:

$ sudo systemctl restart apache2

and try to open the following URL in your browser: http://[server_ip]/info.php. You should get a lot of information about the current PHP installation.

Configure PHP (Optional)

If needed you can change some PHP configuration by editing /etc/php/7.4/apache2/php.ini file. For me, the defaults are often good enough.

Composer (Optional)

Although strictly speaking Composer is optional (you can deploy a Drupal site without it), it's so useful in any merely serious website for dependency management and upgrades, that I strongly suggest using it always. You can find more information at Using Composer to Download and Update Files Drupal documentation page.

You can find the installation instructions at, and here I'll just provide some additional tips. The whole installation is quite simple, and it's only about executing four lines of code. These lines, copied from the link provided at the moment of this writing, are:

$ php -r "copy('', 'composer-setup.php');"
$ php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
$ php composer-setup.php
$ php -r "unlink('composer-setup.php');"

The first line simply downloads composer-setup.php file into the current directory. I have a problem with this line because I'm using an outgoing proxy, and php command isn't smart enough to pick up the system-wide proxy configuration, so it fails. If it happens, you can simply download the file in any other way, maybe with curl or wget. Here's an example with curl:

$ curl --output composer-setup.php

The second line from the original code checks validity / integrity of the downloaded file. If the file isn't valid the command will delete it.

The third line executes the setup script and creates composer.phar executable in the current directory. Since I want my executable to be named composer, and to be installed somewhere within the PATH, I usually modify this line in the following way:

$ sudo php composer-setup.php --filename=composer --install-dir=/usr/local/bin

Note that you can accomplish the same manually, step by step, by first executing the original line, without any arguments, and then manually renaming the file and moving it where you want it - the final result is the same. In fact, it happened to me that the variant with arguments didn't work (I suspect because of some outgoing proxy setting), and if it happens you can simply do it manually.

The fourth (the last) line from the original script simply deletes composer-setup.php file which you can delete in any other way (i.e. rm composer-setup.php).

You can test the installation by checking the Composer version:

$ composer --version
Composer version 2.0.7 2020-11-13 17:31:06

Drush Launcher (Optional)

Drush Launcher is another optional, but very useful tool, and I strongly recommend using it. If you don't use Drush - you definitely don't need Drush Launcher (but you should reconsider this decision). Even if you do use Drush - you can use it without Drush Launcher, but again it'll make your life easier.

Let me very quickly explain what it's all about. Earlier, people who were using drush were usually installing it globally, and use the same installation for all the Drupal websites on that machine. But since Composer workflow has become popular, every Drupal project (which contains the Drupal site, but also a few more things) usually contains its own drush in vendor/drush sub-directory (and compiled executable vendor/bin/drush). This way every Drupal site can have its own version of drush, if a specific version is needed for some reason. So if you want to execute some drush command (let's say cache rebuild), you have two options:

  • Execute drush cr, which will use the globally installed version;
  • Or execute something like vendor/bin/drush cr, which will obviously use the locally installed version.

This introduces two problems: First, you will always need to be careful about which version to use (global or local), and it's error-prone, and second is that the local version requires some additional typing (you need to specify the full path to the executable). These are the exact problems that Drush Launcher solves.

Drush Launcher is a small wrapper around drush, which simply forwards the commands to the actual drush executable. But it adds a little bit of magic to that: it knows which underlying drush command to use. If you execute Drush Launcher from a directory that is within a Drupal project directory (where a local drush executable is available) - it will use the local drush version. If executed from any other location - it will fall back to the global drush version (if one is installed). In other words, you'll always execute something like drush cr, and it will always select the appropriate version (local or global) automatically.

Installing Drush Launcher

The installation is very simple. First, you need to download drush.phar file by executing:

$ curl -OL

Then make it executable by executing:

$ chmod +x drush.phar

Finally, rename it and move it somewhere within the PATH:

$ sudo mv drush.phar /usr/local/bin/drush

To check if it's installed correctly execute drush --version, and you'll get something like:

$ drush --version
Drush Launcher Version: 0.8.0
The Drush launcher could not find a Drupal site to operate on. Please do *one* of the following:
  - Navigate to any where within your Drupal project and try again.
  - Add --root=/path/to/drupal so Drush knows where your site is located.

Note that, although we've executed drush --version, the output displayed actually comes from the newly installed Drush Launcher, not from actual drush, and the launcher currently complains that it cannot find underlying drush (not a surprise since we don't have it yet). But when we execute the same command from a Drupal project directory (once we deploy one), the output will also contain info about the underlying drush that will be used. Just to demonstrate, here's the output of the same command executed from a Drupal project directory:

$ drush --version
Drush Launcher Version: 0.8.0
Drush Commandline Tool 9.7.2

Global Drush (Optional)

In my case, since I'm always using the new Composer workflow (and Drupal project) for my Drupal websites, I don't need a global version of drush. But if you need a global version of drush (if you have old-fashioned, non-Composer Drupal deployments), this section will show how to accomplish that.

The first step is to install Drush globally, but there are few things to know:

  • Drush 9.x (the latest) doesn't support global installations. Here's a quote from the original documentation: "Drush 9 only supports one install method. It requires that your Drupal 8 site be built with Composer and Drush be listed as a dependency." So you should go with Drush 8.x. Note: Although technically it isn't hard to install Drush 9 globally - it's simply an executable that you can copy wherever. But you should not do that because chances are that Drush 9 simply doesn't work properly for non-Composer deployments.
  • You can install Drush 8.x in any way offered in the official documentation, but you need to ensure that the globally installed drush isn't in PATH (or at least that it isn't before Drush Launcher). Keep in mind that both Drush and Drush Launcher are using the same executable name - drush - meaning that, if the global Drush installation ends up in PATH before Drush Launcher - it will be always executed, and Drush Launcher never.

So let's say that you've installed the global drush outside the PATH, for example in /opt/drush. Now you need to make Drush Launcher aware of it. To do that you need to export one environment variable:

$ export DRUSH_LAUNCHER_FALLBACK=/opt/drush

When it comes to how and where to export this environment variable, there are several options (more details here). In short, if you want to set it only for the current user, you should add the previous line to ~/.profile file. To set it system-wide you can either create a file like /etc/profile.d/ (it has to be like /etc/profile.d/*.sh) and add the line there, or add it to /etc/environment file, in which case the line should not contain export keyword but only DRUSH_LAUNCHER_FALLBACK=/opt/drush.

Certbot (Optional)

If you want to protect your website traffic with TLS, and you need an SSL certificate issued from a trusted authority (green in browsers), Let's Encrypt is probably the best choice because it's very easy to implement (there are some automation tools and we're about to install one), it's easy and fast to obtain (in seconds), and finally it's completely free.

For creating, renewing, and managing Let's Encrypt certificates we will use certbot package. As with PHP, the installation procedure is different for different Ubuntu versions.

Ubuntu 20.04

Ubuntu 20.04 repositories include certbot package, so we can install it simply by executing:

$ sudo apt install python3-certbot-apache

Older Ubuntu Versions

For older Ubuntu versions we have to add a PPA repository first. Here's the whole procedure:

$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt update
$ sudo apt install python-certbot-apache

Other Tools

There's one more tool that is crucial in modern Drupal project deployments, and it is git, but it's already included in Ubuntu 20.04, so there's nothing to do. You should check if it's available on your Ubuntu version, and if it isn't install it (sudo apt install git).

Create User Group (Optional)

The last problem that we need to solve is to configure some security settings on Ubuntu itself. The key requirement regarding that is to allow Drupal developers and administrators to write to /opt/websites directory (or some other directory in your case), without using sudo. Even if a user has sudo privileges, he should not use it while working on a Drupal website. In fact, composer will complain if you run it with sudo. To accomplish that we will do the following:

  • Create a new user group in Ubuntu (devs in my case);
  • Add all users that should have write permission for /opt/websites directory to the newly created group;
  • Add these users also to www-data group so that they can modify files created by Drupal / Apache (i.e. sites/default/files content) without using sudo;
  • Change ownership of /opt/websites directory to root:devs (root user, but the newly created group devs);
  • Configure security permissions of the folder so that group members have read/write/execute permissions;
  • Set GID flag on /opt/websites directory, which means that group ownership (devs in my case) will be inherited by contained objects (sub-directories and files within the directory). This effectively means that no matter the actual user who creates a directory or file, our newly created group (devs in my case) will be the owner.

So let's start by creating the directory (if not already created):

$ sudo mkdir /opt/websites

Note that I've had to use sudo above because my user is not allowed to write to /opt directory.

Let's create the security group:

$ sudo groupadd devs

Now we should add users to the newly created group and www-data. In my case, I will add only myself (dragon):

$ sudo adduser dragon devs
$ sudo adduser dragon www-data

Let's change the ownership of the directory:

$ sudo chown root:devs /opt/websites

Finally, let's permissions for the directory:

$ sudo chmod 2775 /opt/websites

To quickly explain 2775 permissions: 2 means set GID (explained above); 7 at the second and third position means that both user owner (root) and group owner (devs) have all permissions (read/write/execute); 5 at the last position means that all other users have only read and execute permissions.

Note that, for the new group membership to have an effect you need to logout / login again, so I'll do that. Now my user should be able to write to /opt/websites without using sudo. To try that execute:

$ touch /opt/websites/afile && rm /opt/websites/afile

That's it for now. Down the road, once we install the first Drupal website, we will have to make some additional changes to allow Apache user (www-data) write permissions for some directories.

Thank You!

That's it! Ubuntu is now ready to host Drupal sites. I hope that I will be able to create few tutorials about how to implement a proper, Git-based, Composer-based, staging development environment. Until then - enjoy and always smile! Thanks for reading!

Add new comment

Anonymous comments require solving captcha, and waiting for administrator's approval.