Telescope Will Burn You in Production
Laravel Telescope is the best debugging tool I've ever accidentally left in production. Here's why I don't use it anymore, and what I run instead.
Telescope is a genuinely great debugging tool. It's also one of the most dangerous things you can leave running in a production environment, and the Laravel ecosystem doesn't talk about that loudly enough. I've seen it expose request payloads, slow down apps under load, and balloon databases — all while sitting quietly behind a route that someone forgot to lock down.
Let me tell you what it actually does, where it goes wrong, and what I run on production systems now.
What Telescope Actually Is
For the uninitiated: Telescope is Laravel's first-party debugging dashboard. You install it, it wires into your app's event system, and it starts recording everything — HTTP requests, query logs, queued jobs, mail, notifications, exceptions, cache hits and misses, scheduled commands, Redis calls, gate checks. It's a flight recorder for your Laravel app.
In local dev, it's fantastic. I can open http://myapp.test/telescope and immediately see the 47 queries that my poorly-written Eloquent relationship is firing, or catch that a queued notification failed silently. For that use case, I have zero complaints.
The problem is it's framed as something you'd run in staging and production too. The docs even walk you through enabling it per environment. And developers — myself included, early on — read that and think "great, I'll have visibility into what's happening in production." That's where it gets you.
What It Costs You
Telescope records everything by default. Every request body. Every response. Every SQL query with bindings. Every exception with its full context array.
That means if a user submits a form with a credit card number — even if you immediately hand it to Stripe and never store it — Telescope may have just written that number into your telescope_entries table. If a patient submits intake information through a healthcare form, Telescope wrote that to your database too. I caught this during a code review for a small clinic app I was auditing for a client. They had Telescope enabled in production, no authentication on the route, and were collecting PHI in every request entry. HIPAA would not have been amused.
Beyond compliance, there's the performance angle. Telescope uses synchronous database writes by default. Every request writes multiple rows to telescope_entries and telescope_monitoring. Under any real load — even moderate load, not high load — this adds latency to every single request. I did a quick benchmark on an app handling about 400 req/min and shaved 18ms average off response time just by disabling Telescope. That's not noise. That's real.
And then there's the table itself. telescope_entries will grow without bound if you're not running telescope:prune on a schedule. I inherited a Laravel 8 app where the previous dev had installed Telescope and walked away. The table had 40 million rows. The database was 60GB. The prune command ran for three hours.
The Route Auth Problem
Telescope ships with a gate that controls access:
// app/Providers/TelescopeServiceProvider.php
protected function gate(): void
{
Gate::define('viewTelescope', function (User $user) {
return in_array($user->email, [
'you@yourcompany.com',
]);
});
}
That gate only fires for authenticated users. But by default in non-local environments, Telescope still registers its routes. So https://yourapp.com/telescope exists. If someone hits it unauthenticated, they get redirected to login — fine. But the moment an authorized user is logged in, they can see everything Telescope recorded: every other user's request data, query bindings, session contents.
The gate is also email-address-based out of the box. Developers add their personal email during setup and forget it's there. Developer leaves the company, email stays in the array, nobody notices.
I've also seen configs where someone tried to restrict Telescope to local only but got the environment check wrong:
// This is what they wrote — looks right, isn't:
public function register(): void
{
$this->hideSensitiveRequestDetails();
Telescope::night();
}
// They forgot to wrap the entire registration:
public function register(): void
{
if ($this->app->environment('local')) {
// Should all be in here
}
}
Laravel's own docs show register() with the environment check, but it's easy to miss that the check has to wrap parent::register() too, or Telescope still boots and starts recording.
What I Actually Run Instead
For production observability, I've settled on a combination that gives me real signal without the liability.
Flare for exceptions. Flare is Spatie's error tracking service, and it integrates with Laravel natively. You get stack traces, context, user info, and request data — but only on exceptions, not on every request. You control what gets scrubbed. It's cheap ($19/mo for most of my client apps) and it's the first place I look when something breaks.
composer require spatie/laravel-flare
// config/flare.php — scrub sensitive fields before they leave your server
'flare_middleware' => [
\Spatie\FlareClient\FlareMiddleware\RemoveRequestIp::class,
\Spatie\FlareClient\FlareMiddleware\CensorRequestBodyFields::class => [
'censor_fields' => ['password', 'password_confirmation', 'ssn', 'dob', 'credit_card'],
],
],
Laravel Pulse for performance. Pulse is the newer first-party option from the Laravel team, and it's explicitly designed for production. It aggregates metrics rather than storing raw entries — so instead of writing a row for every query, it increments counters. Your database footprint is a rounding error compared to Telescope.
Pulse gives you slow queries (aggregated), slow requests, failed jobs, exception counts, queue depth, and server metrics. That's 90% of what I actually need to know in production.
composer require laravel/pulse
php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider"
php artisan migrate
Lock it down properly with a gate that uses a role, not a hardcoded email:
// routes/web.php
Route::middleware(['auth', 'can:view-pulse'])->group(function () {
Route::get('/pulse', function () {
return view('pulse');
});
});
// AppServiceProvider.php
Gate::define('viewPulse', function (User $user) {
return $user->hasRole('admin');
});
Structured logging with a log aggregator. I push logs to Papertrail or Logtail depending on the client's budget. The key is writing structured logs — JSON, not plain text strings — so you can actually search and filter them.
// Log structured context, not interpolated strings
Log::channel('structured')->info('payment.processed', [
'order_id' => $order->id,
'amount_cents' => $order->amount_cents,
'gateway' => 'stripe',
'duration_ms' => $duration,
]);
This gives me a searchable, retentionable audit trail without recording request bodies wholesale.
If You Absolutely Must Run Telescope
If a client insists, or you have a staging environment where Telescope's full detail view is genuinely useful, here's the minimum I'd do:
-
Restrict recording by environment. Use the environment check correctly, wrapping
parent::register()inside it. -
Filter sensitive data before it's stored:
// TelescopeServiceProvider.php
Telescope::filter(function (IncomingEntry $entry) {
if ($entry->type === EntryType::REQUEST) {
$entry->content = array_merge($entry->content, [
'payload' => Arr::except($entry->content['payload'] ?? [], [
'password', 'password_confirmation', 'ssn', 'token', 'secret',
]),
]);
}
return true;
});
- Prune aggressively. 24-48 hour retention max. Add this to your
Console/Kernel.phpschedule:
$schedule->command('telescope:prune --hours=24')->daily();
-
Put Telescope on a separate database connection so its writes don't compete with your application writes.
-
Lock the route with middleware that requires a specific role, not just authentication and an email array.
That's a lot of work to safely run a debugging tool in production. Which is exactly my point.
When I'd Reach for Telescope (and When I Wouldn't)
Telescope is the right tool for local development, full stop. I still install it on every new project via --dev flag and use it constantly while building:
composer require laravel/telescope --dev
Sometimes I'll enable it on a staging environment temporarily to debug a specific problem that only reproduces with real data — with the filtering and pruning in place, and with the understanding that it gets turned back off when I'm done.
I would not run it in production for any client where data sensitivity matters, which is every client I have. Healthcare, biotech, e-commerce, real estate — they all handle PII. The risk/reward is wrong. The reward is convenience; the risk is a data exposure incident.
For production I want Pulse for performance visibility, Flare for exception tracking, and structured logs going somewhere searchable. That stack costs less than $30/month for most apps and gives me everything I actually need without turning my production database into an unindexed heap of raw request data.
Telescope's an exceptional development tool. Treat it like one. It belongs in your .gitignore'd local config, not your production deploy.
Need help shipping something like this? Get in touch.