Why this matters for PCI DSS
A verbose error page is a free reconnaissance brief. From a single stack trace an attacker learns: framework + version (which CVEs apply), filesystem layout (which paths to probe), database schema (which tables exist), and often credentials embedded in connection strings.
SQL error messages are particularly serious — they confirm the presence of an injection point and reveal the schema the attacker needs to exploit it.
PCI DSS 4.0 Requirement 6.2.4 requires defenses against information disclosure. Requirement 6.5 covers application coding standards including error handling.
How to fix it
The principle is the same in every stack: log the full detail to a server-side log; show the user a generic page.
PHP (php.ini, production):
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/error.log
ASP.NET / IIS (web.config):
<system.web>
<customErrors mode="On" defaultRedirect="~/error.aspx" />
<compilation debug="false" />
</system.web>
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace" />
</system.webServer>
Rails (config/environments/production.rb):
config.consider_all_requests_local = false
config.action_dispatch.show_exceptions = true
Django (settings.py):
DEBUG = False
ALLOWED_HOSTS = ['example.com']
Don't forget custom 404 and 500 templates.
Express — register a final error-handling middleware that hides the stack:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Sorry, something went wrong.');
});
Web server defaults — also configure custom error pages at the web server level so a crashed application doesn't fall back to nginx/Apache/IIS default error pages, which themselves leak version info.
Verify by deliberately requesting a non-existent path and an invalid one: curl https://example.com/this-does-not-exist — should show a clean error page, not a stack trace.