Contents
- What is the difference between maintenance mode and feature flags?
- How does Laravel maintenance mode work in practice?
- How do you use Laravel Pennant as a kill switch?
- What does a real incident look like with this pattern?
- How do you build an admin toggle to manage flags without the command line?
- Why should you treat deployment and release as separate events?
Key Takeaways
- Laravel maintenance mode (
php artisan down) gives you a clean user-facing message for full outages in under a minute — with a--secretbypass URL so your team can still access the site- Laravel Pennant gives you surgical control: deactivate a single feature for all users or a specific segment without touching a deployment pipeline
- The two tools cover different scopes — maintenance mode is for broad outages, Pennant is for isolating a broken feature while everything else runs normally
- Kill switches must be defined before the incident, not retrofitted during one — the time to wrap a critical feature in a flag is when you build it
- Decoupling deployment from feature release is the underlying pattern: you ship code when it is ready, and you expose it to users when you are confident it works
Something breaks in production. Not a full outage — just one feature, one flow, one integration misbehaving. Users are hitting errors. Your options without a plan: roll back the entire deployment, push a hotfix and redeploy, or take the whole site down while you diagnose.
None of those are good options. A full rollback might undo work you needed. A hotfix under pressure is where new bugs are born. And maintenance mode for a single broken button is overkill that affects every user on the platform.
There is a better approach: a layered response using two tools Laravel already gives you.
What is the difference between maintenance mode and feature flags?
Laravel maintenance mode and Laravel Pennant solve adjacent but distinct problems. Knowing which to reach for determines how quickly you can respond and how much collateral impact you cause.
Maintenance mode is a broad tool. It puts a user-facing "down for maintenance" screen in front of every request to the application. Use it when the problem affects the entire application — a database migration running longer than expected, a critical infrastructure issue, a dependency that is completely broken.
Laravel Pennant is a surgical tool. It lets you deactivate a specific feature for all users, for a segment of users, or for a single account, while the rest of the application continues running normally. Use it when the problem is isolated — a broken integration, a new feature behaving unexpectedly, a release that went sideways for one user cohort.
Together, they give you a layered incident response that matches the scope of the problem.
How does Laravel maintenance mode work in practice?
Maintenance mode is built into the framework and requires no setup beyond a single command:
php artisan down --secret="my-bypass-token"
The --secret flag generates a bypass URL your team can use to access the site while users see a clean maintenance screen. No SSH required to check on a running migration. No need to whitelist IP addresses. The bypass token persists in the cached state.
You can also provide a redirect or a custom retry header:
php artisan down --redirect="/maintenance" --retry=60
Bringing the site back up is equally immediate:
php artisan up
In a Forge or Envoyer setup, these commands are available as one-click actions in the dashboard. For a genuine full outage, maintenance mode is your first move while you diagnose — users get a clean message instead of cryptic error pages, and you buy time to understand the scope of the problem before reacting.
How do you use Laravel Pennant as a kill switch?
Pennant stores feature state in a features database table. When a feature is wrapped in a Pennant check, disabling it for all users is a single method call — no deployment, no SSH:
// Define the feature (done at build time, not incident time)
Feature::define('invoice-export', fn (User $user) => true);
// Check it wherever the feature runs
if (Feature::active('invoice-export')) {
return $this->generateExport($invoice);
}
return response()->json(['message' => 'Export is temporarily unavailable.'], 503);
When something goes wrong with the export feature, you deactivate it:
// Turn it off for everyone immediately
Feature::deactivateForEveryone('invoice-export');
// Or target a specific user or segment
Feature::for($user)->deactivate('invoice-export');
The Blade template that renders the export button checks the same flag and hides it entirely when it is inactive. Users never see a broken interface — they see a feature that is temporarily unavailable. Once the fix is deployed, you reactivate:
Feature::activateForEveryone('invoice-export');
What does a real incident look like with this pattern?
Here is a concrete scenario. You ship a Stripe integration update on a Friday afternoon. Three hours later, errors start appearing — but only for users on annual billing plans. A partial failure affecting a specific cohort.
Without feature flags, the options are redeploying or rolling back the entire release. With Pennant, you deactivate stripe-annual-billing-v2. The previous code path takes over for that segment. Users on annual plans see a graceful message. Everyone else is completely unaffected. You fix the issue over the weekend and reactivate the flag when the fix is deployed.
The key detail: the flag was defined when the feature was built, not retrofitted during the incident. Wrapping a critical feature in a Pennant check while you are building it takes minutes. Doing it under pressure during a live incident is significantly harder and introduces its own risk.
How do you build an admin toggle to manage flags without the command line?
Pennant stores state in the database, which means you can build a simple admin interface for toggling features without any command-line access. A basic toggle controller:
public function toggle(Request $request, string $feature): void
{
Feature::active($feature)
? Feature::deactivateForEveryone($feature)
: Feature::activateForEveryone($feature);
}
An authenticated admin hits a route, flips a toggle, and the change is immediate across all application instances. No SSH. No deployment. No waking up a developer at 2 AM to run an artisan command.
This is also where Livewire makes a natural fit — a real-time admin panel that reflects current flag state and updates it without a page reload is a straightforward Livewire component built on top of Pennant's simple API.
Why should you treat deployment and release as separate events?
The underlying pattern here — and the reason kill switches are worth building proactively — is treating code deployment and feature release as distinct steps.
You deploy code when it is ready and tested. You release the feature to users when you are confident it works at production scale with real data. The gap between those two events is where feature flags live. Flags give you the ability to ship continuously while still controlling what users actually see.
This is standard practice at teams that ship frequently and need high deployment confidence. The tools are built into Laravel. The overhead is low. And the alternative — finding out a feature is broken after 100% of users have already encountered it — is a much more expensive way to learn.
For a broader view of how gradual rollouts work, the Laravel Pennant A/B testing guide covers percentage-based rollouts, lottery distribution, and beta program patterns in detail.
If you are building something where deployment reliability matters — and it always does — get in touch. Wiring this in before the first incident is the right time to do it.
Kill switches and incident response patterns like these are part of how Pixelworx structures ongoing maintenance and support — production readiness built in, not bolted on after something breaks.