Sitemap

Zero-Downtime Secrets Rotation for Laravel

5 min readDec 7, 2025

--

Stop manually rotating API keys. Let your Laravel app do it automatically.

Press enter or click to view image in full size

If you’ve ever had to rotate database credentials, API keys, or access tokens in a production Laravel application, you know the drill: update the secret, deploy, pray nothing breaks during the transition, then clean up the old credentials. It’s tedious, error-prone, and often done less frequently than it should be because of the hassle involved.

What if your Laravel app could rotate its own secrets automatically, with zero downtime, and clean up after itself?

The Problem with Manual Rotation

Most Laravel applications store secrets in .env files or environment variables. Rotating them typically means:

  1. Generate new credentials from the provider (AWS, Stripe, etc.)
  2. Update your .env or secrets manager
  3. Deploy or restart your application
  4. Hope no requests fail during the transition
  5. Remember to delete the old credentials
  6. Repeat every 30–90 days (if you’re disciplined)

The “hope no requests fail” part is where things get dicey. There’s always a window where the old credential is invalid but some server instances haven’t picked up the new one yet. And that last step — deleting old credentials — often gets forgotten, leaving orphaned keys that become security liabilities.

Introducing Laravel Locksmith

Locksmith is a Laravel package that handles the complete lifecycle of secret rotation: generate, validate, swap, and cleanup — with zero downtime.

composer require brainlet-ali/laravel-locksmith
php artisan locksmith:install

The key insight is grace periods. When you rotate a secret, Locksmith keeps both the old and new values valid for a configurable period (default: 60 minutes). Your application can use either value during this window. After the grace period expires, a queued job automatically deletes the old credential from the provider.

No deployment required. No prayer required.

How It Works

The Grace Period

ROTATION HAPPENS
├── New key generated and validated
├── Old key moved to "previous_value"
├── Both keys work for 60 minutes
└── Cleanup job scheduled
GRACE PERIOD (60 min)
├── Application uses new key
├── Old key still valid (fallback)
└── Cached configs gradually refresh
AFTER GRACE PERIOD
├── Cleanup job runs
├── Old key deleted from provider (AWS, etc.)
└── Only new key remains

This solves the “requests failing during transition” problem entirely. Any request that started with the old key will complete successfully. Any new request uses the new key. No coordination needed.

Two Ways to Rotate

1. Recipes — For services with key-generation APIs

Recipes are pre-built rotation strategies. Locksmith ships with an AWS IAM recipe that creates new access keys, validates them, and deletes old ones automatically.

# Initialize with your IAM user credentials
php artisan locksmith:init aws.credentials
# Rotate anytime
php artisan locksmith:rotate aws.credentials --recipe=aws

Output:

Discarding previous key for [aws.credentials]...
Rotating secret [aws.credentials]...
Secret [aws.credentials] rotated successfully.

Behind the scenes, Locksmith:

  • Deleted any previous key that was in grace period
  • Created a new IAM access key via AWS API
  • Validated the new key works (STS GetCallerIdentity)
  • Stored the new key encrypted in your database
  • Scheduled a cleanup job for 60 minutes later

2. Key Pools — For services without APIs

Some services don’t have APIs to generate new keys (think: pre-shared API tokens, license keys, or third-party services that email you new credentials). For these, Locksmith offers key pools.

# Add pre-generated keys to the pool
php artisan locksmith:pool api.secret --add
> Paste your keys (one per line)
> key_abc123
> key_def456
> key_ghi789
# Rotate to the next key in pool
php artisan locksmith:pool api.secret --rotate

Locksmith tracks which keys are queued, active, and used. When the pool runs low, it fires an event so you can be notified to add more keys.

Reading Secrets in Your Application

use BrainletAli\Locksmith\Facades\Locksmith;
// Get the current secret value
$apiKey = Locksmith::get('api.secret');
// Check if a secret exists
if (Locksmith::has('stripe.key')) {
// ...
}
// Get all valid values (current + previous if in grace period)
$validKeys = Locksmith::getValidValues('api.secret');

The getValidValues() method is useful for validation scenarios where you need to accept both old and new keys during transition.

