Requirements and Hosting

This content was generated with the assistance of AI and should be verified against the official Drupal documentation before being relied on in production.

This sub-section’s Drupal bibliography lists the reference material consulted while preparing these pages.

This page was checked on 2026-10-30 against the Drupal 11.4.x line (the version to start a new site on), with Drush 13 or 14 and PHP 8.3 or later. It also lists the numbers for Drupal 10.4 to 10.6 (supported until 2026-12-09, when Drupal 10 reaches end of life) and for Drupal 12, which is scheduled for the week of 2026-12-07 and is therefore still in beta. Do not start a new site on Drupal 10.

It is written for administrators preparing a server and for site owners choosing a host. It lists what Drupal needs (PHP, extensions, memory, database, web server, Composer), where the official sources disagree, and the common hosting types for the Meridian Museum site. Installing the site is covered in Installation with Composer and DDEV, the release cycle in Versions, Release Cycle and Support, and container setups in Docker and Containers.

The requirements at a glance

Component Drupal 11.4 (recommended for new sites) Drupal 12 (scheduled)

PHP

8.3, 8.4 or 8.5

8.5, with argon2 support

MySQL

8.0 or later

8.0 or later

MariaDB

10.6 or later

10.11 or later

PostgreSQL

16 or later, with the pg_trgm extension

18 or later according to the requirements page (see below)

SQLite

3.45 or later

3.45 or later

Web server

Apache 2.4.7 or later, or nginx

Apache 2.4.7 or later, or nginx

Composer

2.7.0 or later

The latest secure release

PHP memory_limit

At least 64M; 128M to 256M is typical in production

Same

The authoritative pages are the System requirements section of the Drupal documentation (PHP, database server, web server, memory, Composer and shared-hosting pages). The User Guide’s server requirements concept page is a shorter overview. Where a number below differs between sources, both values are given.

PHP versions per core minor

Drupal supports a PHP version only for the core minors marked in the PHP requirements page, so the PHP version often decides when a site must upgrade core.

PHP 10.4 10.5 10.6 11.1 11.2 11.3 11.4 12.0

8.1

yes

yes

yes

no

no

no

no

no

8.2

yes

yes

yes

no

no

no

no

no

8.3

yes

yes

yes

yes

yes

yes

yes

no

8.4

yes

yes

yes

yes

yes

yes

yes

no

8.5

no

no

no

no

no

yes

yes

yes

8.6

no

no

no

no

no

no

no

no

In prose: Drupal 10.4 to 10.6 run on PHP 8.1 to 8.4; Drupal 11.3 and 11.4 run on PHP 8.3 to 8.5; Drupal 12 requires PHP 8.5. Drupal 10 needs at least PHP 8.1, and 8.1.6 or later is recommended. Drupal 11 needs at least PHP 8.3. PHP 8.6 is not supported yet; the PHP requirements page tracks it in a core issue.

Practical consequences:

  • A site on Drupal 10.6 and PHP 8.1 or 8.2 must raise PHP to 8.3 or later before moving to Drupal 11. Do it in that order: first PHP 8.3 on Drupal 10.6, then core.

  • Drupal 11.3 is the first minor that runs on PHP 8.5, and it is the minimum that a Drupal 12 upgrade accepts, so a site on 11.3 or 11.4 with PHP 8.5 is already on the Drupal 12 platform.

  • Contributed modules and Drush have their own PHP constraints. Check composer why-not php 8.5 before changing the PHP version of a site.

Older books such as Drupal 10 Development Cookbook (2023) and Drupal 10 Masterclass (2023) state PHP 8.1 as the target, MariaDB 10.3, MySQL 5.7.8, PostgreSQL 12 and SQLite 3.26 as the database minimums, and one book shows a platform.php value of 8.0.7, which is below the minimum of Drupal 10 itself. Those numbers describe Drupal 10.0 and 10.1; for Drupal 11 they are all too low.

PHP extensions, settings and memory

Drupal core needs these PHP extensions:

  • PDO, with the native extension (the PECL version is not compatible), and one database driver: pdo_mysql (MySQL and MariaDB), pdo_pgsql or pdo_sqlite

  • xml (which brings dom), json and gd for image styles (ImageMagick is supported through contributed modules)

  • mbstring for installation in non-English languages and for multilingual sites, such as the Meridian Museum site in English and Spanish

  • curl for automated testing and several contributed modules, and openssl

  • argon2 support in the PHP build on Drupal 12 (most PHP 8.5 builds have it), because Drupal 12 hashes passwords with argon2id by default

