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,opcacheandpdo_mysqlorpdo_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
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
redismodule). 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.