Drupal is a powerful open-source CMS and content framework, used for complex sites: governments, universities, media and enterprise portals. Its strengths are structured content types, fine-grained permissions, multilingual support and a strong security team. Modern Drupal is built with Composer and managed with Drush, which is how this guide installs it.

Requirements

  • PHP 8.3+ for Drupal 11 (check the current requirements), with gd, mbstring, xml, curl, zip, opcache and pdo_mysql or pdo_pgsql.
  • MySQL 8 / MariaDB 10.6+, or PostgreSQL 16+.
  • Composer 2. See Composer.
  • 2 GB RAM or more.
sudo apt install nginx php8.3-fpm php8.3-{gd,mbstring,xml,curl,zip,opcache,pgsql,mysql,intl} postgresql unzip git

See PHP versions on Ubuntu.

1. Database

PostgreSQL:

sudo -u postgres createuser --pwprompt drupal
sudo -u postgres createdb -O drupal -E UTF8 drupal

(For MySQL, create a utf8mb4 database and user. See MySQL users.)

2. Create the project with Composer

sudo mkdir -p /var/www/drupal && sudo chown $USER:www-data /var/www/drupal
cd /var/www/drupal
composer create-project drupal/recommended-project .
composer require drush/drush

Structure:

  • web/: the web root (core, modules, themes, files);
  • vendor/: dependencies, outside the web root;
  • composer.json: your site’s dependencies, versioned in Git.

3. Install with Drush

vendor/bin/drush site:install standard \
  --db-url=pgsql://drupal:password@localhost/drupal \
  --site-name="Example" \
  --account-name=siteadmin --account-pass='a-long-password' -y

Then set permissions for the files directory:

sudo chown -R www-data:www-data web/sites/default/files
sudo chmod 444 web/sites/default/settings.php

4. settings.php essentials

In web/sites/default/settings.php (or settings.local.php):

$settings['trusted_host_patterns'] = ['^example\.com$', '^www\.example\.com$'];
$settings['file_private_path'] = '/var/www/drupal/private';
$settings['config_sync_directory'] = '../config/sync';
  • Trusted host patterns prevent host header attacks. Drupal warns if they’re missing.
  • Private files outside the web root serve access-controlled downloads.
  • Config sync exports configuration to Git (drush config:export), and imports it on other environments.

5. Nginx

server {
    server_name example.com www.example.com;
    root /var/www/drupal/web;
    index index.php;
    client_max_body_size 64M;

    location = /favicon.ico { log_not_found off; access_log off; }
    location ~ \..*/.*\.php$ { return 403; }
    location ~ ^/sites/.*/private/ { return 403; }
    location ~ ^/sites/[^/]+/files/.*\.php$ { deny all; }
    location ~ (^|/)\. { return 403; }

    location / {
        try_files $uri /index.php?$query_string;
    }
    location @rewrite { rewrite ^ /index.php; }
    location ~ ^/sites/.*/files/styles/ { try_files $uri @rewrite; }   # image styles

    location ~ '\.php$|^/update.php' {
        fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param HTTP_PROXY "";
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|webp|woff2?)$ {
        try_files $uri @rewrite;
        expires max;
        log_not_found off;
    }
}

Add HTTPS with Certbot. See Let’s Encrypt.

Cron

Drupal’s “automated cron” runs on page requests. On a VPS, disable it (uninstall the automated_cron module), and use the system cron:

*/15 * * * * cd /var/www/drupal && vendor/bin/drush cron > /dev/null 2>&1

See crontab.

Performance

  • Enable page caching for anonymous users, plus the dynamic page cache (both default modules), and CSS/JS aggregation.
  • Redis for the cache backend (the redis module). See install Redis.
  • OPcache and tuned PHP-FPM. See PHP-FPM tuning.
  • A reverse proxy cache (Varnish) or CDN for high traffic. See Varnish.

Updates (Composer + Drush)

composer update "drupal/core-*" --with-all-dependencies
vendor/bin/drush updatedb -y
vendor/bin/drush cache:rebuild

Contributed modules update the same way (composer update drupal/module_name). Subscribe to Drupal security advisories, and apply security releases quickly. Test on staging, and back up first. See updating self-hosted apps.

Backups

vendor/bin/drush sql:dump --gzip --result-file=/backups/drupal-$(date +%F).sql
tar -czf /backups/drupal-files-$(date +%F).tar.gz -C /var/www/drupal web/sites/default/files private

The code is reproducible from Git plus composer.lock, so back up the database, the files and config. See backup strategy.

Quick reference

Task Command
New project composer create-project drupal/recommended-project .
Install site drush site:install
Clear caches drush cr
DB updates drush updatedb
Export / import config drush cex / drush cim
Cron drush cron

Related: Joomla and Nginx server blocks.

  • CMS
  • PHP
  • Nginx