settings.php, services.yml and Environments
|
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. |
Two files in web/sites/default/ decide how a Drupal site behaves on a given machine: settings.php, a PHP file that fills
the $databases, $settings and $config variables, and services.yml, a YAML file that overrides parameters of the
service container. Everything that differs between a laptop, a staging server and production — the database host, the
trusted host names, the directory where configuration is exported, the proxy in front of the web server, whether Twig
caches templates — belongs in one of them, and everything secret in them must come from the environment instead of
from a committed file. This page explains each setting, how the files are loaded, and how the Meridian Museum site keeps one
codebase for all of its environments.
It was written on 2026-10-30 against Drupal 11.4.x (the current minor), Drush 13/14 and PHP 8.3 or later. Drupal 10 reaches
end of life on 2026-12-09, so new sites should not start on it; where Drupal 10 or the scheduled Drupal 12 differs, the
text says so. The Meridian Museum is a fictitious regional science museum whose module is meridian_collection, whose theme
is meridian and whose recipe is meridian_events. For installing the codebase and DDEV see
Installation with Composer and DDEV, for the folder layout see
Project layout and local development, and for the
production-hardening checklist see Security hardening.
The three places a setting can live
Drupal has three configuration stores with very different lifetimes, and choosing the wrong one is the most frequent mistake on this topic.
-
Active configuration (the
configtable, exported to YAML withdrush config:export) holds site-builder choices that travel between environments: content types, views, the site name, module settings. It is managed in Configuration management. -
$settingsinsettings.phpholds low-level, per-environment infrastructure settings read very early, before the container exists. It is read withSettings::get()and is never exported. Database credentials, the hash salt, paths and proxy addresses live here. -
$configoverrides insettings.phpreplace individual values of configuration objects at runtime, for example$config['system.site']['name'], without changing what is stored or exported. They are the right tool for an environment-specific value of a configuration key (an API endpoint, a mail transport).
services.yml is the fourth place, and a different kind: it sets container parameters (Twig behaviour, session cookie
options, CORS) and can replace service definitions. Container parameters are compiled into the cached container, so
changing them requires a cache rebuild.
How settings.php is found and loaded
When the request reaches index.php, the kernel locates the site directory (sites/default unless sites/sites.php maps
the host name to another directory, see Multisite) and includes its settings.php. The variables
$databases, $settings, $config and $app_root/$site_path it defines are then captured: $settings becomes the
immutable Drupal\Core\Site\Settings object, so a setting can be read anywhere with
Settings::get('name', $default) but not changed at runtime.
The installer creates settings.php by copying default.settings.php (the file shipped in web/sites/default/ and, from
the scaffold, in core/assets/scaffold/files/), writes the database connection and a generated hash_salt into it, and
makes it read-only. default.settings.php is the authoritative documentation of every setting: it is long, and most of
the settings are commented out with an explanation. Reading it for your exact core version is the best way to find
settings added after this page was written. Never edit default.settings.php itself; copy what you need into settings.php.
Two practical consequences follow.
-
After installation
settings.phpand thesites/defaultdirectory are made non-writable, and the status report warns when they are writable. To edit the file during development, make it writable, edit, and restore the permissions; a deployment script should set them again on every release. A hardened layout is640forsettings.phpand750forsites/default, with thefilesdirectory writable by the web server only. -
Because the file contains credentials, it must not be readable by other system users and must never be committed with real values. The usual pattern is to commit a
settings.phpthat contains no secrets and reads them from the environment, plus an include for an uncommittedsettings.local.php.
The settings.php of the Meridian Museum site
The following file is committed to the Meridian repository. It contains no secret: the database password, the hash salt and the site-specific host name come from environment variables set by DDEV locally and by the hosting platform elsewhere.
<?php
/**
* @file
* Meridian Museum site settings. Committed; contains no secrets.
*/
// Database connection. All values come from the environment.
$databases['default']['default'] = [
'driver' => 'mysql',
'database' => getenv('MERIDIAN_DB_NAME') ?: 'meridian',
'username' => getenv('MERIDIAN_DB_USER') ?: 'meridian',
'password' => getenv('MERIDIAN_DB_PASSWORD') ?: '',
'host' => getenv('MERIDIAN_DB_HOST') ?: 'db',
'port' => getenv('MERIDIAN_DB_PORT') ?: '3306',
'prefix' => '',
'namespace' => 'Drupal\\mysql\\Driver\\Database\\mysql',
'autoload' => 'core/modules/mysql/src/Driver/Database/mysql/',
];
// Secret used for one-time login links, form tokens and CSRF tokens.
// Must be identical on every web server of the same site.
$settings['hash_salt'] = getenv('MERIDIAN_HASH_SALT');
// Configuration is exported here, outside the web root.
$settings['config_sync_directory'] = '../config/sync';
// Private files (member-only documents) live outside the web root.
$settings['file_private_path'] = getenv('MERIDIAN_PRIVATE_PATH') ?: '../private';
// Only answer for the museum's own host names.
$settings['trusted_host_patterns'] = [
'^www\.meridian-museum\.example$',
'^meridian-museum\.example$',
'^meridian\.ddev\.site$',
];
// Load the services.yml of this site (the installer enables this line).
$settings['container_yamls'][] = $app_root . '/' . $site_path . '/services.yml';
// Environment-specific overrides, never committed.
if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
include $app_root . '/' . $site_path . '/settings.local.php';
}
Each block is explained below.
$databases
$databases is an array indexed by connection key, then by target. default/default is the connection used by everything
unless code asks for another. Additional keys (a migrate connection pointing to the old Drupal 7 database, an external
connection to a reporting database) are read with \Drupal\Core\Database\Database::getConnection('default', 'migrate');
see Database API and entity queries and
Migrating from Drupal 7. A second target on the same key, such as
$databases['default']['replica'][], lets queries tagged replica be served from a read replica.
The keys that matter:
-
driver:mysql(covers MySQL and MariaDB),pgsqlorsqlite. Since 10.2 each driver is a module in core (mysql,pgsql,sqlite) and contributed drivers are installed the same way. -
namespaceandautoload: the PSR-4 namespace of the driver and the directory the class loader must register for it before modules are available. The installer writes them; if you copy a connection by hand, copy them too, or the installer fails with a "driver not found" error. For PostgreSQL the namespace isDrupal\\pgsql\\Driver\\Database\\pgsql. -
prefix: a table prefix, kept for compatibility. A prefix does not isolate sites securely; use a separate database or schema per site. -
host,port,database,username,password: the connection. Withunix_socketthe host is ignored. -
init_commands,pdoandisolation_level(MySQL family): optional session initialisation and PDO options. Core already sets a safe SQL mode.
Database versions matter for new sites. The Drupal 11 minimums are MariaDB 10.6, MySQL 8.0, PostgreSQL 16 (with the pg_trgm
extension) and SQLite 3.45; Drupal 10 accepted MariaDB 10.3.7, MySQL 5.7.8 and PostgreSQL 12. Drupal 12 raises them to MariaDB
10.11, MySQL 8.0 and PostgreSQL 18 on the system requirements page, while the announcement of the first Drupal 12 beta lists
different PostgreSQL and MariaDB values, so confirm them against the release notes of the final 12.0.0 before provisioning.
Older books such as Drupal 10 Development Cookbook (2023) and Drupal 10 Masterclass (2023) target Drupal 10.0 with
MariaDB 10.3 or MySQL 5.7.8; those database versions cannot run Drupal 11 at all. See
Requirements and hosting.
Pitfalls: a password containing characters that are special to a shell is not a problem in PHP, but is when the value is
passed through a .env file or a Compose file without quoting; a missing namespace produces an installer error that looks
like a missing PDO driver; and the database is not created by Drupal, so on production it must exist before the first request.
Source: Database server requirements
hash_salt
$settings['hash_salt'] is a long random string mixed into the hashes Drupal creates for one-time login and password
reset links, form and CSRF tokens, image style derivative tokens, private-file URL tokens and several caches. The installer
generates one. Three rules apply.
-
It must not be empty. An empty salt makes Drupal refuse to boot, because the tokens would be guessable.
-
It must be the same on all web servers of a cluster, otherwise a form rendered by one server is rejected by another and login links fail.
-
Changing it invalidates every outstanding one-time login link, form token and image style token (styles are regenerated). That is acceptable after an incident and disruptive otherwise.
Reading it from the environment keeps it out of the repository and lets every environment have its own value. Generate a
value once, store it in the platform’s secret store, and expose it as MERIDIAN_HASH_SALT. A throw-away value for a local
site can be produced and written to the DDEV environment like this:
# Print a 64-byte random value, then store it as a DDEV project variable.
php -r 'echo bin2hex(random_bytes(48)), PHP_EOL;'
ddev config --web-environment-add=MERIDIAN_HASH_SALT=CHANGE_ME_USE_THE_GENERATED_VALUE
ddev restart
A related but separate knob lives in services.yml: password.algorithm selects how user passwords are hashed. The default
PASSWORD_DEFAULT is bcrypt on current PHP, and the file documents argon2id as an alternative. Drupal 12 changes the
default to argon2id, which needs PHP built with argon2 support. The salt above has nothing to do with password hashes.
trusted_host_patterns
A request carries the host it was addressed to in the Host header, which the client controls. Drupal builds absolute URLs
from it — password reset links, canonical links, redirects — so a forged header can poison cached pages or send a
reset link to an attacker. $settings['trusted_host_patterns'] is Drupal’s allow-list, enforced through Symfony’s
trusted host mechanism: an array of regular expressions, written without delimiters, that must match the host name.
Rules and pitfalls:
-
Anchor every pattern with
^and$and escape dots (\.); an unanchoredmeridian-museum.examplealso matchesmeridian-museum.example.evil.test. -
A request whose host matches none of the patterns is answered with a 400 error, "The provided host name is not valid for this server". The most frequent cause is forgetting the local host name (
meridian.ddev.site,localhost) or the load balancer’s internal name used by health checks. -
The status report shows "Trusted Host Settings" as an error until the setting exists. Do not silence it with a pattern like
.*, which defeats the protection. -
A pattern can be built from the environment so one file serves several environments, for example
'^' . preg_quote(getenv('MERIDIAN_HOST')) . '$'.
Source: Trusted host settings
config_sync_directory
$settings['config_sync_directory'] is the folder drush config:export writes to and drush config:import reads from. Since
Drupal 8.8 the setting is required and no longer defaults to a randomly named directory under sites/default/files; if it
is missing, the status report and the configuration synchronisation screen at /admin/config/development/configuration show
an error. Use a path outside the web root, relative to the Drupal root or absolute, and put it in version control:
// Relative to the web directory (the Drupal root): the project's config/sync folder.
$settings['config_sync_directory'] = '../config/sync';
The installer writes this line when a path is supplied, and drush site:install --existing-config reads from it. Drupal
refuses to import a configuration set whose site UUID differs from the site’s own, so a new environment must be installed
from the same exported configuration or have its UUID aligned with drush config:set system.site uuid. A second
setting, $settings['config_exclude_modules'], lists modules (for example devel and stage_file_proxy) that are excluded
from export and import so a development-only module never reaches production; Drupal does not validate the list, and
excluding a module that other configuration depends on yields an unimportable set. Full workflows, split configuration and
the deployment order are in Configuration management and
Deployment and CI/CD.
file_private_path, public and temporary files
Drupal has three file locations, each configurable in settings.php.
-
Public files (
$settings['file_public_path'], defaultsites/default/files) are served directly by the web server. Never leave PHP execution enabled there; the generated.htaccessblocks it on Apache and the nginx example in Security hardening does the same. -
Private files (
$settings['file_private_path']) are served only through Drupal, after an access check. The directory must be absolute or relative to the Drupal root, outside the web root, and writable by the web server. If it is unset, theprivate://stream wrapper does not exist and private file fields fail with an unwritable-destination error. -
Temporary files (
$settings['file_temp_path'], default the operating-system temporary directory) must be absolute and must not be served by the web server. On a multi-server deployment every server needs a temporary directory large enough for uploads, but public and private paths should point to shared storage; see File system and private files.
On the Meridian site members download exhibition catalogues (PDFs) from a private field, so the path is set per environment:
// Production: a mounted volume shared by all web servers.
$settings['file_private_path'] = '/var/meridian/private';
// Local DDEV: the project's private folder next to web/.
// $settings['file_private_path'] = '../private';
Source: File module overview
The two permission settings $settings['file_chmod_directory'] (default 0775) and $settings['file_chmod_file']
(default 0664) control the mode of files and directories Drupal creates and can be tightened to 0770 and 0660.
Reverse proxies, load balancers and CDNs
Most production sites sit behind a proxy that terminates TLS: a load balancer, Varnish, Cloudflare or a Kubernetes ingress.
The web server then sees every request as coming from the proxy over plain HTTP, so without help Drupal would record the
proxy’s address in the log and flood-control tables, generate http:// URLs and fail to set secure cookies. Three settings
describe the proxy:
// Tell Drupal it is behind a proxy it may trust.
$settings['reverse_proxy'] = TRUE;
// Every proxy address as seen by the web server (IPs or CIDR ranges).
$settings['reverse_proxy_addresses'] = ['10.0.0.10', '10.0.0.11'];
// Trust only the headers the proxy really sends.
$settings['reverse_proxy_trusted_headers'] =
\Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_FOR
| \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_HOST
| \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PORT
| \Symfony\Component\HttpFoundation\Request::HEADER_X_FORWARDED_PROTO;
Pitfalls and security implications:
-
Trust only the addresses of your own proxies. Headers such as
X-Forwarded-Forare spoofable by any client that reaches the web server directly, so the web server must accept connections only from the proxy. The default trusted-header set indefault.settings.phpis documented as not secure; list exactly the headers your proxy sets. -
When the proxy hides the original scheme, the symptoms are mixed content, a login that loops, or sessions that never become secure. Verify what Drupal sees with the Site information page and with
drush php:evalprinting the generated base URL. -
The proxy or CDN must not strip or cache-collapse the headers Drupal’s HTMX-based interactions use. From Drupal 11.3 the
HX-RequestandHX-Redirectheaders must be forwarded; a CDN that drops them breaks the partial updates core now implements with HTMX (see AJAX and HTMX). -
Cache purging rules and the
Cache-Controlheaders Drupal emits are covered in Performance and caching. -
The contributed Trusted Reverse Proxy module is an alternative for dynamic proxy ranges, such as a CDN publishing its addresses.
-
Microsoft IIS is no longer a supported web server (support was removed in Drupal 11.0); Apache 2.4.7 or later and nginx are the supported servers.
The state cache setting is gone
Older settings files, and guides that copy them, contain this block:
// Removed from default.settings.php in Drupal 11.
$settings['state_cache'] = TRUE;
$settings['state_cache'] told the State system whether to cache all of its key-value pairs in a single cache item. It
was recommended as TRUE on Drupal 10 sites, and the status report warned when it was unset. In Drupal 11.0 the opt-out
was removed: state is always cached, the setting is deprecated and ignored, and default.settings.php no longer contains
it. A settings file migrated from Drupal 10 should simply drop the line; leaving it does no harm but triggers a deprecation
message. Any book or tutorial that tells you to add it, or that lists it as a performance tweak, describes Drupal 8 to 10.
settings.local.php and environment-specific settings
settings.local.php is a convention, not a feature: the last lines of settings.php include it if it exists. Core ships
example.settings.local.php in web/sites/ as a template; copy it to web/sites/default/settings.local.php on development
machines only. It enables a development profile:
-
it adds
sites/development.services.ymltocontainer_yamls(Twig debugging, a null cache backend for rendering); -
it sets
$config['system.logging']['error_level'] = 'verbose'so errors show a backtrace; -
it turns off CSS and JavaScript aggregation;
-
it sets
$settings['rebuild_access'] = TRUE(allowing/core/rebuild.phpto clear caches) and$settings['skip_permissions_hardening'] = TRUE, which stops the system module from complaining that the site directory is writable; -
it contains commented lines to switch the render, page and dynamic page caches to
cache.backend.null, to let test extensions be discovered withextension_discovery_scan_tests, and to setconfig_exclude_modules.
These are exactly the settings that must never reach production: settings.local.php must be excluded in .gitignore
and must not exist on the production host. A production-safe pattern is to make the include conditional on an
environment variable, so a stray file cannot activate by accident:
$env = getenv('MERIDIAN_ENV') ?: 'production';
// Shared by every environment.
$config['system.performance']['cache']['page']['max_age'] = 900;
if ($env === 'local' && file_exists($app_root . '/' . $site_path . '/settings.local.php')) {
include $app_root . '/' . $site_path . '/settings.local.php';
}
if ($env === 'staging') {
// Keep staging out of search engines and away from real visitors' mail.
$config['system.logging']['error_level'] = 'some';
$config['symfony_mailer.settings']['default_transport'] = 'sendmail_disabled';
}
if ($env === 'production') {
$config['system.logging']['error_level'] = 'hide';
}
Notes on environment handling:
-
$configoverrides are visible in the configuration form pages as the overridden value but are not saved into the active configuration and never exported, which is why a production hostname or API endpoint belongs here and not in a form. Overrides are applied when a configuration object loads; reading withgetEditable()gives the un-overridden value. -
The
symfony_mailer.settingskey above is only an example of a contributed module’s configuration object; use the configuration name of the module you actually run. -
Local tooling can write its own file. DDEV generates
settings.ddev.phpwith the database credentials of the local container and makessettings.phpinclude it, and only when theIS_DDEV_PROJECTenvironment variable is present, so the samesettings.phpis harmless in production. Settingdisable_settings_management: truein.ddev/config.yamlstops DDEV from touching your settings files. Hosting platforms (Acquia, Pantheon, Platform.sh, Lagoon and others) similarly inject their own include or environment variables; follow the provider’s documentation rather than overriding them. -
Per-environment configuration that must be reviewed and exported, such as which modules are enabled on staging only, belongs in a split configuration setup rather than in
settings.php; see Configuration management.
Source: DDEV: CMS settings
Reading values elsewhere in code
Code reads $settings with the Settings class and never touches the global variable. A Meridian service that needs the
base URL of the collections-management API reads it from settings so it can differ per environment:
<?php
namespace Drupal\meridian_collection;
use Drupal\Core\Site\Settings;
/**
* Builds the base URL of the collections-management API.
*/
final class CollectionsApiEndpoint {
/**
* Returns the configured endpoint, or a safe default for local work.
*/
public static function baseUrl(): string {
return rtrim(Settings::get('meridian_collections_api_url', 'https://collections.invalid'), '/');
}
}
and settings.php or settings.local.php sets $settings['meridian_collections_api_url']. The API token for the same service is
not stored in settings or configuration at all; it is read in the service with getenv() or resolved through the Key
module. Use $settings for infrastructure that does not need an administration screen, $config for an override of an
existing configuration key, and a configuration object for anything editors or site builders must change. See
Services and dependency injection for how to inject the settings object
instead of calling the static method.
Source: Settings class on api.drupal.org
services.yml
sites/default/services.yml is created by copying default.services.yml. It is loaded because settings.php contains
$settings['container_yamls'][] = $app_root . '/' . $site_path . '/services.yml';. The file has a parameters: section and,
optionally, a services: section that overrides core services. Because the container is compiled and cached, every
change needs drush cache:rebuild (or the rebuild script); a typo in the YAML breaks the whole site, which is another
reason to keep the file small and in version control.
The parameters most often changed:
parameters:
# Twig. Keep debug off and caching on in production.
twig.config:
debug: false
auto_reload: null
cache: true
# Session cookies and garbage collection.
session.storage.options:
gc_probability: 1
gc_divisor: 100
gc_maxlifetime: 200000
cookie_lifetime: 2000000
cookie_samesite: Lax
# name_suffix: 'meridian'
# Allow user 1 to behave as an ordinary user once real administrators exist.
security.enable_super_user: false
# Leave CORS off unless a decoupled front end needs it.
cors.config:
enabled: false
What each one does:
-
twig.config.debugadds template-suggestion comments and enables the Twigdump()function.auto_reload, whennull, followsdebug;cache: falserecompiles templates on every request. All three are for development only; the development profile indevelopment.services.ymlflips them, see Twig templates and debugging.allowed_file_extensionslimits which file types Twig may load. -
session.storage.optionsare passed to PHP’s session machinery.gc_probability/gc_divisor(1 in 100 requests) andgc_maxlifetimecontrol how long idle session data survives;cookie_lifetimeis how long the browser keeps the cookie (0means until the browser closes);cookie_samesiteacceptsNone,LaxorStrictand isLaxby default;cookie_domainshares a session across subdomains, which widens the attack surface and is usually a mistake;name_suffixkeeps cookie names unique when several sites share a domain. -
security.enable_super_user(Drupal 10.3 and later) controls whether account 1 bypasses all permission checks. It istruein the shipped file for compatibility. The file’s own advice is to set it tofalseonce at least one user has the Administrator role, so a compromised or forgotten user 1 is just another user with roles. Do this only after confirming that an administrator role holds every permission; otherwise you can lock yourself out. -
renderer.config,http.response.debug_cacheability_headersandentity.memory_cache.slotstune the render pipeline, exposeX-Drupal-Cache-*headers for debugging cache metadata, and cap the entity memory cache (see Performance and caching). Cache-debug headers should be off in production. -
cors.configis the Cross-Origin Resource Sharing policy, disabled by default; a decoupled front end such as the Next.js site in Decoupled Drupal with Next.js lists its allowed origins here. -
factory.keyvalueandfactory.keyvalue.expirablechoose the key-value storage backend,queue.configtunes queue suspension, andpassword.algorithm/password.optionsselect the password hashing algorithm.
Secrets handling
Everything sensitive must stay out of the repository, out of exported configuration, out of logs and out of container images.
-
Database password, hash salt, API tokens, OAuth client secrets and AI provider keys come from environment variables (
getenv()), from a file mounted by the orchestrator, or from a secret manager the hosting platform injects. Use obvious placeholders such asCHANGE_MEin documentation and in committed example files. -
Do not store keys in configuration: configuration is exported and committed. The Key module stores a reference to a secret (an environment variable, a file, a provider) instead of the value, and modules such as the AI module use it.
-
settings.phpandservices.ymlmust not be web-readable; an exposed backup copy (settings.php.bak,settings.php~) is a common leak, and the.htaccessthat Drupal ships blocks such files on Apache. Do not leave backups insites/default. -
phpinfo(), verbose error output and the Twigdump()function disclose environment variables; keep them off in production. -
In containers, pass variables at runtime rather than baking them into the image, and prefer secret mounts to plain environment variables where the platform offers them. See Docker and containers.
-
Rotate on exposure: a leaked database password is changed at the database and in the secret store; a leaked
hash_saltis replaced (invalidating outstanding links and tokens).
Verifying the active settings with Drush
Drush shows what Drupal actually resolved, which settles most "which file won?" questions:
# Site root, database summary, PHP and Drupal versions, and the settings file in use.
ddev drush core:status
# Read a $settings value as Drupal sees it.
ddev drush php:eval 'echo \Drupal\Core\Site\Settings::get("config_sync_directory");'
# Read a configuration value including overrides, then the stored value only.
ddev drush config:get system.site name
ddev drush config:get system.site name --include-overridden
# Container parameters changed in services.yml need a rebuild.
ddev drush cache:rebuild
ddev drush php:eval 'print_r(\Drupal::getContainer()->getParameter("twig.config"));'
Source: Drush: core:status
The Status report at /admin/reports/status also checks the pieces on this page: the trusted host setting, writable
settings.php, the configuration sync directory, the private file system, the PHP and database versions and
whether Twig debugging is on. A production site should show none of the red entries. See
Logging, the status report and troubleshooting.
Common errors
| Symptom | Likely cause | Fix |
|---|---|---|
"The provided host name is not valid for this server" |
|
Add an anchored, escaped pattern for that host. |
"Drupal is unable to boot: hash_salt" |
|
Provide the variable to PHP-FPM, not only to the shell; check with |
Installer or Drush fails with a database driver error |
|
Restore the driver keys the installer writes; install |
Configuration import reports a different site UUID |
The target database was installed from a different configuration set. |
Install with |
Configuration changes made in |
The container is cached. |
Run |
Every user is logged out after a deployment |
|
Use one salt per site and keep |
All visitors share one IP address in the log |
|
Enable the proxy settings and list only trusted proxy addresses. |
Private files return "file not found" or cannot be uploaded |
|
Set an absolute path outside the web root and fix ownership. |
Version notes and corrections of older material
-
$settings['state_cache']was deprecated and removed in Drupal 11.0; older Drupal 10 material that recommends it is out of date. -
security.enable_super_userappeared in 10.3; guides before that cannot disable user 1’s super-user bypass. -
The
config_sync_directorysetting replaced the old$config_directories['sync']array in Drupal 8.8, and the array was removed in Drupal 9. -
Database driver modules (
mysql,pgsql,sqlite) are part of core since 10.2, which is whynamespaceandautoloadmust be present in$databases. -
Drupal 11.0 removed IIS support. From 11.3 a proxy or CDN in front of the site must pass the
HX-RequestandHX-Redirectheaders. -
Drupal 12 is scheduled for the week of 2026-12-07; it requires PHP 8.5 with argon2 support, defaults to argon2id password hashing and raises the database minimums, so a settings file that depends on older platform assumptions should be reviewed with Preparing for Drupal 12.
-
Drupal 10 Development Cookbook (2023) and Drupal 10 Masterclass (2023) were written for Drupal 10.0 and 10.1 on PHP 8.1 with DDEV or a classic LAMP stack. Their
settings.phpandservices.ymlcoverage is still a good map of the concepts, but they do not show the Drupal 11 database minimums, the removal ofstate_cache, the 10.3 super-user parameter, or reading secrets from the environment as the default practice. Where a book keepsdevelopment.services.ymlandsettings.local.phpon a shared server, treat that as a development-only pattern.