Security 8 min read

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.

Matt King
Matt King
March 9, 2026
Last updated: March 9, 2026
Laravel Debug Mode in Production: Why It's Dangerous and How to Fix It

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:

  1. Rotate every secret in your .env file: database passwords, API keys, mail credentials, APP_KEY
  2. Check access logs for requests to error-triggering URLs
  3. Audit your database for unauthorized access
  4. Review cloud provider logs for API key usage
  5. 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:

  1. Set APP_DEBUG=false in your production .env
  2. Run php artisan config:cache
  3. Create custom error pages
  4. Add CI/CD checks to prevent regressions
  5. 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.