Drupal: Logging, the Status Report and Troubleshooting
|
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. |
When a Drupal site misbehaves, the cause is almost always already written down somewhere: in the Recent log messages
report, in the Status report, in the web server’s or PHP’s error log, or in the output of a Drush command. This page
explains where each of those lives, how Drupal’s logging works internally, how to read the Status report, what to do
about a white screen or an HTTP 500, how and when to rebuild caches, how to recover a site that broke after an upgrade
or a configuration import, and which contributed developer tools (Devel and Webprofiler) help to find a problem. The
running example is the Meridian Museum site, whose meridian_collection module synchronises collection objects from a
fictitious collections-management API.
It was checked on 2026-10-30 against Drupal 11.4.x, Drush 13 and 14 and PHP 8.3 or later. Drupal 10 reaches end of life on 2026-12-09 (see Versions, Release Cycle and Support), and Drupal 12.0.0 is planned for the week of 2026-12-07, so the sections on removed modules apply to both upgrades. The audience is the site administrator and the developer who is called when the site is down; routine scheduled work is in Cron, Queues and Maintenance, and the command-line tool is described in Drush.
A diagnosis path
Troubleshooting is faster when it follows a fixed order, from the cheapest and least risky check to the most invasive one. The order used on this page is shown below.
Two rules hold throughout. First, work on a copy (a DDEV project or a staging environment) whenever the fix involves
editing configuration or the database, and take a backup before touching production (see
Backups and Disaster Recovery). Second, remember that the cause is
frequently an incomplete deployment: code updated but drush updatedb or drush config:import not run, or the reverse.
The drush deploy command runs the correct sequence (updatedb, config:import, cache:rebuild and deploy hooks).
Where Drupal writes its logs
Drupal sends every log message to the PSR-3 logger channel service (logger.factory). Each message has a channel
(called a type in the report, for example php, cron, user or meridian_collection), a severity (the eight
RFC 5424 levels from Emergency down to Debug), a text with placeholders, a timestamp, the user, the request URL and the
client address. The factory forwards each message to every logger that is registered with the service tag logger.
Core registers two of them as modules, and the web server and PHP keep two further logs that Drupal does not control.
| Log | Provided by | Typical use |
|---|---|---|
Recent log messages ( |
Database Logging module ( |
Day-to-day review by administrators and developers on small and medium sites |
Syslog |
Syslog module ( |
Forensic and long-term logging, shipped to a central log service |
PHP error log |
PHP ( |
Fatal errors, parse errors and out-of-memory conditions that happen before Drupal can log anything |
Web server error log |
Apache, nginx or PHP-FPM |
HTTP 500 responses with no Drupal page, timeouts, permission and rewrite problems |
Recent log messages (Database Logging)
The Database Logging module stores messages in the watchdog database table and shows them under Reports > Recent log
messages at admin/reports/dblog. The report lists the type, date, message, user and operations, can be filtered by
type and severity, and links each row to a detail page with the full message, location, referrer, hostname and the
variables. The User Guide chapter that describes it is
Concept: Logging (the "Recent log messages" section of the
Prevent-and-fix chapter).
The same chapter states the limit that matters for design decisions: administrators and automated cron tasks can clear
these messages, so the report should not be used for forensic logging, and the Syslog module is the recommended
alternative for that purpose. The table is pruned on every cron run. The number of rows to keep is a setting on the
Logging and errors page (admin/config/development/logging), stored as row_limit in the dblog.settings
configuration object; the choices run from 100 up to 1,000,000 rows, or unlimited. On a busy site an unlimited table
slows the database and makes backups large. If cron does not run, the table is never pruned, which is one more reason
to monitor cron (see Cron, Queues and Maintenance).
The permission that gates the report is View site reports. It is a restricted permission because messages can contain user names, e-mail addresses and request details; grant it only to administrators (see Users, Roles and Permissions).
Writing messages from the Meridian module
A module logs through the logger.channel.<name> services. A channel is declared once in *.services.yml, then injected
like any other service (see Services and Dependency Injection).
# meridian_collection.services.yml
services:
logger.channel.meridian_collection:
parent: logger.channel_base
arguments: ['meridian_collection']
meridian_collection.object_sync:
class: Drupal\meridian_collection\ObjectSync
autowire: true
<?php
declare(strict_types=1);
namespace Drupal\meridian_collection;
use Drupal\Core\Cache\CacheBackendInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
/**
* Synchronises collection objects from the collections-management API.
*/
final class ObjectSync {
public function __construct(
#[Autowire(service: 'logger.channel.meridian_collection')]
private readonly LoggerInterface $logger,
) {}
public function importOne(string $accession_number, array $payload): void {
if (empty($payload['title'])) {
// Placeholders keep the message translatable and safe to display.
$this->logger->warning('Object @number has no title and was skipped.', [
'@number' => $accession_number,
]);
return;
}
try {
$this->save($accession_number, $payload);
$this->logger->info('Imported object @number.', ['@number' => $accession_number]);
}
catch (\Throwable $e) {
// Pass the exception under the "exception" key; Drupal's logger
// processors then add the file and line to the entry.
$this->logger->error('Import of @number failed: @message', [
'@number' => $accession_number,
'@message' => $e->getMessage(),
'exception' => $e,
]);
}
}
private function save(string $accession_number, array $payload): void {
// Create or update the collection_object entity here.
}
}
Source: Creating modules and LoggerChannelFactory API reference
Pitfalls when writing messages:
-
Use placeholders (
@nameescaped,%nameescaped and emphasised,:namefor URLs) and never concatenate user input into the message. The dblog report renders the stored message with its variables, so a concatenated value is a stored cross-site scripting risk for the administrator reading it. -
Do not log secrets, tokens, full request bodies or personal data. The
watchdogtable is readable by anyone with the report permission and is copied into every database backup. -
Choose the severity deliberately.
errorand above are the messages that monitoring should alert on; aninfomessage for every imported object produces thousands of rows per sync. -
In procedural code a
\Drupal::logger('meridian_collection')call is acceptable, but classes should receive the logger by injection so that it can be replaced in tests.
Syslog
The Syslog module (syslog) writes each message to the operating system’s syslog facility instead of, or in addition
to, the database. Its settings are on the same Logging and errors page: the identity string that prefixes each line,
the facility (a LOG_LOCAL0 to LOG_LOCAL7 value or LOG_USER) and the message format with tokens such as
!base_url, !timestamp, !type, !ip, !request_uri, !referer, !severity, !uid, !link and !message. On a
host the lines end up in /var/log/syslog or the journal, where an agent can ship them to a central service; in a
container the usual practice is to write to standard error and let the platform collect it. The two modules can run
together, and a common arrangement is Syslog for retention and alerting with Database Logging kept on a small row
limit for convenience, or switched off entirely on production sites that have central logging. The Drupal User Guide
and the community documentation describe Syslog as the better choice for forensic needs; this page does not claim a
particular throughput, which depends on the host.
Uninstalling Database Logging removes the report but also removes the only in-site record; before doing so on a site with no other logger, confirm that Syslog or an external logger is configured.
Choosing what visitors see: the error level
The same Logging and errors page controls how PHP errors and exceptions are shown to the visitor. The setting is
error_level in the system.logging configuration object:
| Value | Effect |
|---|---|
|
No error messages are shown. The right value for production. |
|
Shows errors and warnings ("Errors and warnings"). |
|
Shows errors, warnings and notices. |
|
Shows everything in |
On a production site, set hide so that stack traces, file paths and database details are not shown to visitors, who
could use them to attack the site. In a development environment, override the value in settings.php or in an
environment-specific file instead of exporting it to the shared configuration (see
settings.php and Environments):
<?php
// web/sites/default/settings.local.php (DDEV and local development only).
$config['system.logging']['error_level'] = 'verbose';
// Keep the full log of what happens locally.
$config['dblog.settings']['row_limit'] = 0;
A verbose setting left on a production site is a recurring information-disclosure finding in security reviews, so
make the settings.php override conditional on the environment, and never commit a settings.local.php that is loaded
on production.
Reading logs from the command line
Drush reads the watchdog table without a browser. The command is watchdog:show with the aliases wd-show, ws and
watchdog-show; its documentation is at drush watchdog:show.
# The ten most recent messages (the default count)
ddev drush watchdog:show
# Search the message text for a substring
ddev drush watchdog:show "Import of"
# Show 50 messages, only warnings and worse, from one channel
ddev drush watchdog:show --count=50 --severity-min=Warning --type=meridian_collection
# Only PHP errors, with the location and hostname columns
ddev drush watchdog:show --type=php --extended
# Follow new messages as they arrive (a separate command)
ddev drush watchdog:tail
# Machine-readable output for a script
ddev drush watchdog:show --count=100 --format=json
Source: Drush: watchdog:show
The options are --count (default 10), --severity for one level (a number or a name), --severity-min for that
level or worse, --type for one channel, --extended for extra columns, --fields and --field to choose columns,
--filter for an expression such as type!=locale, and --format for the output format. drush watchdog:tail prints
new entries as they arrive, and drush watchdog:delete empties or prunes the table. Because the table can be
cleared by an administrator or by cron, take the entries you need as evidence before running any command that deletes
them.
Drush reads the database that the site’s settings.php configures, so a site with no working database connection also
has no Drush log. In that case the web server and PHP logs are the only evidence, which is why they are the first place
to look for a white screen.
The Status report
The Status report at admin/reports/status (Reports > Status report) lists every requirement that core and installed
modules check. The Drupal User Guide describes it as a short overview of the site’s settings that flags problems it
detects, and recommends copying it when asking for help on the Drupal.org forums or issue queues. Its entries are
grouped by severity, and the number of errors and warnings is shown on the page and, for errors, in the administrative
toolbar’s report item.
Where the entries come from
Each entry is produced by a requirements check: a module returns an array of requirements, each with a title, a value,
an optional description and a severity. The checks run in three phases: during installation, during updates and at
runtime, and the Status report shows the runtime ones. The severity levels are Informational, OK, Warning and Error. In
Drupal 11.2 and later the severities are the cases of the RequirementSeverity enum, and the old integer constants
REQUIREMENT_ERROR and the like are deprecated in 11.2.0 for removal in 12.0.0; modules that compare severities with
integers need to be updated (see the
API reference for
REQUIREMENT_ERROR). In recent 11.x releases the runtime and update phases are also being split into separate hooks,
so a module that implements requirements should be checked against the API reference for the version it targets.
hook_install, hook_requirements and the other install-time hooks have to stay procedural even in a module that uses
object-oriented hooks everywhere else (see Hooks and Object-Oriented Hook
Implementations).
The same data is available without a browser:
# Every line of the Status report
ddev drush core:requirements
# Only the errors (severity 2)
ddev drush core:requirements --severity=2
# Machine-readable output, handy for a monitoring check
ddev drush core:requirements --severity=1 --format=json
Source: Drush: core:requirements
The command has the aliases status-report and rq, an --ignore option that removes named requirements (the key
names appear in --format=yaml output), and exits with a non-zero status when an error is present at the chosen
severity, which makes it usable as a deployment gate. In a CI job the pattern is to run drush core:requirements --severity=2 after the deployment and fail the job if it reports anything.
Common requirement errors and what they mean
| Entry | Meaning and usual remedy |
|---|---|
Database updates: Out of date |
A module or core was updated but its update functions have not run. Run |
Configuration synchronisation: Out of sync |
The active configuration differs from the exported files. Review with |
Cron maintenance tasks: Never run, or not run for a long time |
Nothing is triggering cron. Add a system cron job, see Cron, Queues and Maintenance. |
Trusted Host Settings: Not enabled |
|
Database system version, PHP version or PHP extensions (for example |
The host is below the minimum or lacks an extension, see Requirements and Hosting. A missing PHP extension in the CLI and in the web SAPI is a classic cause of "works with Drush, fails in the browser". |
Update notifications: security update available, or update information cannot be fetched |
Run the update procedure in Updates, Composer and Security Advisories, or check outbound connectivity from the server. |
File system: Not writable, or temporary directory missing |
The web server user cannot write to |
Settings file protection: not protected |
|
Missing modules or themes |
The |
When the Status report itself cannot load (a fatal error or a memory-limit failure), use the Drush command and the logs first. The report is read-only, so a warning in it never needs urgent action, but an error that persists after a deployment normally means the deployment is incomplete.
White screens and 500 errors
A blank page or the generic message "The website encountered an unexpected error. Try again later." is the same family of problems seen from the visitor’s side. The three visible forms are:
-
A completely blank page (the "white screen of death"): PHP stopped with a fatal error or ran out of memory, and
display_errorsis off, so nothing was printed. The evidence is in the PHP or web server error log. -
The Drupal message "The website encountered an unexpected error": an exception reached Drupal’s exception handler, which logged it (look in Recent log messages, type
php) and showed the generic page becauseerror_levelishide. -
A web server HTTP 500 or 502/504 page: the failure happened before or outside Drupal: a syntax error in
.htaccess, a PHP-FPM crash or timeout, a missingvendordirectory, or a permission problem.
A checklist, in order
-
Read the error log first. On DDEV:
ddev logsfor the web container andddev logs -s dbfor the database; on a host, the PHP-FPM log and the virtual host’s error log. A fatal error line names the file, the line and the message. This is faster than any experiment. -
Show the error. Temporarily set
$config['system.logging']['error_level'] = 'verbose';insettings.local.php(or the production-safe equivalent: reproduce on staging). If even that shows nothing, the failure is before Drupal boots, for example insettings.php; check it for syntax errors withphp -l web/sites/default/settings.php. -
Check the most likely causes in this order:
-
a code change that was deployed without
composer install(thevendordirectory is out of step withcomposer.lock), or with a different PHP version than the one that installed it; -
a PHP memory or execution time limit (
memory_limit,max_execution_time) hit by an expensive page, an import or a cache rebuild; -
a missing or renamed class after a module update or removal, usually shown as
Class not foundorService not found, which a cache rebuild fixes if the service container is stale; -
a database that is unreachable, full or read-only (
SQLSTATE[HY000]); -
a configuration import that enabled a module whose code is not present;
-
file permissions or ownership changed after a deployment.
-
-
Rebuild caches (next section) once the cause is fixed or if the error mentions a missing service or plugin.
-
Compare with a working copy. On a site with configuration management,
git difffor the configuration files and the lock file often points at the change that broke it.
The community pages that cover this are Troubleshooting Drupal
(its section list includes Broken site problems) and
Broken site
problems. The latter’s recovery sequence after installing or updating a module or theme is: reload the page, run the
database updates, clear caches from the Performance page, run cron from the Status report, run rebuild.php, check the
log for details, and, for a blank page with no clear error, run drush cache:clear and read the terminal output.
Truncating the cache tables is mentioned there as a last resort.
HTTP 500 and 503 responses specific to Drupal
-
A 503 with "Site under maintenance" is maintenance mode, not a fault. Turn it off with
drush maint:set 0(see Cron, Queues and Maintenance). A deployment that failed halfway can leave it on. -
"Unable to connect to the database" or
SQLSTATE[HY000] [2002]points to the database server, to wrong values insettings.php, or to the credentials in the environment variables thatsettings.phpreads. The database values belong in the environment, never in a committed file. -
"The provided host name is not valid for this server" is the trusted host check rejecting the request; add the domain to
trusted_host_patterns. -
A 500 only for a proxy, CDN or Varnish layer can be a missing HTMX header pass-through. From Drupal 11.3, proxies and CDNs in front of the site must forward the
HX-RequestandHX-Redirectheaders, otherwise HTMX-based forms and the admin UI behave strangely (see AJAX and HTMX). -
Windows and IIS: IIS support was removed in Drupal 11.0, and Windows production hosting has been deprecated since 11.3; the troubleshooting steps on this page assume Apache or nginx.
Rebuilding caches
Many Drupal faults are stale caches: a new service, plugin, route or template that the container, discovery caches or render caches do not yet know about. A cache rebuild discards the compiled container, the plugin and route caches, the Twig compiled templates and the render and page caches, and rebuilds the critical ones. It is safe but expensive, because the first requests afterwards are slow (a large site can take a minute), so avoid doing it during peak traffic if you can.
| Method | When to use |
|---|---|
Configuration > Development > Performance > Clear all caches ( |
The site works and you have an administrator account. The User Guide’s Clearing the Cache page lists it first. |
|
The normal method from a terminal, including during |
|
Lists individual cache bins so a single one, such as |
|
No Drush and no working administrator login: for example a shared host. The script is enabled temporarily in
|
Truncating the |
A last resort when a corrupted cache entry keeps the site from booting. |
Using core/rebuild.php
The rebuild script is protected: it only runs when settings.php allows it, so that a stranger cannot trigger an
expensive rebuild. The sequence given in the User Guide page is:
<?php
// Last lines of web/sites/default/settings.php. Remove them after the rebuild.
$settings['rebuild_access'] = TRUE;
Open https://meridian.example.org/core/rebuild.php in a browser (replace the host with your own), wait for the home
page redirect, and then delete the line again. As an alternative to editing the file, an authenticated user with the
Administer site configuration permission can run the script without the setting, but then the site is not usually
broken enough to need it.
Leaving rebuild_access on in production lets any visitor trigger a rebuild repeatedly, which is a cheap
denial-of-service vector, so always remove it afterwards.
# Full rebuild (alias: cr)
ddev drush cache:rebuild
# After a deployment, the supported order in one command
ddev drush deploy
# Clear only the render cache bin
ddev drush cache:clear render
# Equivalent check: is the container healthy after the rebuild?
ddev drush core:requirements --severity=2
Source: Drush: cache:rebuild
Rebuilding caches is the answer to many problems and the cause of none, so treat a cache rebuild that "fixes" a fault
as a clue: if a rebuild is needed after every deployment, something (a missing drush deploy step) is not being done;
if it is needed repeatedly during normal use, a module is writing cache entries with wrong cache tags or contexts (see
Render API and Cache Metadata and
Performance and Caching).
Recovering a broken site
Missing modules after an upgrade
Typical symptoms are a warning in the Status report or on the Extend page that a module or theme "is missing from the
file system", a fatal error on every request after a deployment, or a configuration import that fails. The cause is a
core.extension entry (installed module or theme) whose code is no longer present. The official page
How
to fix "The following module is missing from the file system…" warning messages lists the causes and cures. In
summary:
-
The module was removed from the code without being uninstalled. The recommended repair is to put the code back (
composer require drupal/example), then uninstall it properly (drush pm:uninstall example) so that it can clean up its configuration, tables and fields, and only then remove the code again. Manually deleting its rows is discouraged: the page calls it "not recommended" because the module’s cleanup is skipped, and shows deleting thesystem.schemakey-value row and thecore.extensionentry (drush config:delete core.extension module.example), then a cache rebuild, as the last resort. -
The module moved within the code base, for example from
modules/tomodules/contrib/. Clear caches and restart the web server or PHP-FPM so that cached paths are dropped, or move it back. -
Fields, configuration or views still depend on it. Get a stack trace to find the caller, then report it in the module’s issue queue; a cron run or cache rebuild sometimes cleans such leftovers.
-
A profile or another module still lists it as a dependency. Check the
*.info.ymlfiles.
For the Meridian site this is realistic after an upgrade from Drupal 10 to 11, where core modules were removed (Actions UI, Activity Tracker, Book, Forum, Statistics and Tour went in 11.0; Color, Quick Edit, RDF, Aggregator and HAL in 10.0; Ban, Contact, Search, Toolbar, Telephone and others move to contrib in Drupal 12). The rule is the opposite of the one above: before upgrading, install the contrib version of any removed module you use, and do not uninstall it. Uninstalling the removed module first deletes its data, and the upgrade then fails with the missing-module symptoms. See Removed Core Modules and Replacements and Upgrading Drupal 10 to 11.
Old tutorials describe drush dis, drush cc all and drush sql-query against the system table. Those belong to
Drupal 7; on Drupal 10 and 11 the commands are pm:uninstall and cache:rebuild, and extension state is in the
core.extension configuration object, not the system table.
Broken configuration
A configuration import (drush config:import) can fail validation, enable modules in the wrong order or leave the site
in a state where the Status report reports the configuration as out of sync. The recovery path is:
-
Run
drush config:statusto see which items differ (Only in DB, Only in sync dir, Different). -
If the import enabled a module whose code is absent, deploy the code first (
composer install), then import again. Configuration import works on the premise that the code and the configuration belong to the same commit. -
If a single configuration object is wrong in the database, fix it in place:
drush config:set system.logging error_level hide, or edit the YAML and usedrush config:import --partial --source=.... Export afterwards so Git matches the site. -
If the site cannot boot at all, restore the previous release and its database backup rather than editing the database by hand.
# What differs between the active configuration and the exported files?
ddev drush config:status
# Read one object and compare it with the exported YAML
ddev drush config:get meridian_collection.settings
# Fix one value, then export so that Git agrees
ddev drush config:set meridian_collection.settings api_timeout 20 -y
ddev drush config:export -y
# Re-run the supported deployment sequence
ddev drush deploy -v
Source: Drush: config:status and Configuration management
Locked out and other emergencies
-
Locked out of the administrator account:
drush user:loginprints a one-time login link;drush user:password admin CHANGE_MEsets a new password (use a unique value and change it immediately). Both run with the site’s own credentials and do not need the web login. -
Site stuck in maintenance mode after a failed deployment:
drush maint:set 0, thendrush cache:rebuild. -
The
Anonymoususer record is missing (user ID 0): the Troubleshooting page has a recovery procedure, because many access checks assume that row exists. -
The administrator theme or a custom theme is broken: set
$config['system.theme']['admin'] = 'claro';and$config['system.theme']['default'] = 'olivero';temporarily insettings.php, or usedrush theme:enable; this uses themes that exist in core 11 (Claro and Olivero are removed in Drupal 12, where Default Admin replaces Claro). -
Database access lost after a restore: check the environment variables the
settings.phpreads, not the database server first.
The general recovery rule is to prefer the supported tool: Composer for code, drush deploy for the sequence, a
backup for the database. Module and theme upload from the administrative interface was removed in Drupal 10.4/11.0,
and updating from the interface in 11.2, so there is no browser-based repair for code problems any more.
Devel and Webprofiler
Two contributed projects help to find the cause of a problem on a development site. Both are developer tools and neither is part of core.
-
Devel (drupal.org/project/devel) adds helper functions such as
dpm()anddump()(through Symfony VarDumper in current releases), a toolbar and entity "Devel" tabs that show an entity’s raw field values, a render array or a loaded object, a mail system that writes outgoing mail to files, and Drush commands. Its page states that it is safe on a production site only if just developers hold the Access development information permission. The current 5.x releases need PHP 8.3 and Drush 13.7 or later in the 5.5 line; its compatibility table and the release block on the project page differ slightly on the supported core versions (10.4 or later in the table,^10.3 || ^11 || ^12in the release block), so read the version constraint Composer resolves. The Devel Generate submodule creates sample content in bulk, for example 200 Meridian exhibitions to test a view. -
Webprofiler (drupal.org/project/webprofiler) adds a toolbar at the bottom of every page, in the style of the Symfony profiler, showing the number and text of database queries, the services used, the cache hits, the render time and the request details. The current 11.2.x release series supports Drupal 11; for other majors use the matching branch listed on the project page. It is the tool to use when a Meridian page is slow or a view runs hundreds of queries.
Install them only with --dev so they are absent from production:
ddev composer require --dev drupal/devel:^5 drupal/webprofiler:^11.2
ddev drush pm:install devel webprofiler -y
ddev drush config:set system.logging error_level verbose -y
Source: Devel project page and Webprofiler project page
Never enable these modules on production: they expose internal data (queries, service lists, field values, backtraces), add overhead and may widen the attack surface. Uninstall them before exporting configuration for the production environment, or keep the configuration split so that the development-only modules are not exported (see Configuration Management). For debugging PHP code line by line, a debugger such as Xdebug in DDEV is more effective; see Debugging and Profiling PHP and Errors and Exceptions in PHP. Template problems are covered in Twig Templates and Debugging.
Version notes and corrections of older material
-
Drupal 10 ends on 2026-12-09. Troubleshooting steps here assume Drupal 11 and Drush 13 or 14; the same commands work on 10.2 and later with Drush 13.
-
Older books such as Drupal 10 Development Cookbook (2023) and Drupal 10 Masterclass (2023) show the Drush 12 command set, including
drush.services.ymlfor custom commands. That file no longer exists since Drush 13; commands are written with PHP attributes and autowiring. Usewatchdog:show,core:requirements,cache:rebuildandconfig:statusas shown here. -
Older tutorials still tell you to clear caches with
drush cc all, to disable modules withdrush dis, or to edit thesystemtable. These are Drupal 7 idioms; on Drupal 8 and later usedrush cr,drush pm:uninstalland thecore.extensionconfiguration. -
Tutorials that start from the Standard installation profile and a prebuilt "Article" or "Page" are out of date: from Drupal 11.4 the Standard profile no longer creates those content types, so a diagnosis of "content type not found" after an install is expected until the types are created (see Content Types and Fields).
-
The Statistics module that older books use to count views was removed in 11.0; do not look for its report. The
core/jquery.uilibrary and Quick Edit are also gone, which explains "library not found" errors in old themes. -
Code that tests against the integer constants
REQUIREMENT_OKandREQUIREMENT_ERRORshould move to theRequirementSeverityenum before Drupal 12. -
The assertion handling in the
.htaccessfile changed in the 11.4.11 and 10.6.20 security releases, which swapassert.activeforzend.assertions; a site with a customised.htaccessshould compare it with the shipped one when an unexpected 500 appears after a core update (see Security Hardening). -
The Drupal 12 requirements (PHP 8.5, Symfony 8) mean that after upgrading, the most common 500 is a PHP version mismatch between the CLI and the web server; see Preparing for Drupal 12 and Where Older Books and Tutorials Are Outdated.
Common errors and pitfalls
-
Logging at the wrong severity or without placeholders, which produces unreadable logs and a stored cross-site scripting risk.
-
Relying on Recent log messages as an audit trail; cron and administrators can clear it, so send audit-relevant events to Syslog or an external service.
-
Leaving
error_levelatverbose, orrebuild_accessset toTRUE, on a production site. -
Running
drush cras a ritual instead of finding which step of the deployment is missing. -
Uninstalling a removed core module before an upgrade instead of installing its contrib replacement.
-
Editing the
systemtable or thekey_valuetable by hand whenpm:uninstallorconfig:deletewould do the job. -
Reading the web login for evidence when the real cause is in the PHP or web server log, or testing in the browser with a different PHP version from the CLI.
-
Installing Devel or Webprofiler with plain
composer requireso that they reach production.