How to Install Drupal 11 on a Dedicated Server

Drupal is an open-source CMS designed for building websites with structured content, user management, and an extensive module ecosystem. Learn how to securely deploy it using Nginx, PHP 8.3, and MariaDB on Ubuntu.

This tutorial explains how to install Drupal 11 on an Ubuntu 24.04 LTS dedicated server using:

  • Nginx
  • PHP 8.3
  • PHP-FPM
  • MariaDB
  • Composer
  • Let's Encrypt SSL
  • Drush

The installation uses Drupal's Composer-based project structure, with /web as the public document root.

Prerequisites

Before starting, prepare:

  • A dedicated server running Ubuntu 24.04 LTS
  • A domain name
  • DNS access for the domain
  • SSH access
  • A non-root user with sudo privileges
  • At least 1 GB of RAM
  • Sufficient storage for Drupal, uploaded files, logs, and backups
Environment Example Values This guide uses these example values:
  • Domain: example.com
  • Drupal directory: /var/www/drupal
  • Database name: drupal
  • Database user: drupaluser
Please replace these values with your actual production data.

1. Connect to the Dedicated Server

Connect to the server through SSH:

Bash
ssh username@SERVER_IP

Check the operating system:

Bash
lsb_release -a

Update the system and install basic utilities:

Bash
sudo apt update
sudo apt upgrade -y
sudo apt install curl unzip git ca-certificates dnsutils -y

If Ubuntu requires a reboot:

Bash
sudo reboot

Reconnect to the server after it becomes available.

2. Install Nginx

Install Nginx:

Bash
sudo apt install nginx -y

Enable and start the service, check its status, and verify the installed version:

Bash
sudo systemctl enable --now nginx
sudo systemctl status nginx
nginx -v

3. Install and Secure MariaDB

Install MariaDB:

Bash
sudo apt install mariadb-server -y

Enable and start MariaDB, then check the service:

Bash
sudo systemctl enable --now mariadb
sudo systemctl status mariadb

Run MariaDB's security script. Follow the prompts to remove unnecessary anonymous accounts and the test database, and to restrict unnecessary remote administrative access:

Bash
sudo mariadb-secure-installation
mariadb --version

4. Create the Drupal Database

Open the MariaDB shell:

Bash
sudo mariadb

Execute the following SQL queries to create the database, user, and assign privileges. Be sure to replace the placeholder password:

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

Your database details for the final installation wizard are now:

  • Database type: MySQL / MariaDB
  • Database name: drupal
  • Username: drupaluser
  • Password: YOUR_DATABASE_PASSWORD
  • Host: localhost

Use a strong, unique password for the database account.

5. Install PHP 8.3 and PHP Extensions

Drupal 11 supports PHP 8.3, and this guide uses PHP 8.3. Install PHP-FPM and the required extensions:

Bash
sudo apt install php8.3-fpm php8.3-cli php8.3-mysql php8.3-gd php8.3-curl php8.3-xml php8.3-mbstring php8.3-zip php8.3-intl php8.3-opcache -y

Check the PHP version, status, and enable PHP-FPM:

Bash
php -v
sudo systemctl status php8.3-fpm
sudo systemctl enable php8.3-fpm

Optional: APCu

APCu can be used for local application caching, but it is not a mandatory Drupal core requirement. If you want to install it:

Bash
sudo apt install php8.3-apcu -y

6. Install Composer

Check whether Composer is already available:

Bash
composer --version

If it is not installed:

Bash
sudo apt install composer -y
composer --version

Drupal 11 requires a sufficiently recent Composer 2 release, so verify that the installed version meets the requirements of the Drupal release you are deploying.

7. Create the Drupal Project

Create the application directory, give your deployment user ownership, and move into the directory:

Bash
sudo mkdir -p /var/www
sudo chown $USER:$USER /var/www
cd /var/www

Create the Drupal project using the official Composer project template:

Bash
composer create-project drupal/recommended-project drupal
Project Structure The resulting structure will look similar to:
/var/www/drupal/
├── composer.json
├── composer.lock
├── vendor/
└── web/
    ├── core/
    ├── modules/
    ├── profiles/
    ├── sites/
    └── themes/
The public web root will be: /var/www/drupal/web. Do not point Nginx at /var/www/drupal. The /web directory should be the public document root.

8. Prepare Drupal Permissions for Installation

Drupal needs temporary write access to its settings file during installation. Move into the Drupal web directory, create settings.php from the default template, and set the project ownership:

Bash
cd /var/www/drupal/web
sudo cp sites/default/default.settings.php sites/default/settings.php
sudo chown -R $USER:www-data /var/www/drupal

Allow the web server group to write to settings.php during installation, allow the installer to work with the default site directory, create Drupal's public files directory, and configure permissions:

