How to Solve 419 Page Expired in Laravel When Submitting Forms from Subdomain
You have a Laravel app on main-site.com, a form on sub.main-site.com, and when you submit the form you get: 419 Page Expired The page has expired due to inactivity. The route exists, your controller is fine, and the same form works perfectly from the main domain. The problem is not the route. It is the way Laravel handles sessions and CSRF tokens across subdomains.
Let’s fix it step by step.
1. Why 419 Page Expired happens
Laravel protects POST, PUT, PATCH, and DELETE requests with CSRF tokens. To validate the token, Laravel needs:
- A valid
_tokenfield in the form orX-CSRF-TOKENheader. - A session cookie that matches that token.
When you submit from a subdomain like sub.main-site.com to main-site.com, one of these often breaks:
- The browser does not send the main domain’s session cookie.
- The CSRF token in the form is generated for one domain but the request hits another.
Result: Laravel cannot match token + session, so it throws a 419 Page Expired error.
2. Make sure the form includes a valid CSRF token
First, confirm that the form really has a token.
In Blade:
<form method="POST" action="https://main-site.com/contact/submit">
@csrf
<!-- your inputs -->
<button type="submit">Send</button>
</form>
If you build the HTML manually, the token field must look like this:
<input type="hidden" name="_token" value="{{ csrf_token() }}">
View the page source on the subdomain and double check that _token is present and not empty.
If the form is generated by Vue/React, you can inject the token into a meta tag and read it:
<meta name="csrf-token" content="{{ csrf_token() }}">
Then, in JS, you can add it to a hidden input or header.
3. Share sessions between main domain and subdomain
To validate CSRF, Laravel needs to read the session cookie that was created when the token was generated.
By default, that cookie is bound to a specific domain (like main-site.com only).
If your form lives on sub.main-site.com but the app that processes the form is main-site.com, you usually want a shared session domain.
Open config/session.php and look at:
'domain' => env('SESSION_DOMAIN', null),
Now set SESSION_DOMAIN in your .env so the cookie works on the main domain and all subdomains.
For all subdomains of main-site.com:
SESSION_DOMAIN=.main-site.com
Note the leading dot. That tells the browser:
“Send this cookie to main-site.com and any subdomain like sub.main-site.com, admin.main-site.com, etc.”
After changing this value, clear config cache:
php artisan config:clear
php artisan cache:clear
Then log out and log in again so a new cookie with the correct domain is generated.
4. Make sure APP_URL and form action URLs match
If the app’s APP_URL is https://main-site.com, but the form uses http://main-site.com or another host, the browser may treat them differently, and some middlewares or redirects can break the CSRF flow.
In .env:
APP_URL=https://main-site.com
Then in your Blade templates, build URLs with helpers instead of hard coding:
<form method="POST" action="{{ url('/contact/submit') }}">
@csrf
...
</form>
If your form on the subdomain is still using something like http://main-site.com, change it to {{ url('...') }} or use route('contact.submit').
Always submit to the same scheme (HTTPS vs HTTP) and same host that your Laravel app expects.
5. Check SameSite and secure cookie settings
Modern browsers limit cross-site cookies using the SameSite attribute. Even subdomains can get caught in this if configuration is strict.
In config/session.php:
'same_site' => env('SESSION_SAME_SITE', 'lax'),
For most cases with subdomains, lax is fine, and the cookie is still sent on normal POST requests.
If you are doing something more advanced (iframes, third-party domains), you might need:
SESSION_SAME_SITE=none
SESSION_SECURE_COOKIE=true
When you use SameSite=None, the cookie must be a secure cookie (HTTPS).
On production:
SESSION_DRIVER=file
SESSION_DOMAIN=.main-site.com
SESSION_SAME_SITE=none
SESSION_SECURE_COOKIE=true
Then clear config and try again.
6. Subdomain Laravel app posting to another Laravel app
Sometimes the subdomain runs its own Laravel code (SPA, microsite) that posts to the main Laravel app. In that case:
- Both apps must share the same
APP_KEY. - Both must share the same
SESSION_DOMAINso cookies are valid across them. - The subdomain app must use the CSRF token from the main app if the form is processed there.
Simplest pattern:
- Serve one main Laravel app from
main-site.com. - Use it to render the subdomain front end templates as well.
- That way CSRF and sessions are all in one place.
If you really must have two separate apps, consider turning off CSRF for specific, strictly validated cross-app routes and using signed or JWT tokens instead, but do that only if you understand the security trade-offs.
7. When using JavaScript (Axios / Fetch) from the subdomain
If the form submission is actually an AJAX call to the main domain, you also need:
- Proper CORS configuration.
- Credentials (cookies) to be sent with the request.
- CSRF token in the headers.
Example with Axios on sub.main-site.com sending to main-site.com:
import axios from 'axios';
axios.defaults.withCredentials = true;
axios.post('https://main-site.com/contact/submit', {
name: 'John',
message: 'Hello',
}, {
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'X-Requested-With': 'XMLHttpRequest',
},
});
On the Laravel side, configure CORS (config/cors.php) to allow the subdomain origin.
8. Temporarily disable CSRF for testing (do not leave like this)
If nothing seems to work and you just want to confirm that the problem is CSRF, you can temporarily exclude a route from CSRF verification.
In app/Http/Middleware/VerifyCsrfToken.php:
protected $except = [
'contact/submit-from-subdomain',
];
If the 419 error disappears after excluding this route, you know your configuration is the issue.
Turn the protection back on once you finish debugging by removing the route from $except.
9. Quick checklist
When you see “419 Page Expired” in Laravel while submitting forms from a subdomain:
- Confirm the form has a valid
_tokenusing@csrf. - Set
SESSION_DOMAIN=.main-site.comso sessions are shared across subdomains. - Ensure
APP_URLand form action URLs use the correct scheme and host. - Adjust
SESSION_SAME_SITEandSESSION_SECURE_COOKIEif needed. - If using Axios or Fetch, send the CSRF token and cookies with the request.
- Avoid keeping routes permanently excluded from CSRF; use that only for debugging.
Once the session cookie and CSRF token line up between the main domain and subdomain, the 419 Page Expired error will disappear and your form submissions will work reliably.