OpenSSL matters more than it looks. The Update Manager fetches update data over HTTPS, so a PHP without OpenSSL, or without usable CA certificates, produces "Failed to fetch available update data" on the Status report. The guide on the OpenSSL requirement lists checking connectivity and proxy settings, enabling the extension in php.ini, pointing curl.cainfo or openssl.cafile at a CA bundle, and, as a last resort, setting $settings['update_fetch_with_http_fallback' = TRUE;]. That fallback allows plain HTTP and exposes the site to man-in-the-middle attacks, so it is not for production.

Memory has two numbers:

  • the server needs about 1 GB of RAM in total for Drupal; a Drupal 10 site with about 30 contributed modules peaked near 200 MB during composer update, and the documentation recommends 1 GB free for Composer to be safe

  • the PHP memory_limit has a floor of 64M (Drupal warns below it), and 128M or 256M is typical in production. Media-heavy sites such as a museum image archive, Search API indexers and Drush batch commands need more

A php.ini fragment for a production web pool:

; /etc/php/8.4/fpm/conf.d/90-meridian.ini
memory_limit = 256M
upload_max_filesize = 32M
post_max_size = 40M
max_execution_time = 60
; Drupal 10 and 11 read plugin metadata from PHP attributes and annotations.
opcache.enable = 1
opcache.memory_consumption = 192
opcache.max_accelerated_files = 20000
opcache.save_comments = 1
; Xdebug (development machines only)
; xdebug.max_nesting_level = 256

opcache.save_comments must stay at 1 (the default): with the comments stripped, annotation-based plugin discovery stops working, and annotations are still used by older contributed modules. On development machines, Xdebug needs xdebug.max_nesting_level = 256 on versions before 2.3 and xdebug.show_exception_trace = 0 so that the installer does not crash. The PHP command-line binary that runs Drush and Composer reads a different php.ini than the web pool (php --ini shows which); give it a higher or unlimited memory_limit for long batch operations. General PHP configuration is covered in Configuration and Extensions and OPcache in OPcache, JIT and Performance.

Databases per major

Drupal 10, 11 and 12 differ in database minimums:

Database Drupal 10 Drupal 11 Drupal 12

MySQL

5.7.8 or later

8.0 or later

8.0 or later

MariaDB

10.3.7 or later

10.6 or later

10.11 or later

PostgreSQL

12 or later

16 or later

18 or later (requirements page)

SQLite

3.26 or later

3.45 or later

3.45 or later

Notes that apply to each database:

  • MySQL and MariaDB need InnoDB as the primary storage engine and the PDO extension. Both are supported by the same driver. Some contributed modules contain MySQL-specific code, which is reported in the module’s own issue queue.

  • PostgreSQL needs the pg_trgm extension in all three majors. Create it as a sufficiently privileged user while connected to the Drupal database itself; creating it in another database on the same server does not help.

  • SQLite is meant for small sites, tests and demonstrations. Drupal 10 additionally needs the json1 extension.

  • Microsoft SQL Server and MongoDB are available through contributed modules only.

The Drupal 12 PostgreSQL discrepancy. The database requirements page lists PostgreSQL 18 or later for Drupal 12. The 12.0.0 alpha and beta 1 release notes state a different floor (19 is named there as the target, with 18 the current stable version), and the 12.0.0-beta2 installer check and INSTALL.txt are reported to require PostgreSQL 16. Until the final 12.0.0 release settles it, a site that must run on PostgreSQL should plan for 18 and treat anything older as unconfirmed. MariaDB 10.11 is the Drupal 12 minimum in the same sources; PHP 8.5 is required with argon2.

Create the Meridian Museum database on MariaDB with a UTF-8 character set (the placeholder password is replaced by a value from the environment):

mariadb -u root -p <<'SQL'
CREATE DATABASE meridian CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'meridian'@'localhost' IDENTIFIED BY 'CHANGE_ME';
GRANT ALL PRIVILEGES ON meridian.* TO 'meridian'@'localhost';
FLUSH PRIVILEGES;
SQL

For PostgreSQL the equivalent step is the extension, run inside the database:

CREATE EXTENSION IF NOT EXISTS pg_trgm;

Transaction isolation

On MySQL and MariaDB, Drupal recommends the READ COMMITTED transaction isolation level. The server default, REPEATABLE READ, is supported but can cause table deadlocks that slow the site or make it unresponsive; the Status report (/admin/reports/status) warns when it detects it. READ UNCOMMITTED and SERIALIZABLE are unsupported. Set the level on the server rather than in Drupal, because the settings.php option runs a statement on every request:

# /etc/mysql/conf.d/meridian.cnf
[mysqld]
transaction_isolation = "READ-COMMITTED"
// settings.php: works, but costs one extra query per request.
$databases['default']['default']['isolation_level'] = 'READ COMMITTED';

