Error Reporting in Production

Yo Yogesh.M Updated 17 Oct 2026
5 min read ·Lesson 82 of 109
In this post

Error Reporting in Production Why Production Error Handling Is Different In simple terms: During development, you want to see every error immediately, with full details. On a live website, the same me

Error Reporting in Production

Why Production Error Handling Is Different

In simple terms: During development, you want to see every error immediately, with full details. On a live website, the same messages are dangerous: they can reveal file paths, database names, SQL queries, passwords in stack traces, and the software versions you use — a roadmap for attackers. In production, errors must be hidden from visitors but recorded for developers.

Technically: PHP's behavior is controlled by php.ini directives (display_errors, log_errors, error_reporting, error_log, and others), plus the application's own error handlers and framework settings. PHP ships with two templates — php.ini-development and php.ini-production — whose recommended values differ exactly in these areas. Error disclosure is classified as a security weakness (information exposure) because it helps attackers plan further attacks.

What Error Messages Can Reveal

Leaked DetailExampleHelps Attackers
File system paths/var/www/shop/src/Payment/Gateway.phpMap the application; target path traversal
SQL queries and table namesSQLSTATE[42S22]: Unknown column ... in 'users'Craft SQL injection payloads
Stack traces with argumentsPDO->__construct('mysql:host=...', 'admin', ...)Obtain credentials
VersionsPHP/8.2.3, framework version in error pagesFind known vulnerabilities
ConfigurationDebug pages listing environment variablesSteal API keys and secrets

; php.ini (production)
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/app-error.log
error_reporting = E_ALL
html_errors = Off
zend.exception_ignore_args = On
expose_php = Off
DirectiveProductionDevelopmentMeaning
display_errorsOffOnShow errors in the output
display_startup_errorsOffOnShow errors that occur while PHP starts
log_errorsOnOnWrite errors to the log
error_logA protected file path (or unset to use stderr in containers)OptionalWhere errors are written
error_reportingE_ALLE_ALLWhich errors are reported — always all of them
zend.exception_ignore_argsOnOffOmit function arguments from stack traces (they may contain secrets)
expose_phpOffEitherRemove the X-Powered-By: PHP/x.y.z header (see Hiding PHP)

error_reporting stays at E_ALL in production: errors must still be reported — to the log — even though they are not displayed. Lowering it hides problems from developers, not from attackers.


Verifying the Configuration

Different environments often use different php.ini files: the command line, PHP-FPM, and Apache's module each have their own. Verify the settings in the environment that serves web requests, for example with a protected admin-only script:

<?php
// admin/config-check.php — protect with authentication or run via CLI with the FPM ini file
$expected = [
    'display_errors' => false,
    'display_startup_errors' => false,
    'log_errors' => true,
    'expose_php' => false,
    'zend.exception_ignore_args' => true,
];

foreach ($expected as $setting => $shouldBeOn) {
    $isOn = filter_var(ini_get($setting), FILTER_VALIDATE_BOOL);
    printf("%-28s %-6s %s\n", $setting, $isOn ? 'On' : 'Off', $isOn === $shouldBeOn ? 'OK' : 'CHANGE');
}

printf("%-28s %-6s %s\n", 'error_reporting', error_reporting() === E_ALL ? 'E_ALL' : (string) error_reporting(), error_reporting() === E_ALL ? 'OK' : 'CHANGE');

Output (on a correctly configured production server)

display_errors               Off    OK
display_startup_errors       Off    OK
log_errors                   On     OK
expose_php                   Off    OK
zend.exception_ignore_args   On     OK
error_reporting              E_ALL  OK

ini_get() returns strings such as "1", "0", "On", or ""; FILTER_VALIDATE_BOOL interprets all of them correctly. On the command line, php --ini shows which configuration files are loaded, and php -i | grep display_errors shows the CLI's values (which may differ from PHP-FPM's).

Warning: Never leave phpinfo() scripts on a production server. They display the complete configuration, environment variables, and paths to anyone who finds them.


Showing Users a Safe Error Page

With display_errors = Off, an uncaught error results in a blank page or a generic server error. Provide a proper page instead:

  1. Application level: a global exception handler and shutdown function log the error and render a friendly page with HTTP status 500 (see Custom Error Handler).
  2. Web server level: configure static error pages for failures PHP cannot handle, such as PHP-FPM being down (502/504):
error_page 500 502 503 504 /errors/500.html;
location = /errors/500.html {
    internal;
}

A good error page apologizes, offers a way forward (home page, support contact), and may show a reference ID that also appears in the log entry — so support staff can find the exact error without exposing details:

$reference = bin2hex(random_bytes(4));   // e.g. "9f3c1a7e"
error_log("[ref $reference] " . $exception);
// show: "Something went wrong. Reference: 9f3c1a7e"

Framework Debug Modes

Frameworks have their own debug switches, which must be off in production:

Framework / ToolProduction Setting
LaravelAPP_ENV=production, APP_DEBUG=false
SymfonyAPP_ENV=prod, APP_DEBUG=0
Debug toolbars and error pages (Whoops, Symfony Profiler, Laravel Debugbar, Telescope)Not installed or disabled in production

A detailed framework error page in production can expose the full environment, including database passwords and API keys. Deployment scripts should verify these settings.


Other Information Leaks

LeakPrevention
Directory listingsDisable automatic indexes (autoindex off; in Nginx, Options -Indexes in Apache)
Backup and editor files (config.php.bak, .swp, .env.old)Never create them in the document root; block dotfiles and backup extensions
Version control folders (.git/)Deploy without them, or keep them outside the document root
Verbose API errorsReturn generic messages with error codes; log details server-side
Health-check endpointsReport "OK"/"degraded" without internal details, or protect them

Monitoring Errors

Hidden errors must still reach developers quickly:

  • Centralized logging: ship logs from all servers to one place (for example, an ELK stack, Grafana Loki, or a cloud logging service).
  • Error tracking services such as Sentry or Bugsnag group identical errors, record context, and notify the team.
  • Alerts on error spikes, repeated 500 responses, or fatal errors.
  • Log hygiene: rotate logs, restrict access, and avoid logging passwords, tokens, or personal data (see Error Logging).

Common Mistakes

display_errors = On in Production

Effect: Visitors (and attackers) see internal details.

Fix: Turn it off in the production php.ini, and verify it in the web context.

Lowering error_reporting Instead of Hiding Display

Effect: Real problems are never logged.

Fix: Keep E_ALL; control visibility with display_errors.

Framework Debug Mode Left On

Effect: Environment variables and source code exposed.

Fix: Set production environment variables and check them during deployment.

No Monitoring

Effect: Errors go unnoticed until users complain.

Fix: Collect logs centrally and alert on errors.


Best Practices

  • Use php.ini-production as the basis for production servers.
  • Hide errors from users, log everything, and keep error_reporting = E_ALL.
  • Enable zend.exception_ignore_args and disable expose_php in production.
  • Show friendly error pages with reference IDs, at both the application and web server levels.
  • Disable debug modes, remove phpinfo() files, block sensitive files, and monitor logs actively.

Official reference: https://www.php.net/manual/en/security.errors.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators
Ask about this post
AI Ask about this post

Ask questions about Error Reporting in Production and get answers drawn from it.

Signed-in readers only.