Scheduling Automatic Rotation

Add rotation to your Laravel scheduler for hands-off operation:

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// Rotate AWS credentials weekly
$schedule->command('locksmith:rotate aws.credentials --recipe=aws')
->weekly();
    // Rotate all configured pools daily
$schedule->command('locksmith:pool-rotate')
->daily();
// Clean up expired grace periods
$schedule->command('locksmith:clear-expired')
->hourly();
}

Notifications

Get notified when rotations succeed, fail, or when key pools run low:

// config/locksmith.php
'notifications' => [
'enabled' => true,
'channels' => ['mail', 'slack'],
'recipients' => ['ops@example.com'],
'slack_webhook' => env('LOCKSMITH_SLACK_WEBHOOK'),
],

The Self-Cleaning Part

This is where Locksmith shines. AWS IAM users can only have 2 access keys at a time. If you rotate twice without cleaning up, you’re stuck.

Locksmith handles this automatically:

  1. During rotation: If there’s a previous key in grace period, Locksmith deletes it from AWS before generating a new one
  2. After grace period: A queued job deletes the old key from AWS and clears the previous_value

You never hit the 2-key limit. You never have orphaned credentials. The system is self-cleaning.

Under the Hood

Secrets are stored in a locksmith_secrets table with AES-256 encryption (using Laravel's built-in encryption). Each secret has:

  • value — Current encrypted secret
  • previous_value — Previous value during grace period
  • previous_value_expires_at — When grace period ends

Rotation logs track every operation with correlation IDs, timing, and error messages for debugging.

// Get rotation history
$logs = Locksmith::getLogs('aws.credentials');
// Get recent failures
$failures = Locksmith::getRecentFailures(hours: 24);

Building Custom Recipes

The AWS recipe is just one implementation. You can build recipes for any service:

use BrainletAli\Locksmith\Contracts\Recipe;
use BrainletAli\Locksmith\Contracts\DiscardableRecipe;
class StripeKeyRecipe implements Recipe, DiscardableRecipe
{
public function generate(): string
{
$key = Stripe::apiKeys()->create(['name' => 'auto-rotated']);
return json_encode(['id' => $key->id, 'secret' => $key->secret]);
}
public function validate(string $value): bool
{
$data = json_decode($value, true);
Stripe::setApiKey($data['secret']);
return Stripe::balance()->retrieve() !== null;
}
public function discard(string $value): void
{
$data = json_decode($value, true);
Stripe::apiKeys()->delete($data['id']);
}
}

Register it in config:

// config/locksmith.php
'recipes' => [
'stripe' => [
'driver' => App\Locksmith\StripeKeyRecipe::class,
'keys' => ['stripe.api_key'],
],
],

Now php artisan locksmith:rotate stripe.api_key --recipe=stripe works.

Getting Started

composer require brainlet-ali/laravel-locksmith
php artisan locksmith:install

The install command publishes the config file, runs migrations, and optionally installs the AWS SDK if you select the AWS recipe.

For AWS IAM rotation:

php artisan locksmith:init aws.credentials
# Enter: username, access_key_id, secret_access_key
php artisan locksmith:rotate aws.credentials --recipe=aws

Check status anytime:

php artisan locksmith:status
+-----------------+--------------+---------------+---------------+
| Key | Status | Last Rotation | Rotated |
+-----------------+--------------+---------------+---------------+
| aws.credentials | Grace Period | Success | 5 minutes ago |
+-----------------+--------------+---------------+---------------+

Why I Built This

I got tired of the manual rotation dance. Every security audit asks “how often do you rotate credentials?” and the honest answer was “not often enough.” The tooling either didn’t exist for Laravel or required external infrastructure like AWS Secrets Manager or HashiCorp Vault.

Locksmith is Laravel-native. It uses your existing database, queue, and scheduler. No external dependencies beyond the providers you’re already using.

Try It

The package is MIT licensed and available now:

Feedback, issues, and PRs welcome. If you find it useful, a star on GitHub helps others discover it.

Laravel Locksmith — because secrets should rotate themselves.

--

--