READ COMMITTED supports only row-based binary logging, so a server that used mixed logging switches to row logging, which needs more storage. On MariaDB and on MySQL 5.7.19 and earlier the setting is named tx_isolation.

Web servers

Drupal runs on Apache and nginx, on Linux, Unix, macOS and Windows.

  • Apache must be 2.4.7 or later. mod_rewrite is required for clean URLs, mod_headers for serving gzip-compressed CSS and JavaScript, and the virtual host needs AllowOverride All so that Drupal’s .htaccess is honoured. When MultiViews is on by default, add -MultiViews to Options. The .htaccess shipped with core also blocks access to private files and sets the PHP flags, which only apply when PHP runs as an Apache module; with PHP-FPM those settings go in the pool configuration. The 11.4.11 and 10.6.20 versions of that file replace the removed assert.active setting with zend.assertions.

  • nginx is supported from a very old 1.1, and any current release works. Because .htaccess is Apache-only, every protection in it (denying .yml, .twig and other source files, blocking PHP in the files directory, denying private files) must be recreated in the server block.

  • IIS: as of Drupal 11.0 Drupal does not support Microsoft IIS. Sites on IIS must move to Apache or nginx before upgrading from Drupal 10.

  • Windows: running Drupal directly on Windows in production is deprecated from 11.3 because there is no automated Windows test environment and few developers who can test on it. Use Linux, or Linux on Windows through WSL or containers; Windows remains fine for development (DDEV runs on it through WSL2).

  • The PHP built-in web server (php -S) is for development and testing only.

A virtual host for the production site:

<VirtualHost *:443>
    ServerName www.meridian-museum.example
    DocumentRoot /var/www/meridian/web

    <Directory /var/www/meridian/web>
        AllowOverride All
        Options -Indexes -MultiViews +FollowSymLinks
        Require all granted
    </Directory>

    <FilesMatch "\.php$">
        SetHandler "proxy:unix:/run/php/php8.4-fpm.sock|fcgi://localhost"
    </FilesMatch>

    SSLEngine on
    SSLCertificateFile /etc/ssl/certs/meridian.pem
    SSLCertificateKeyFile /etc/ssl/private/meridian.key
</VirtualHost>

The same site on nginx, with the protections that .htaccess provides on Apache:

server {
    listen 443 ssl;
    server_name www.meridian-museum.example;
    root /var/www/meridian/web;
    index index.php;

    location ~ \..*/.*\.php$ { return 403; }
    location ~ ^/sites/.*/private/ { return 403; }
    location ~ (^|/)\. { return 403; }

    location / {
        try_files $uri /index.php?$query_string;
    }

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

    location ~ ^/sites/.*/files/styles/ {
        try_files $uri @rewrite;
    }

    location @rewrite {
        rewrite ^ /index.php;
    }
}

HTMX headers behind proxies and CDNs (11.3 and later)

Drupal 11.3 added built-in HTMX support, which drives parts of the core UI with HTTP headers. Any reverse proxy, CDN or web application firewall in front of the site must pass them unchanged, otherwise dynamic parts of the interface stop working. The documented request headers are HX-Boosted, HX-Current-URL, HX-History-Restore-Request, HX-Prompt, HX-Request, HX-Target, HX-Trigger-Name and HX-Trigger. The response headers are HX-Location, HX-Push-Url, HX-Redirect, HX-Refresh, HX-Replace-Url, HX-Reswap, HX-Retarget, HX-Reselect, HX-Trigger, HX-Trigger-After-Settle and HX-Trigger-After-Swap. A strict header allow-list on a CDN is the usual cause of "works locally, breaks behind the CDN" reports. The Ajax and HTMX layer is covered in AJAX and HTMX.

Composer

Composer is the only supported way to install and update Drupal code. The minimum is 2.3.6 for Drupal 10 (needed by Automatic Updates), 2.7.0 for Drupal 11 (because of a February 2024 security fix), and the latest release for Drupal 12. Run Composer with the same PHP version as the web server, and give the process enough memory; Composer is the most memory-hungry step of a deployment. Pin the platform so that a developer’s newer PHP cannot resolve packages the production PHP cannot run:

{
    "name": "meridian/museum-site",
    "type": "project",
    "require": {
        "php": ">=8.3",
        "drupal/core-recommended": "^11.4",
        "drupal/core-composer-scaffold": "^11.4",
        "drush/drush": "^13"
    },
    "config": {
        "platform": { "php": "8.3.0" },
        "sort-packages": true
    }
}