Bash
sudo chmod 660 sites/default/settings.php
sudo chmod 770 sites/default
sudo mkdir -p sites/default/files
sudo chown -R $USER:www-data sites/default/files
sudo find sites/default/files -type d -exec chmod 770 {} \;
sudo find sites/default/files -type f -exec chmod 660 {} \;
Security Warning Do not use insecure permissions such as: chmod -R 777 /var/www/drupal.

9. Configure Nginx

Remove Ubuntu's default enabled site:

Bash
sudo rm -f /etc/nginx/sites-enabled/default

Create the Drupal Nginx configuration: sudo nano /etc/nginx/sites-available/drupal

Use the following configuration and replace example.com with your actual domain:

Nginx
server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    root /var/www/drupal/web;
    index index.php index.html;

    # Drupal front controller
    location / {
        try_files $uri /index.php?$query_string;
    }

    # Prevent PHP execution in Drupal's public files directory
    location ~ ^/sites/[^/]+/files/.*\.php$ {
        deny all;
    }

    # Block hidden files and directories
    location ~ (^|/)\. {
        deny all;
    }

    # Handle Drupal Image Styles
    location ~ ^/sites/.*/files/styles/ {
        try_files $uri /index.php?$query_string;
    }

    # Serve normal static assets directly
    location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|svg|webp)$ {
        try_files $uri =404;
        expires 30d;
        access_log off;
        log_not_found off;
    }

    # Pass PHP requests to PHP-FPM
    location ~ '\.php$|^/update.php' {
        fastcgi_split_path_info ^(.+?\.php)(|/.*)$;

        try_files $uri =404;

        include fastcgi_params;

        fastcgi_param HTTP_PROXY "";
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param QUERY_STRING $query_string;

        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

The important security and performance points here are:

  • /var/www/drupal/web is the public root.
  • PHP execution is blocked inside uploaded/public files.
  • Hidden files are denied.
  • Drupal Image Styles receive their own routing rule.
  • Missing CSS, JavaScript, and normal image files return 404 without unnecessarily bootstrapping Drupal.
  • PHP requests are checked against the requested URI before being passed to PHP-FPM.
  • Nginx documents try_files $uri =404 as a way to verify that the requested PHP file exists before sending it to FastCGI.

Enable the site, test the configuration, and if successful, reload Nginx:

Bash
sudo ln -s /etc/nginx/sites-available/drupal /etc/nginx/sites-enabled/drupal
sudo nginx -t
sudo systemctl reload nginx

10. Point the Domain to the Server

Create DNS records pointing the domain to the dedicated server's public IP.

  • For the root domain: Type: A, Name: @, Value: SERVER_IP
  • For www: Type: A, Name: www, Value: SERVER_IP

Check DNS resolution:

Bash
dig example.com

Make sure the domain resolves to the correct server before requesting the SSL certificate.

11. Configure HTTPS with Let's Encrypt

Install Certbot, request the certificate, and follow Certbot's prompts:

Bash
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com

Test the Nginx configuration, reload Nginx, and check the certificate renewal timer:

Bash
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl status certbot.timer
sudo certbot renew --dry-run

Your Drupal installation should now be accessible through: https://example.com

12. Complete the Drupal Installation

Open https://example.com in your browser. The Drupal installation wizard should appear.

When prompted for database information, use:

  • Database type: MySQL / MariaDB
  • Database name: drupal
  • Database username: drupaluser
  • Database password: YOUR_DATABASE_PASSWORD
  • Database host: localhost

Complete the remaining installation fields (Site name, Site email, Administrator username, Administrator password, Country, Time zone). Complete the installation and sign in to the Drupal administration interface.

13. Lock Down Drupal Permissions After Installation

Once Drupal has completed installation, remove unnecessary write access from settings.php, restrict the default site directory, and set the normal Drupal directory and file permissions:

Bash
sudo chmod a-w /var/www/drupal/web/sites/default/settings.php
sudo chmod 750 /var/www/drupal/web/sites/default
sudo find /var/www/drupal -type d -exec chmod 750 {} \;
sudo find /var/www/drupal -type f -exec chmod 640 {} \;

Restore the writable permissions required by Drupal's public files directory:

Bash
sudo find /var/www/drupal/web/sites/default/files -type d -exec chmod 770 {} \;
sudo find /var/www/drupal/web/sites/default/files -type f -exec chmod 660 {} \;

The intended model is that the deployment user owns the Drupal code while the www-data group can read the application and write only to directories that actually require web-server write access. Drupal's security guidance recommends preventing the web server from modifying the code it executes while allowing appropriate write access to the files directory.

14. Configure Trusted Host Patterns

Edit Drupal's settings file: sudo nano /var/www/drupal/web/sites/default/settings.php

Add:

PHP
$settings['trusted_host_patterns'] = [
  '^example\.com$',
  '^www\.example\.com$',
];

Replace the domains with your actual hostnames. Save the file and verify that Drupal can still load correctly.

15. Configure the Firewall

Allow SSH, HTTP, and HTTPS, then enable UFW and check the rules:

Bash
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Always make sure SSH is allowed before enabling the firewall.

16. Configure PHP

Check the active PHP configuration:

Bash
php --ini

Depending on the site's workload, you may configure values such as:

  • memory_limit = 256M
  • upload_max_filesize = 64M
  • post_max_size = 64M
  • max_execution_time = 120

These are example production values rather than universal Drupal requirements. Choose values appropriate for your modules, uploads, traffic, and available server resources.

After changing PHP-FPM configuration, restart it: sudo systemctl restart php8.3-fpm.

17. Check PHP OPcache

Check whether OPcache is loaded:

Bash
php -m | grep -i opcache

If you modify OPcache settings: sudo systemctl restart php8.3-fpm.

OPcache should be configured according to the application's workload and the server's available memory.

18. Install Drush and Configure Drupal Cron

Move to the Drupal project, install Drush through Composer, check the installation, and run Drupal cron manually first:

Bash
cd /var/www/drupal
composer require drush/drush
vendor/bin/drush status
vendor/bin/drush cron

If the command completes successfully, configure a system cron job. For this deployment, run the Drupal cron process as the same user that runs PHP-FPM: www-data.

Edit the www-data user's crontab:

Bash
sudo crontab -u www-data -e

Add this line. This runs Drupal cron every 15 minutes:

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

Running the job under the web-server user keeps files created by Drupal cron within the same ownership context as files created by PHP-FPM. Drupal's own cron documentation shows that a dedicated web/application user can be used for scheduled Drush execution, while Drupal's permissions guidance emphasizes keeping writable site data accessible to the web-server process.

Warning Do not use sudo crontab -e for this Drupal cron job because that creates the scheduled process under root.

19. Check Drupal's Status Report

Log in to Drupal and open: Administration → Reports → Status report.

Review the report for:

  • PHP requirements
  • Database configuration
  • File permissions
  • Trusted host configuration
  • Security warnings
  • Update information
  • Configuration problems

Resolve critical issues before considering the deployment production-ready.

20. Configure Backups

A production Drupal installation should have both database and file backups.

Create a database backup:

Bash
mariadb-dump -u drupaluser -p drupal > drupal-backup.sql

Important Drupal site data includes: /var/www/drupal/web/sites/

The Composer files and dependency definitions should also be retained so the application can be reconstructed.

Do not store the only copy of your backups on the same dedicated server.

A proper backup strategy should account for:

  • Server failure
  • Disk failure
  • Accidental deletion
  • Database corruption
  • Security incidents

Test restoration periodically rather than assuming that a successful backup command guarantees recoverability.

21. Monitor the Dedicated Server

Monitor the underlying server as well as Drupal. Important metrics include:

  • CPU utilization
  • Memory usage
  • Disk capacity
  • Disk I/O
  • PHP-FPM activity
  • Nginx activity
  • MariaDB performance
  • Network traffic
  • System logs

Useful commands include:

Bash
top
free -h
df -h

Check the status of your essential services:

Bash
systemctl status nginx
systemctl status php8.3-fpm
systemctl status mariadb

22. Keep Drupal Updated

Use Composer to manage Drupal core and its dependencies.

Before applying updates:

  1. Back up the database.
  2. Back up important site files.
  3. Review dependency changes.
  4. Check contributed module and theme compatibility.
  5. Apply updates.
  6. Run required database updates.
  7. Clear caches when necessary.
  8. Check the Drupal Status report.

For Drupal core updates:

Bash
cd /var/www/drupal
composer update "drupal/core-*" --with-all-dependencies

Always review the resulting dependency changes before applying them to a production environment.

23. Final Production Checklist

Before going live, verify:

Conclusion

Installing Drupal 11 on a dedicated server requires more than installing a web server, PHP, and a database. A production deployment should also address database hardening, filesystem permissions, HTTPS, firewall configuration, trusted hosts, scheduled maintenance, backups, and server monitoring.

This setup uses Drupal's Composer-based project structure with /web as the public document root, keeping Composer dependencies outside the web-accessible directory.

The Nginx configuration also separates Drupal Image Styles from normal static assets, prevents PHP execution in public file directories, blocks hidden files, and verifies requested PHP files before passing them to PHP-FPM.

Finally, running scheduled Drupal cron tasks as the web-server user keeps Drupal-generated writable files within the same ownership context as PHP-FPM.

With these controls in place, the dedicated server provides a cleaner and more secure foundation for running a production Drupal website.

Need a Reliable Server for Drupal 11?

A fast CMS requires bare-metal performance that won't bottleneck under heavy visitor traffic or database queries.

Looking for the perfect hardware to run your web applications? Explore fitservers.com for high-performance, cost-effective dedicated servers. Whether you are running a single corporate site or multiple heavy web platforms, Fit Servers gives you the raw power and dedicated bandwidth required to keep your sites fast, secure, and always online.

Configure your dedicated server today!