Laravel Debug Mode in Production: Why It's Dangerous and How to Fix It
Debug mode in production exposes stack traces, database credentials, environment variables, and internal paths. Learn exactly what it reveals, how attackers use it, and how to make sure it never reaches production.
Laravel's debug mode is one of the most useful features during development. It gives you detailed stack traces, query logs, and environment information when something goes wrong. It is also one of the most dangerous things you can leave enabled in production.
This is not a theoretical risk. Debug mode exposures are one of the most common security findings in Laravel applications. Attackers actively scan for them because the information revealed makes further attacks trivially easy.
What Debug Mode Exposes
When APP_DEBUG=true in production and an error occurs, Laravel's error handler (Ignition) displays a detailed debug page. Here is exactly what that page reveals:
| Exposed Information | Severity | Why It Matters |
|---|---|---|
| Full stack trace | High | Reveals internal file paths, class names, and application structure |
| Environment variables | Critical | Exposes DB_PASSWORD, APP_KEY, API keys, mail credentials, and every secret in your .env |
| Database connection details | Critical | Database host, port, username, and password shown in plain text |
| Server file paths | Medium | Reveals your deployment structure (/var/www/app, /home/forge, etc.) |
| Database queries | High | Shows raw SQL queries, including table names and column structures |
| PHP and Laravel version | Medium | Helps attackers target version-specific vulnerabilities |
| Loaded configuration | High | Full dump of all config values including third-party service credentials |
| Request data | Medium | Shows headers, cookies, session data, and input parameters |
That is not just an information leak. It is a complete blueprint of your application handed to anyone who triggers an error.
How Attackers Exploit Debug Mode
Attackers do not just stumble on debug pages. They actively scan for them. Here is the typical attack flow:
Step 1: Discovery
An attacker sends a request to a path that will trigger an error. Common techniques:
GET /non-existent-page HTTP/1.1
GET /%00 HTTP/1.1
GET /index.php/test HTTP/1.1
POST /api/v1/test with malformed JSON
If the response contains a stack trace instead of a generic error page, the application is in debug mode.
Step 2: Credential harvesting
The debug page displays all environment variables. The attacker now has:
DB_HOST=production-db.internal.company.com
DB_DATABASE=app_production
DB_USERNAME=app_user
DB_PASSWORD=s3cur3_p@ssw0rd_2024
MAIL_USERNAME=apikey
MAIL_PASSWORD=SG.xxxxxxxxxxxxx
AWS_ACCESS_KEY_ID=AKIAxxxxxxxxx
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxx
APP_KEY=base64:xxxxxxxxxxxxxxxx
Step 3: Direct exploitation
With database credentials, the attacker connects directly to your database (if the port is open) or uses the AWS keys to access your cloud resources. The APP_KEY can be used to decrypt encrypted data and forge session cookies.
Step 4: Ignition RCE (older versions)
In Laravel applications running Ignition versions before 2.5.2, the debug page itself has a remote code execution vulnerability (CVE-2021-3129). An attacker can execute arbitrary PHP code through the debug page's "Run Solution" feature. This was actively exploited in the wild.
How to Check If Debug Mode Is On
Method 1: Check your .env file
# On your production server
grep APP_DEBUG /var/www/yourapp/.env
The output should be:
APP_DEBUG=false
If it says APP_DEBUG=true, your application is exposed.
Method 2: Trigger a 404
Visit a URL that does not exist on your application:
https://yourapp.com/this-page-should-not-exist-12345
If debug mode is ON: You will see a detailed Ignition error page with a stack trace, environment details, and file paths.
If debug mode is OFF: You will see a generic "404 | Not Found" page or your custom error page.
Method 3: Check response headers
Some debug tools add response headers. Look for:
X-Debug-Token: xxxxxx
X-Debug-Token-Link: /_profiler/xxxxxx
These indicate that Symfony's debug toolbar or profiler is active.
Method 4: Check the config cache
If you are using config caching, the cached config might differ from your .env:
php artisan config:show app.debug
Or check the cached config directly:
grep "'debug'" bootstrap/cache/config.php
Method 5: External monitoring
Use StackShield to check from the outside. It probes your application for debug mode indicators the same way an attacker would, and alerts you if debug mode is detected.
How to Fix It
Step 1: Disable debug mode
In your production .env file:
APP_DEBUG=false
APP_ENV=production
Step 2: Clear and rebuild the config cache
php artisan config:clear
php artisan config:cache
This ensures the cached configuration reflects your current .env values.
Step 3: Set a custom error handler for production
Create custom error pages so your users see something useful instead of a blank page:
mkdir -p resources/views/errors
Create resources/views/errors/404.blade.php:
@extends('layouts.app')
@section('content')
<div class="text-center py-20">
<h1 class="text-4xl font-bold">Page Not Found</h1>
<p class="mt-4 text-gray-600">The page you are looking for does not exist.</p>
<a href="/" class="mt-6 inline-block text-blue-600">Go Home</a>
</div>
@endsection
Create resources/views/errors/500.blade.php:
@extends('layouts.app')
@section('content')
<div class="text-center py-20">
<h1 class="text-4xl font-bold">Something Went Wrong</h1>
<p class="mt-4 text-gray-600">We are working on fixing this. Please try again later.</p>
</div>
@endsection
Step 4: Verify the fix
Visit a non-existent URL on your production site. You should see your custom error page, not a stack trace.
How to Prevent It from Happening Again
Debug mode leaks usually happen during deployments. A developer sets APP_DEBUG=true to troubleshoot an issue, fixes the problem, and forgets to set it back. Or a staging .env file gets copied to production.
Here are concrete ways to prevent this:
Use environment-specific .env files
Never copy .env files between environments. Each environment should have its own file managed separately. If you use Laravel Forge, Vapor, or Envoyer, environment variables are managed through their dashboards, not through .env files in your repository.
Add a CI/CD check
Add a deployment step that verifies debug mode is off:
# GitHub Actions example
- name: Verify debug mode is disabled
run: |
if grep -q "APP_DEBUG=true" .env.production; then
echo "ERROR: Debug mode is enabled in production .env"
exit 1
fi
Add a runtime check
Add a check in your AppServiceProvider that logs a warning if debug mode is on in production:
// app/Providers/AppServiceProvider.php
public function boot()
{
if (app()->environment('production') && config('app.debug')) {
Log::critical('Debug mode is enabled in production!');
// Optionally, force it off
config(['app.debug' => false]);
}
}
Use config caching
Always run php artisan config:cache as part of your deployment process. This bakes your configuration into a cached file and ensures consistency. It also means accidental .env changes do not take effect until the cache is rebuilt.
Set up continuous monitoring
Manual checks are unreliable. People forget. Deployments happen at 2 AM. A config cache rebuild pulls in the wrong value.
StackShield monitors your application from the outside and checks for debug mode on every scan. If debug mode is ever detected in production, you get an alert in Slack, email, or via webhook within minutes, not when a customer reports seeing your database password.
The Real Cost of Debug Mode in Production
Debug mode exposure is not just a security issue. It is a potential data breach.
If your debug page has been publicly accessible, you should assume your credentials have been compromised. That means:
- Rotate every secret in your .env file: database passwords, API keys, mail credentials, APP_KEY
- Check access logs for requests to error-triggering URLs
- Audit your database for unauthorized access
- Review cloud provider logs for API key usage
- Notify affected parties if user data may have been accessed
This is exactly the kind of incident that continuous monitoring prevents. A $29/month monitoring tool is significantly cheaper than a breach response.
Summary
Debug mode in production is one of the most common and most dangerous Laravel misconfigurations. It exposes everything an attacker needs to compromise your application, from database credentials to encryption keys.
Fix it now:
- Set
APP_DEBUG=falsein your production .env - Run
php artisan config:cache - Create custom error pages
- Add CI/CD checks to prevent regressions
- Set up continuous monitoring with StackShield to catch it if it ever happens again
Start a free 14-day trial of StackShield to check your Laravel application for debug mode and 30+ other security issues.
Frequently Asked Questions
How do I check if debug mode is enabled on my Laravel application?
There are several ways to check. Visit a URL that does not exist (like /this-page-does-not-exist) and see if you get a detailed stack trace or a generic 404 page. You can also check your .env file for APP_DEBUG=true, or inspect the response headers for X-Debug-Token or similar debug indicators. StackShield checks for exposed debug mode automatically on every scan.
What information does Laravel debug mode expose?
When APP_DEBUG is true, Laravel error pages display full stack traces, all environment variables (including database passwords, API keys, and mail credentials), file paths on your server, database query logs, loaded configuration values, and PHP/Laravel version information. This gives an attacker a complete map of your application internals.
Can debug mode be exploited for remote code execution?
Yes. In older versions of Ignition (before 2.5.2), the debug page had a known remote code execution vulnerability (CVE-2021-3129). Even in patched versions, the information exposed by debug mode (file paths, environment variables, framework version) helps attackers craft targeted exploits against other vulnerabilities in your application.
How do I show user-friendly error pages with debug mode off?
Laravel automatically shows generic error pages when APP_DEBUG is false. You can customize these by creating Blade templates in resources/views/errors/. For example, create 404.blade.php, 500.blade.php, and 503.blade.php with your own branded error pages. Laravel will use these automatically.
Related Articles
OWASP Top 10 for Laravel: A Practical Guide
A hands-on mapping of every OWASP Top 10 (2021) category to specific Laravel vulnerabilities, with code examples of what goes wrong and how to fix it.
SecurityIs Your Laravel .env File Exposed? How to Check and Fix It
Your .env file contains database credentials, API keys, and encryption secrets. If it's accessible from the web, attackers already have everything they need. Here's how to check and fix it.
SecurityLaravel Telescope in Production: Security Risks You Need to Know
Laravel Telescope records every request, query, job, and log entry in your application. Left exposed in production, it gives attackers a real-time view into your entire system.