Changing a production .env file and still seeing the old database host, mail settings, application URL, or feature flag is a familiar Laravel deployment surprise.
Usually, Laravel is doing exactly what it was told to do: use a cached configuration snapshot.
Disclosure: Codegenie maintains the open-source package discussed near the end of this article. The normal Laravel deployment commands remain the preferred solution.
Why the new value is ignored
Laravel's configuration files read environment variables and expose them through the configuration repository:
// config/services.php
return [
'example' => [
'endpoint' => env('EXAMPLE_ENDPOINT'),
],
];
Application code should then read the configuration value:
$endpoint = config('services.example.endpoint');
When you run:
php artisan config:cache
Laravel combines the configuration into a cached snapshot. Once that snapshot exists, the framework does not load the application's .env file during requests or Artisan commands. That is why editing .env alone does not change the value your application sees.
Laravel also recommends calling env() only from configuration files. Calling env() directly from controllers, jobs, or services can return null after configuration has been cached.
The official explanation is in Laravel's configuration caching documentation.
The normal production fix
A reliable deployment should rebuild the cache after the new code and environment configuration are in place:
composer install --no-dev --optimize-autoloader
php artisan config:cache
If the application uses route caching, rebuild that too:
php artisan route:cache
If you intentionally do not want cached configuration, clear it instead:
php artisan config:clear
The important part is that the cache command belongs to the deployment process. Run it on every release, after all relevant files and environment settings have been updated.
For deployments with release directories, build caches inside the new release before switching the live symlink. That avoids serving a half-updated application.
Why FTP and shared hosting make this harder
The ideal flow assumes you can execute PHP CLI commands during deployment.
On shared hosting that assumption may be false:
- the site is uploaded through FTP;
- cPanel or Plesk provides no useful deploy hook;
- SSH is unavailable;
- PHP functions such as
exec()are disabled; - the old cache file survives while
.envorconfig/*.phpchanges.
Clearing a browser cache or application data cache does not solve this. Configuration cache and route cache are deployment artifacts, not regular response or database cache.
A web request could notice the problem, but timing matters. Laravel reads cached configuration while the application is bootstrapping. Middleware and normal service providers run too late to prevent that request from loading stale configuration.
What a safe fallback needs to do
A useful fallback should protect correctness without pretending to replace a deployment pipeline.
It should:
- run before Laravel consumes cached configuration or routes;
- detect relevant source changes without reading or storing secret values;
- stop stale cache from being used immediately;
- rebuild through PHP CLI when that is available;
- degrade safely when shell execution is unavailable;
- use locks and exact source signatures to avoid racing repairs;
- avoid enabling cache types the application was not already using.
That design is the basis of Laravel Config Cache Guard.
How the fallback works
Install it with one Composer command:
composer require codegenie-be/laravel-config-cache-guard
Composer loads the guard before bootstrap/app.php. It compares metadata for relevant sources such as .env, configuration files, route files, and route bootstrap files. It tracks timestamps, sizes, and filesystem metadataβnot environment values.
When the sources are unchanged, the request continues normally.
When an existing config cache is stale, the guard prevents Laravel from using it. When an existing route cache is stale, the guard bypasses it using a signature-based cache path.
If PHP CLI execution is available, it can rebuild before Laravel boots.
If exec() is disabled, the current request continues without the known-stale deployment cache. The guard writes an internal pending marker, and the package service provider rebuilds through Laravel's own Artisan::call() after the response. A following request can then use the refreshed cache.
There is no public repair endpoint, no repair token, and no telemetry.
Defaults that avoid surprising applications
The package does not automatically turn on config caching for every project.
If no config cache exists, config-cache creation remains disabled unless you explicitly opt in:
CONFIG_CACHE_GUARD_CREATE_CONFIG_CACHE=true
Route guarding starts only when the application already has route cache.
This matters because a correctness guard should not silently change an application's caching policy.
You can optionally inspect the integration with:
php artisan config-cache-guard:status
That status command is verification, not a second installation step.
Limitations
This fallback still needs a writable Laravel bootstrap cache directory.
Without exec(), repair happens after the current response. The first request runs without stale deployment cache, but it may be uncached. The next request uses the rebuilt cache if repair succeeded.
Custom bootstrap paths selected later in application code cannot always be discovered before Laravel boots.
And most importantly: if your platform supports reliable deployment hooks, keep using the standard Laravel cache commands. The package is a safety net for restricted hosting, not a replacement for a healthy deployment process and not a promise to make every website faster.
Compatibility and testing
The current release supports Laravel 12 and 13 on non-EOL PHP versions. Its test suite installs fresh Laravel applications and verifies real HTTP requests, including Linux and Windows scenarios and the exec()-disabled repair path.
The source, demo, security notes, and deployment recipes are available on GitHub.
If you deploy Laravel through FTP, cPanel, Plesk, or another restricted host, technical feedback is especially welcome. Which deployment limitations have you encountered, and how do you currently prevent stale configuration from reaching users?
Top comments (0)