The 419 status code isn't part of the official HTTP spec, but Laravel and a handful of other frameworks use it to signal a failed CSRF token check. You'll see "419 Page Expired" or "419 Authentication Timeout" when the token your browser submitted doesn't match the one the server expects—or when the session that issued the token has already died.
It's not a server crash. It's a deliberate security gate.
Most 419 errors happen after a user leaves a form open for an hour, the session expires, and they finally click submit. The framework rejects the stale token and throws 419. Below are the six fixes that resolve it in nearly every case I've debugged.
1. Check the CSRF token is actually in your form
Laravel won't accept a POST, PUT, PATCH, or DELETE request unless it carries a valid token. If your Blade template is missing @csrf, the request arrives without one and fails immediately.
Open the form template and confirm you have:
<form method="POST" action="/your-route">
@csrf
<!-- fields -->
<button type="submit">Submit</button>
</form>
The @csrf directive injects a hidden input named _token. Inspect the rendered HTML in your browser to verify the field exists and contains a 40-character hash.
If you're sending AJAX requests, include the token in the request headers:
fetch('/api/endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify(data)
});
Make sure your layout includes the meta tag:
<meta name="csrf-token" content="{{ csrf_token() }}">
2. Extend the session lifetime
By default Laravel sessions expire after 120 minutes of inactivity. If users fill out long forms or leave tabs open, the token becomes orphaned.
Edit config/session.php:
'lifetime' => 480, // 8 hours
'expire_on_close' => false,
After the change, restart queues and clear config cache:
php artisan config:cache
php artisan queue:restart
On shared hosting with limited SSH access, you can adjust SESSION_LIFETIME in .env instead:
SESSION_LIFETIME=480
Then clear the config cache through cPanel's PHP CLI tool or request the host run php artisan config:cache for you.
3. Verify the session driver is working
Laravel supports file, database, Redis, and Memcached session drivers. If the driver can't write sessions, tokens never persist and every request gets a fresh token that won't match the form's hidden field.
Check which driver you're using in .env:
SESSION_DRIVER=file
File driver: confirm storage/framework/sessions exists and is writable by the web server. Run:
ls -ld storage/framework/sessions
chmod -R 775 storage/framework/sessions
chown -R www-data:www-data storage/framework/sessions
Replace www-data with nobody, apache, or nginx if your server uses a different user.
Database driver: make sure the sessions table exists:
php artisan session:table
php artisan migrate
Test the connection by inserting a dummy record into the table manually through phpMyAdmin or Adminer.
Redis/Memcached: verify the service is running and Laravel can reach it. Check the connection settings in config/database.php and run a ping:
redis-cli ping
If it returns PONG, the service is alive. If not, start it:
sudo systemctl start redis
4. Fix cookie domain and path mismatches
Sessions rely on cookies, and browsers won't send cookies back if the domain or path doesn't match. A misconfigured SESSION_DOMAIN or SESSION_SECURE_COOKIE will silently break token validation.
Open config/session.php and check these values:
'domain' => env('SESSION_DOMAIN', null),
'path' => '/',
'secure' => env('SESSION_SECURE_COOKIE', false),
'same_site' => 'lax',
Subdomain issues: if your app runs on app.example.com but SESSION_DOMAIN is set to example.com, the cookie won't attach. Set it to null or the exact subdomain:
SESSION_DOMAIN=app.example.com
HTTPS mismatch: if SESSION_SECURE_COOKIE=true but your site loads over HTTP (or behind a proxy that doesn't forward the X-Forwarded-Proto header), cookies won't send. Either enforce HTTPS everywhere or set it to false in .env:
SESSION_SECURE_COOKIE=false
For sites behind Cloudflare or a load balancer, add this to app/Http/Middleware/TrustProxies.php:
protected $proxies = '*';
protected $headers = \Illuminate\Http\Request::HEADER_X_FORWARDED_FOR |
\Illuminate\Http\Request::HEADER_X_FORWARDED_HOST |
\Illuminate\Http\Request::HEADER_X_FORWARDED_PORT |
\Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO;
Clear the cache and test again.
5. Exclude specific routes from CSRF protection
Webhooks and third-party callbacks can't include a CSRF token because they originate outside your app. Stripe, PayPal, and Mailgun POST to your endpoints without touching a form first.
Add those routes to the exception list in app/Http/Middleware/VerifyCsrfToken.php:
protected $except = [
'webhook/stripe',
'webhook/paypal',
'api/*',
];
Use wildcards carefully. Excluding api/* is fine if your API uses token-based auth, but don't exclude routes that handle user actions.
6. Check for middleware conflicts and duplicate tokens
Sometimes custom middleware or JavaScript frameworks overwrite the token before submission.
If you're using Vue, React, or Inertia, confirm the CSRF token is being passed correctly. Inertia includes it automatically in the X-XSRF-TOKEN header, but you need to ensure the cookie is set:
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
Laravel reads the token from either the _token POST field or the X-CSRF-TOKEN header. If your JavaScript submits the form programmatically and strips the hidden field, the request fails.
Inspect the request in Chrome DevTools under the Network tab. Look at the Form Data or Payload section and verify _token is present. If it's missing, your JavaScript is removing it.
So what about shared hosting and file lock issues?
On cheap shared hosting, dozens of sites share the same filesystem, and simultaneous writes to session files sometimes cause race conditions. The file driver might fail to write the session entirely, leading to token mismatches.
Switch to the database driver. It's slower but more reliable in high-contention environments:
SESSION_DRIVER=database
Run the migration and test. If you still see 419 errors, the database itself might be overloaded or the connection pool exhausted. Check slow query logs and consider upgrading to a VPS.
Common mistakes that don't fix 419
Clearing browser cache: this helps with CSS or JS issues but won't fix an expired token on the server side.
Disabling CSRF protection entirely: possible by removing \App\Http\Middleware\VerifyCsrfToken::class from app/Http/Kernel.php, but it opens your app to cross-site request forgery attacks. Don't do it.
Restarting Apache or Nginx without clearing Laravel's config cache: the old cached values stay in bootstrap/cache/config.php. Always run php artisan config:cache after changing .env or config files.
Quick diagnostic checklist
Run through this list in order when a 419 error appears:
- Confirm
@csrfis in the form template. - Check
storage/logs/laravel.logfor session write errors. - Verify the session driver is running and accessible.
- Inspect the rendered HTML to confirm the
_tokenfield exists. - Test cookie transmission in DevTools (Application → Cookies).
- Clear config and route caches:
php artisan config:cache && php artisan route:cache. - If behind a proxy, confirm
TrustProxiesmiddleware is configured.
Most 419 errors resolve after steps 1, 3, and 6.
What to check first
Start with the session driver and storage permissions. Ninety percent of 419 errors trace back to a session that can't persist because the file directory is unwritable, the database connection failed, or Redis isn't running.
If sessions are working, verify the token is actually in the form and cookies are transmitting correctly. Use browser DevTools to inspect both.
Extend the session lifetime if users regularly report 419 after filling out long forms, and exclude webhook routes from CSRF checks to prevent false positives from external services.
The error is fixable. Work through the checklist above and you'll narrow it down fast.
