Drupal is an open source content management system suited to structured, multilingual and high-traffic sites. Modern Drupal is managed with Composer, which installs Drupal core, contributed modules and their PHP dependencies, and with Drush, its command line tool. In this tutorial you will set up Apache, PHP-FPM 8.3 and MariaDB on Ubuntu 24.04, create a Drupal 11 project with Composer, install the site with Drush, and finish with HTTPS, trusted host settings and cron.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS with at least 2 GB of RAM, for example a CubePath VPS. Composer needs the memory when resolving dependencies.
  • A non-root user with sudo privileges, referred to as your_user.
  • A domain name with an A record pointing to the server, referred to as your_domain.
  • Ports 80 and 443 open to the internet.

Drupal 11 requires PHP 8.3 or newer and MariaDB 10.6 or newer. Ubuntu 24.04 ships PHP 8.3 and MariaDB 10.11, so the distribution packages are enough.

Step 1 - Installing Apache and PHP-FPM

Install Apache, PHP-FPM and the PHP extensions Drupal needs (gd for image styles, xml and mbstring for text handling, apcu and opcache for performance), plus unzip and git for Composer:

sudo apt update
sudo apt install apache2 php-fpm php-cli php-mysql php-gd php-xml php-mbstring php-curl php-zip php-apcu unzip git

Apache talks to PHP-FPM through mod_proxy_fcgi. Enable it together with mod_rewrite, which Drupal uses for clean URLs, and the PHP-FPM configuration snippet shipped by the package:

sudo a2enmod proxy_fcgi setenvif rewrite
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2

Check that PHP-FPM is running:

systemctl is-active php8.3-fpm apache2
active
active

Allow web traffic through the firewall:

sudo ufw allow OpenSSH
sudo ufw allow 'Apache Full'
sudo ufw enable

Step 2 - Tuning PHP settings

The defaults for memory and upload size are too low for a typical Drupal site. Instead of editing php.ini, add a small override file that survives package upgrades:

sudo nano /etc/php/8.3/fpm/conf.d/90-drupal.ini
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 120

Restart PHP-FPM and confirm the new value:

sudo systemctl restart php8.3-fpm
sudo php-fpm8.3 -i | grep ^memory_limit
memory_limit => 256M => 256M

Step 3 - Creating the database

Install MariaDB:

sudo apt install mariadb-server

Open the MariaDB shell. The root account uses socket authentication on Ubuntu, so sudo is enough:

sudo mariadb

Create the database and a user limited to it. Replace your_strong_password with a long random password:

CREATE DATABASE drupal CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'drupal'@'localhost' IDENTIFIED BY 'your_strong_password';
GRANT ALL PRIVILEGES ON drupal.* TO 'drupal'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Test the login:

mariadb -u drupal -p -e "SELECT CURRENT_USER();"
+------------------+
| CURRENT_USER()   |
+------------------+
| drupal@localhost |
+------------------+

Step 4 - Creating the Drupal project with Composer

Install Composer from the Ubuntu archive:

sudo apt install composer
composer --version

Composer should never run as root or as the web server user. Create the project directory and give it to your own user:

sudo mkdir -p /var/www/drupal
sudo chown "$USER":"$USER" /var/www/drupal

Create the project from the official drupal/recommended-project template. It pins every dependency to the versions tested with the current Drupal release and places the public files in a web/ subdirectory:

composer create-project drupal/recommended-project /var/www/drupal

Add Drush to the project. It is installed per project, not globally:

cd /var/www/drupal
composer require drush/drush

Verify it:

vendor/bin/drush --version
Drush Commandline Tool 13.6.2.0

Step 5 - Installing the site with Drush

Drush can run the full installer from the command line, which is faster and more repeatable than the browser wizard. Replace the password, site name and admin email:

cd /var/www/drupal
vendor/bin/drush site:install standard \
  --db-url='mysql://drupal:your_strong_password@localhost/drupal' \
  --site-name='My Drupal Site' \
  --account-name=admin \
  --account-mail=you@your_domain

Confirm with yes. Drush creates web/sites/default/settings.php, the files directory and the database tables, and prints the generated admin password at the end:

 [success] Installation complete.  User name: admin  User password: 8kVx2...

Store that password in your password manager. You can change it later from the user profile.

The web server needs to write only to the public files directory. Give its group to www-data and make it group writable, while the rest of the code stays owned by your user and read-only for Apache:

