To generate an app key in Laravel 13, run php artisan key:generate in the project root. The command creates a random 32-byte key, base64-encodes it and writes it to APP_KEY in your .env file. Laravel uses this key to encrypt cookies, sessions and anything you pass to Crypt or the encrypted cast, so every environment needs its own key and it must never be shared publicly.
New projects created with the Laravel installer or composer create-project already get a key. You run the command yourself after cloning a repository, copying .env.example, or setting up a new server.
Prerequisites
- Laravel 13 with PHP 8.3 or newer.
- A
.envfile in the project root (copy it from.env.exampleif it is missing). - Write access to
.envfor the user running Artisan.
Step 1: Create the .env file and generate the key
cp .env.example .env
php artisan key:generateArtisan prints a confirmation and .env now contains a line like this (yours will differ):
APP_KEY=base64:REPLACE_WITH_YOUR_OWN_GENERATED_VALUE=If your configuration is cached, clear it so Laravel reads the new value: php artisan config:clear.
What does APP_KEY protect?
- Cookies and sessions. Laravel encrypts every cookie, including the session cookie, with this key.
- Encrypted data. Values stored with
Crypt::encryptString()or an Eloquentencryptedcast. - Signed URLs. Temporary and signed routes are signed with the key, so changing it invalidates existing links.
It does not affect password hashes. Passwords are hashed with bcrypt or Argon2, which do not use APP_KEY, so users can still log in after a key change; they are only signed out of their current sessions.
Step 2: Use --show and --force where needed
The command has two options:
# Print a new key without touching .env
php artisan key:generate --show
# Allow the command to run when APP_ENV=production
php artisan key:generate --force--show is useful when your production secrets live in a platform dashboard or a secrets manager rather than a .env file: generate the value locally and paste it into the platform. In production, Artisan asks for confirmation before overwriting the key; --force skips that prompt. Only use it when you really mean to replace a key, because the old one is gone once it is overwritten.
How do you fix "No application encryption key has been specified"?
Laravel throws this error when APP_KEY is empty at runtime. Check these in order:
.envexists in the project root and contains a non-emptyAPP_KEY.- The config cache is not stale. Run
php artisan config:clear, thenphp artisan config:cacheagain on production. - In Docker or a platform, the variable is passed to the container or app. A key in your local
.envdoes not reach a container unless you mount the file or set the environment variable. - The value keeps its
base64:prefix. Without it, Laravel treats the string as a raw key of the wrong length.
Step 3: Rotate the key without logging everyone out
If a key leaks, for example in a public repository, replace it immediately. Laravel supports graceful rotation through APP_PREVIOUS_KEYS. Laravel encrypts with the current key, and when decryption fails it tries each previous key in turn.
php artisan key:generate --showPut the new value in APP_KEY and move the old one into APP_PREVIOUS_KEYS:
APP_KEY=base64:NEW_KEY_VALUE=
APP_PREVIOUS_KEYS=base64:OLD_KEY_VALUE=Deploy and rebuild the config cache. Existing sessions and encrypted columns keep working. If you want to stop depending on the old key completely, re-encrypt stored data with a short script and then remove it from APP_PREVIOUS_KEYS:
<?php
use App\Models\Integration;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
// Reading the cast attribute decrypts with whichever key works.
// Writing through the query builder stores a fresh value encrypted with the current key.
Integration::query()->chunkById(200, function ($integrations) {
foreach ($integrations as $integration) {
DB::table('integrations')
->where('id', $integration->id)
->update(['api_token' => Crypt::encryptString($integration->api_token)]);
}
});This example assumes api_token uses the encrypted cast, which stores values with encryptString. Reassigning the same value on the model and calling save() would not work: Eloquent compares the decrypted values, sees no change and skips the update. Writing through the query builder avoids that. Back up the table first and run it in a maintenance window if the table is large.
When a key has leaked, rotating it is not the whole fix: also rotate any secrets that were encrypted with it, because whoever has the old key can decrypt old copies of that data.
Common mistakes
- Committing
.env. Keep it in.gitignore. Commit only.env.examplewith an emptyAPP_KEY. - Sharing one key across environments. Local, staging and production should each have their own key.
- Regenerating the key on every deploy. Some deploy scripts run
key:generate --forceeach time, which logs everyone out and breaks encrypted data. Generate once per environment. - Several servers with different keys. Behind a load balancer, all app servers must share the same key or sessions fail randomly.
How do you verify it works?
php artisan tinker
>>> config('app.key') !== null
>>> Crypt::decryptString(Crypt::encryptString('hello'))The second line should return hello. In the browser, log in, refresh a few pages and confirm the session persists.
FAQ
Is APP_KEY used for password hashing?
No. Password hashes are independent of the key, so changing it does not invalidate passwords.
Can I write my own key by hand?
Do not. Use key:generate, which uses a cryptographically secure random generator and the correct length for the cipher.
Which cipher does Laravel use?
The default is AES-256-CBC, set by cipher in config/app.php. Values are also signed with a MAC so tampering is detected.



