All guides · Security

Error Disclosure: Stack Traces and Debug Output in Production

When an application fails, frameworks in development mode show a detailed error page: the exception, the file and line, often the code around it, the query that failed, and sometimes environment variables and configuration. That is exactly what a developer needs, and exactly what an attacker needs too.

What error output reveals

  • File paths and directory layout, which help with path traversal and file inclusion attacks.
  • Framework and library versions, which point to known vulnerabilities.
  • SQL queries and database errors. A visible SQL syntax error after unusual input is a classic sign of SQL injection, and the message helps an attacker shape the payload.
  • Secrets. Debug pages from some frameworks list environment variables, database credentials or API keys.

An error on a public page also tells visitors and search engines that something is broken.

How to turn it off

The rule is the same everywhere: log errors to a file or service, show visitors a generic error page.

PHP (php.ini, or the host’s PHP settings panel):

display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/error.log

WordPress (wp-config.php):

define('WP_DEBUG', false);
define('WP_DEBUG_DISPLAY', false);

If you need logging on a live site, use WP_DEBUG_LOG with WP_DEBUG_DISPLAY set to false.

Laravel (.env): APP_DEBUG=false and APP_ENV=production. With debug on, Laravel’s error page can show environment details.

Symfony: run with APP_ENV=prod. The X-Debug-Token and X-Debug-Token-Link headers come from the web profiler, which must not be installed or enabled in production.

Django (settings.py): DEBUG = False, and set ALLOWED_HOSTS. The yellow debug page shows settings and the full traceback.

ASP.NET (web.config): show custom errors and remove the version headers:

<system.web>
  <customErrors mode="RemoteOnly" defaultRedirect="/error" />
  <httpRuntime enableVersionHeader="false" />
</system.web>

enableVersionHeader="false" removes X-AspNet-Version. For ASP.NET MVC, also set MvcHandler.DisableMvcResponseHeader = true; in Application_Start to drop X-AspNetMvc-Version. In ASP.NET Core, only use the developer exception page when the environment is Development.

Java / Spring Boot: keep server.error.include-stacktrace=never (and do not enable it for production profiles), and configure an error page in your servlet container.

Node.js / Express: run with NODE_ENV=production, in which Express’s default error handler does not print the stack, and register your own error handler that returns a plain message.

Database errors: catch them in your data layer and log them; never echo the driver’s message into the response. Use parameterised queries, so that unusual input cannot break the query in the first place.

After changing settings, trigger a test error (for example, a route that throws, only reachable from your own address) and confirm the public response is generic and the log has the details.

How PerfoMetrix checks this

We look at the page and response headers we already downloaded. We send no extra requests to your site and do not try to provoke errors; we only report what the normal page already shows.

In the visible text, we look for PHP errors that include a file path and line number, SQL error messages (MySQL, PostgreSQL, Oracle, SQL Server), Python tracebacks, the ASP.NET “Server Error in Application” page, Django’s debug page, Laravel and Whoops error pages, and Java and Node.js stack traces. In the headers, we look for X-AspNet-Version, X-AspNetMvc-Version, X-Debug-Token and X-Debug-Token-Link.

Any finding fails the check, and the report lists what was found. Because we only scan one page, a pass does not mean other pages or error paths are clean.

Further reading: OWASP: Error Handling Cheat Sheet and OWASP Web Security Testing Guide: improper error handling.

Last updated October 28, 2026