Uploading a module or theme from the UI was removed in Drupal 10.4 and 11.0, and updating from the UI in 11.2; the Package Manager behind Automatic Updates and Project Browser still needs a writable codebase, which affects hosting choice (see the next section). drupal/legacy-project is abandoned as of 11.4, so use drupal/recommended-project.

Where the code lives: the web root

With drupal/recommended-project the public folder is web/, and vendor/, composer.json and the configuration sync directory sit above it. The host must therefore point the document root at web/.

flowchart TD A["Project root: composer.json, vendor, config/sync"] --> B["web/ (document root)"] B --> C["index.php, core/, modules/, themes/"] B --> D["sites/default/settings.php"] B --> E["sites/default/files"] A --> F["private/ outside the web root"]

Relocated web root on shared hosting

Many shared plans serve a fixed public_html folder that cannot be changed. The Drupal documentation lists four approaches, from cleanest to least recommended:

  1. Set the document root to web/ in the control panel (cPanel addon domains, Plesk hosting settings, Hetzner’s konsoleH). Only an administrator can change the primary domain’s root in cPanel, so ask the host.

  2. Redirect with .htaccess in public_html. This does not work during installation, emergency rebuilds or UI package downloads. For Drupal 11.3 and earlier the rewrite is combined with a SCRIPT_NAME fix at the end of settings.php. For Drupal 11.4 that workaround breaks: the documented replacement sets a DRUPAL_WEBROOT environment variable and adds a block to web/autoload_runtime.php that strips the prefix from SCRIPT_NAME. The page marks the 11.4 recipe as not thoroughly tested.

  3. Use a subdomain whose document root is web/, with a redirect from the main domain.

  4. Symlink public_html to web/, which needs SSH and is not recommended: during maintenance a URL such as /public_html/core/ can expose the folder the symlink points to.

An outline of the redirecting .htaccess for Drupal 11.4 (the documented block in web/autoload_runtime.php is also required):

# public_html/.htaccess
RewriteEngine On
RewriteRule .* - [E=DRUPAL_WEBROOT:/web]
RewriteCond %{REQUEST_URI} !^/web/
RewriteRule ^(.*)$ /web/$1 [L]

Because the shared-hosting recipes alter Drupal’s entry scripts and cannot be used for installs, a site that matters should not rely on them. The security consequences of exposing vendor/ or composer.json are covered in Security Hardening.

Choosing a hosting type

Type Fits Limits

Shared hosting

Small brochure sites, a tight budget

No SSH or Composer on many plans, fixed document root, low PHP memory, no Drush, hard to keep a writable codebase; Meridian’s meridian:sync queue worker cannot run without real cron

VPS or dedicated server

Full control: PHP-FPM, Redis, Varnish, cron, Drush, Composer deployment

You patch the operating system, PHP and the database, and monitor the server

Managed Drupal platform

Drupal-specific hosts and PaaS (Acquia, Pantheon, Platform.sh/Upsun, Amazee.io and others): Composer builds, environments per branch, backups, CDN, security patching of the stack

Cost; the build process and read-only code file system dictate how deployments and Automatic Updates work

Containers

Reproducible stacks with Docker, Compose or Kubernetes: the same image from DDEV to production

You own the orchestration; files, sessions and cron need shared services

Evaluate a host against the minimums above: a PHP version matching your core minor, the database version, Composer and SSH access (or a build pipeline), a cron that can run every few minutes, enough memory_limit, HTTPS, backups, and a way to deploy settings.php and environment secrets without storing them in Git. The shared-host document root problem and the HTMX header allow-list are the two issues that most often surprise teams. Container images are explained in Docker getting started and Compose for local development.

For local development, DDEV reproduces the production stack with one file:

# .ddev/config.yaml
name: meridian
type: drupal11
docroot: web
php_version: "8.4"
webserver_type: nginx-fpm
database:
  type: mariadb
  version: "10.11"
composer_version: "2"

Common errors and pitfalls

  • The installer reports "Your PHP version is too low" because the command-line PHP and the web PHP differ. Compare php -v with phpinfo().

  • "Failed to fetch available update data" is a missing OpenSSL extension, a broken CA bundle or a blocked outbound connection, not a Drupal bug.

  • The Status report shows REPEATABLE READ: change the server setting as described above.

  • PostgreSQL install fails because pg_trgm was created in the postgres database instead of the Drupal one.

  • Clean URLs fail on Apache because mod_rewrite is off or AllowOverride is None.

  • The .htaccess is absent after copying a project (hidden file), so the site appears to work but is unprotected.

  • A CDN strips HTMX headers and parts of the 11.3 or later UI stop updating.

  • A site on IIS or on Windows in production cannot follow core beyond the deprecation; plan the move to Linux.