sudo chown -R "$USER":www-data /var/www/drupal/web/sites/default/files
sudo chmod -R g+w /var/www/drupal/web/sites/default/files
sudo find /var/www/drupal/web/sites/default/files -type d -exec chmod g+s {} +

The setgid bit on the directories makes files created later inherit the www-data group.

Step 6 - Configuring the Apache virtual host

Create a virtual host whose document root is the web/ directory, never the project root, so vendor/ and composer.json are not reachable from the internet:

sudo nano /etc/apache2/sites-available/drupal.conf
<VirtualHost *:80>
    ServerName your_domain
    ServerAlias www.your_domain
    DocumentRoot /var/www/drupal/web

    <Directory /var/www/drupal/web>
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/drupal_error.log
    CustomLog ${APACHE_LOG_DIR}/drupal_access.log combined
</VirtualHost>

AllowOverride All lets Apache apply the .htaccess file that ships with Drupal, which contains the clean URL rewrites and blocks access to sensitive files. Enable the site, disable the default one and test the configuration:

sudo a2ensite drupal.conf
sudo a2dissite 000-default.conf
sudo apache2ctl configtest
Syntax OK

Reload Apache:

sudo systemctl reload apache2

Step 7 - Enabling HTTPS with Let's Encrypt

Install Certbot with the Apache plugin and request a certificate. Certbot creates the HTTPS virtual host and a redirect from HTTP:

sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d your_domain -d www.your_domain

Renewal runs from a systemd timer installed by the package. Confirm that it works:

sudo certbot renew --dry-run

Open https://your_domain/user/login and sign in with the admin account from Step 5.

Step 8 - Securing settings.php

Drupal rejects requests for unknown host names only if you tell it which ones are valid. The installer may have already made the settings file read-only, so give your user write access first:

chmod u+w /var/www/drupal/web/sites/default /var/www/drupal/web/sites/default/settings.php

Open the settings file:

nano /var/www/drupal/web/sites/default/settings.php

Add the following at the end of the file, escaping the dots in your domain:

$settings['trusted_host_patterns'] = [
  '^your_domain$',
  '^www\.your_domain$',
];

For example, for example.com the first pattern is '^example\.com$'. Then make the settings file and its directory read-only:

chmod 444 /var/www/drupal/web/sites/default/settings.php
chmod 555 /var/www/drupal/web/sites/default

Rebuild the cache and check the status report from the command line:

cd /var/www/drupal
vendor/bin/drush cache:rebuild
vendor/bin/drush core:requirements --severity=2

A clean installation returns no errors. In the browser, Reports > Status report shows the same checks, including the trusted host setting.

Step 9 - Running cron from the system

Drupal needs cron to index content, clean up temporary files and check for updates. The standard profile runs it on page views ("automated cron"), which adds latency to visitor requests. Disable that module and schedule Drush instead:

cd /var/www/drupal
vendor/bin/drush pm:uninstall automated_cron

Open your user's crontab:

crontab -e

Add this line to run cron every 15 minutes:

*/15 * * * * cd /var/www/drupal && vendor/bin/drush cron --quiet

After 15 minutes, Reports > Status report shows the time of the last cron run.

Installing modules and updating Drupal

Contributed modules are added with Composer and enabled with Drush. For example, to add Admin Toolbar and Pathauto:

cd /var/www/drupal
composer require drupal/admin_toolbar drupal/pathauto
vendor/bin/drush pm:install admin_toolbar pathauto

To update Drupal core and its dependencies, back up the database first, then run the database updates and rebuild the cache:

cd /var/www/drupal
vendor/bin/drush sql:dump --gzip --result-file="$HOME/drupal-before-update.sql"
composer update "drupal/core-*" --with-all-dependencies
vendor/bin/drush updatedb
vendor/bin/drush cache:rebuild

Troubleshooting

"The provided host name is not valid for this server." The domain you are using is not in trusted_host_patterns. Check the regular expressions in settings.php, including the escaped dots.

The files directory is not writable. The status report complains when www-data cannot write to web/sites/default/files. Repeat the ownership commands from Step 5.

Composer runs out of memory. Run it with COMPOSER_MEMORY_LIMIT=-1 composer ... or add swap to servers with 1 GB of RAM.

Conclusion

Drupal 11 is now running on Ubuntu 24.04 with Apache, PHP-FPM 8.3 and MariaDB, installed with Composer, served over HTTPS and maintained with Drush and system cron. As next steps, configure a regular database and files backup, add Redis for the cache backend on busy sites, and subscribe to the Drupal security advisories to apply core and module updates promptly.