Current situation: PHP2Shell. Two critical PHP vulnerabilities (CVSS 9.2) allow malicious code without login. Patches since 21 October 2026.What to do now →

display_errors off: Disabling Error Output in Production

Visible PHP error messages reveal paths, database names and versions. Here is how to switch them off in PHP, in the framework and in the web server without losing sight of the errors.

Last updated 6 min readHardening and configuration

In short: On a live website, display_errors = Off belongs in php.ini, together with display_startup_errors = Off, log_errors = On and an error log outside the web root. In addition, CMS and frameworks must run in production mode, otherwise their own error pages show the details. Locked with php_admin_flag and checked after every update, errors stay in the log instead of the browser.

What a single error message reveals

A warning at the top of the page, a “Fatal error” instead of the order overview: such output looks harmless. For an attacker it is a map. It shows full file paths, names of databases and tables, libraries in use and often the PHP version.

Especially in situations like PHP2Shell, where vulnerable servers are searched for on a massive scale, a server should reveal as little about itself as possible. This closes no vulnerability in the PHP interpreter, but it makes every further step after a break-in harder and targeted attacks on your application more laborious.

Data protection comes on top. Error messages can contain parts of requests, such as email addresses from forms. What appears in a visitor's browser is no longer under your control.

The defaults are not meant for production

Many assume that PHP hides errors on its own. The PHP manual says otherwise: the built-in default of display_errors is 1, and display_startup_errors has also been enabled by default since PHP 8.0. Only the bundled template php.ini-production sets both to Off. The manual expressly calls display_errors a development feature that should never be used on systems on the internet.

Whether your server uses the production template is not a given. Container images sometimes start without any php.ini, hosting panels set their own values, and after a migration the development template is often copied.

display_errors off: the settings at a glance

The following values make sense for production servers. Constants such as E_ALL work only in php.ini; in the Apache configuration they have no meaning according to the PHP manual and must be given as a number.

Setting Production Purpose
display_errors Off no errors in the browser
display_startup_errors Off no errors in the output when PHP starts
log_errors On write errors to the log
error_log path outside the web root separate log file per website or pool
error_reporting E_ALL log all errors, do not display them
html_errors Off plain text instead of HTML in logs
zend.exception_ignore_args On no function arguments in stack traces
fatal_error_backtraces 0 until the update (PHP 8.5 only) interim measure according to php.net

zend.exception_ignore_args is particularly valuable. Without this setting, stack traces in logs and error pages can contain the arguments of function calls, including passwords or tokens that were just being processed.

; php.ini of the web SAPI
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/website-fehler.log
error_reporting = E_ALL
html_errors = Off
zend.exception_ignore_args = On
; PHP 8.5 only, interim measure until the update to 8.5.12:
; fatal_error_backtraces = 0

In the FPM pool, additionally lock the most important values so that no application switches them back on with ini_set():

; e.g. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_flag[display_errors] = off
php_admin_flag[display_startup_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php/seite1-fehler.log

Then restart the respective PHP service. How locking works exactly is explained in the article php_admin_value: locking settings against overrides. A complete basic configuration can be found under Configuring php.ini securely.

The second switch: the application's debug mode

PHP is only half the battle. Modern frameworks catch errors themselves and show their own, often very detailed error pages. In debug mode they contain source code excerpts, configuration values and sometimes environment variables. These pages appear even when display_errors is switched off in PHP.

So check each application's own switch. For WordPress these are WP_DEBUG and WP_DEBUG_DISPLAY in wp-config.php, for Laravel and Symfony APP_DEBUG and the environment in the .env file, for other systems the production mode according to the vendor documentation. On a live system, debug mode must stay off permanently; even “just briefly for testing” is a risk.

How errors are handled cleanly in the application instead of being suppressed is described in Errors and exceptions in PHP.

How to find error output before others do

Setting it once is not enough. Error output returns after updates, migrations or panel changes. A short check routine helps:

  1. Check the effective values in the web context, not only on the command line, for example with a test file placed briefly and deleted immediately afterwards.
  2. Check each application's debug mode in its configuration file.
  3. Search delivered pages for typical error texts, as in the code block below.
  4. In the search engine, search your domain with a site: query for terms such as “Fatal error” or “Warning”.
  5. Check that the error log is being written and is not in the web root.
# Search delivered pages for PHP error texts (own domains only)
for url in https://www.example.de/ https://www.example.de/kontakt/; do
  curl -s "$url" | grep -E -o "(Fatal error|Warning|Notice|Deprecated|Stack trace)[^<]{0,80}" | head -3
done
# Is the error log being written?
ls -l /var/log/php/
Important

Hiding errors is no substitute for fixing them. A log full of warnings is a sign of maintenance needs, and after a break-in it is often an important trace. What to log and what not is described in Logging safely: recording errors without a data leak.

Conclusion: quiet in the browser, talkative in the log

The rule for production servers is simple: errors belong in the log, not on the website. In practice it fails because of defaults that are not meant for production, debug switches in applications and configurations that fall back after updates or migrations.

Anyone who switches off display_errors and display_startup_errors, locks the values in the pool, checks the application's debug mode and verifies the result regularly closes one of the most common sources of unnecessary information disclosure. The effort is small, and the effect lasts as long as the check remains part of every update.

One point should not be lost here: the error log is not a trash can. Anyone who reads it regularly recognizes faulty updates, outdated functions and sometimes the first traces of an attack, long before customers notice anything.

How does PHP SOS help?

  • Check: We read out the effective error settings per website and pool and search delivered pages for error output.
  • Switch over: We set PHP and application settings for production operation and lock them against overriding.
  • Set up logs: Error logs sit outside the web root, are rotated and are accessible to your developers.
  • Keep it permanent: Under the security maintenance contract we recheck the settings after every update.

Frequently asked questions

Isn't display_errors switched off in PHP anyway?

Not automatically. According to the PHP manual, the built-in default of display_errors is 1. Only the bundled template php.ini-production sets Off. Whether your server uses this template depends on the distribution, panel or container image and should be checked.

Is display_errors = Off enough if WordPress or Laravel run in debug mode?

No. Many CMS and frameworks bring their own error pages, which can show details independently of PHP. So also switch off the application's debug mode, for example WP_DEBUG_DISPLAY in WordPress or APP_DEBUG in Laravel and Symfony.

Where do errors go when they are no longer displayed?

With log_errors = On, PHP writes them to the file named by error_log, or to the log of the respective operating mode, such as the Apache or PHP-FPM error log. The file should be outside the web root and rotated regularly.

What does fatal_error_backtraces have to do with PHP2Shell?

The setting exists from PHP 8.5 and is active by default. php.net names fatal_error_backtraces=0 with a subsequent restart as one of the interim measures for PHP 8.5 while the update to 8.5.12 is still pending. It does not replace the update.

Sources

Immediate contact

Every hour counts. Write to us now.

Briefly describe your system, PHP version and observation. We will get back to you, clarify the situation and work remotely until the incident is closed and documented.

Immediate help