Laravel defaults are developer-friendly, but production environments require strict hardening. This checklist focuses on the controls that close common breach paths first.
Critical baseline
- Set
APP_DEBUG=falsein production. - Block public access to
.envand backups. - Rotate
APP_KEYand sensitive service credentials as needed.
Auth and session controls
- Enforce strong password + MFA where applicable.
- Use secure session/cookie flags.
- Throttle login/reset endpoints.
Queue, jobs, and deployment
- Run workers with least privileges.
- Sanitize job payloads and reduce sensitive logging.
- Validate deploy pipeline secrets and artifact exposure.
Copy-paste Laravel production hardening checklist
# .env production baseline
APP_ENV=production
APP_DEBUG=false
LOG_LEVEL=error
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=strict
# Block .env access in Apache .htaccess
<FilesMatch "^\.env">
Order allow,deny
Deny from all
</FilesMatch>
# Nginx equivalent
location ~ /\.env {
deny all;
return 403;
}
Database and secrets hygiene
- Each application environment (staging, production) must use a separate database user with minimum required permissions.
- Never use root database credentials in
.env. Create a dedicated user:GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO laravel_user@localhost; - Store secrets in environment variables or a vault — never commit them to Git. Review the exposed .env file fix guide.
- Rotate
APP_KEYusingphp artisan key:generateafter any suspected credential leak.
Security headers for Laravel
- Add security headers middleware to
app/Http/Kernel.phpusing a package likespatie/laravel-cspfor CSP. - Apply
Strict-Transport-Security,X-Frame-Options: SAMEORIGIN, andX-Content-Type-Options: nosniffglobally. - Review the complete security headers guide for copy-paste Apache/Nginx configs.
- Use AI QA Monkey free security scanner to verify all headers are live in production.
CORS configuration for Laravel APIs
- Configure
config/cors.phpwith explicit allowed origins — never use*for authenticated APIs. - Review the CORS misconfiguration fix guide for Laravel-specific patterns.
Input validation and mass assignment protection
Laravel Eloquent's mass assignment feature is one of the most frequently exploited Laravel vulnerabilities. Attackers inject unexpected fields through POST requests to elevate privileges or corrupt data.
- Always define
$fillableor$guarded: Never leave both empty.protected $guarded = []makes all columns mass-assignable — the most dangerous default. - Use Form Request validation: Replace inline
$request->validate()calls with dedicatedFormRequestclasses that enforce type, format, and business rules per endpoint. - Validate file uploads strictly: Use
mimes,max, anddimensionsrules. Store uploads outside thepublic/directory and serve through a signed URL controller. - Escape output in Blade templates: Always use
{{ }}(auto-escaped) instead of{!! !!}(raw). Audit every{!! !!}use case for XSS exposure.
Route and middleware security
Laravel routes define the entire attack surface of your API. Every unprotected route is a potential entry point.
- Apply
authmiddleware globally: Use route groups withmiddleware('auth')and explicitly whitelist public routes. Avoid relying on developers to remember to add auth per route. - Rate limiting on sensitive routes: Apply
throttlemiddleware to login, password reset, registration, and any resource-creation endpoint. Default: 5 attempts per minute for auth routes. - Audit
api.phpvsweb.php: API routes lack CSRF protection by default. Ensure all state-changing API endpoints use token authentication (Sanctum, Passport) rather than session-based auth. - Remove unused routes: Run
php artisan route:listand audit every route. DELETE unused routes — they are undocumented attack surface.
Logging and monitoring for production
Silent failures are dangerous in production Laravel apps. Proper logging captures the evidence needed to detect intrusions and debug security incidents.
- Set
LOG_LEVEL=errorin.envproduction to avoid logging sensitive request data. - Integrate with a log aggregation platform (Papertrail, Logtail, CloudWatch) to alert on error spikes, 500 responses, and authentication failures.
- Never log raw request bodies or query strings containing tokens or passwords. Use
$request->except(['password', 'token'])in log calls. - Monitor
failed_jobstable for queue failures — failed jobs can indicate serialization attacks or dependency issues in deserialized payloads.
Dependency and package security
PHP package vulnerabilities are a growing attack vector. The Composer ecosystem has seen multiple supply chain incidents.
- Run
composer audit(available since Composer 2.4) on every deployment to detect packages with known CVEs. - Pin package versions in
composer.lockand commit it to version control. Never runcomposer updatein production without testing. - Remove development dependencies from production:
composer install --no-dev --optimize-autoloader. - Subscribe to PHP security advisories via FriendsOfPHP/security-advisories or integrate Snyk/Dependabot into your CI pipeline.
Deployment pipeline hardening
The deployment process is a common source of secret leaks. CI/CD pipelines that log environment variables or store artifacts insecurely can expose production credentials.
- Store secrets exclusively in CI/CD secret stores (GitHub Actions Secrets, GitLab CI Variables) — never in
.envfiles committed to the repository. - Use deployment user accounts with minimal SSH permissions. Avoid deploying as root.
- Run
php artisan config:cache,route:cache, andview:cachepost-deploy to optimize and validate configuration. - Set up a post-deploy health check that verifies
APP_DEBUG=falseand tests a protected endpoint to catch misconfiguration before traffic hits.
Laravel Sanctum and Passport: API authentication hardening
Laravel Sanctum (SPA and mobile auth) and Passport (full OAuth2) have distinct security models. Teams frequently misconfigure both, leading to token exposure and privilege escalation.
- Sanctum token expiration: By default, Sanctum personal access tokens do not expire. Set a global expiration in
config/sanctum.php:'expiration' => 60 * 24(24 hours in minutes). For high-security applications, use shorter windows and implement token refresh. - Sanctum SPA CSRF: SPA authentication via Sanctum requires the frontend to first call
/sanctum/csrf-cookieto get a CSRF cookie. If this step is skipped or theSESSION_DOMAINis misconfigured, CSRF protection is bypassed. - Passport scope restriction: Define explicit scopes for each OAuth client and validate scopes on protected routes using
->middleware('scope:read-users'). Tokens without scope restrictions grant access to all endpoints. - Revoke tokens on password change: When a user changes their password, all existing tokens should be revoked. Implement this in the password update controller:
$user->tokens()->delete()for Sanctum or$user->oauthAccessToken()->delete()for Passport. - Sanctum stateful domains: Verify
SANCTUM_STATEFUL_DOMAINSin.envlists only production frontend domains. An overly broad wildcard allows CSRF attacks from unexpected origins.
Laravel SQL injection: parameterization and raw queries
Laravel's Eloquent ORM parameterizes queries automatically. The vulnerability appears when developers bypass the ORM for performance and use raw queries incorrectly.
// UNSAFE — string interpolation in raw query
$users = DB::select("SELECT * FROM users WHERE name = '$name'");
// SAFE — parameterized bindings
$users = DB::select("SELECT * FROM users WHERE name = ?", [$name]);
// SAFE — named bindings
$users = DB::select("SELECT * FROM users WHERE name = :name", ['name' => $name]);
// SAFE — Eloquent (auto-parameterized)
$users = User::where('name', $name)->get();
// UNSAFE — orderBy with user input (column names cannot be parameterized)
$users = User::orderBy($request->sort)->get(); // SQL injection if $sort is not validated
// SAFE — validate against allowlist for dynamic column names
$allowedColumns = ['name', 'email', 'created_at'];
$sort = in_array($request->sort, $allowedColumns) ? $request->sort : 'created_at';
$users = User::orderBy($sort)->get();
File upload security in Laravel
File uploads are a high-risk feature. Improperly handled uploads allow attackers to upload PHP web shells, malware, or oversized files that exhaust disk space.
- Validate MIME type server-side: Use
mimes:jpg,jpeg,png,pdfin Form Request rules. Do not rely on the file extension — validate the actual MIME type usingfinfoor Laravel's built-in MIME validation. - Store outside public directory: Use
storage/app/private/for uploaded files and serve them through a controller with authorization checks:Storage::disk('private')->path($filename). - Rename uploaded files: Never use the original filename. Generate a UUID or hash:
$file->storeAs('uploads', Str::uuid() . '.' . $file->extension()). This prevents path traversal and overwrites of existing files. - Limit file size: Set both Laravel validation (
'max:2048'in KB) and PHP'supload_max_filesizeandpost_max_sizeinphp.ini. Laravel validation alone does not prevent oversized files from reaching PHP memory. - Scan for malware (high-risk applications): Integrate ClamAV or a cloud scanning API (VirusTotal, AWS Malware Protection) for applications handling untrusted file uploads at scale.
Laravel HTTP client and server-side request forgery (SSRF)
Laravel's HTTP client wraps Guzzle and is widely used for external API calls. When URL parameters are user-controlled, the application becomes vulnerable to Server-Side Request Forgery (SSRF) — where the server makes requests to internal infrastructure on behalf of the attacker.
// VULNERABLE — user controls the URL
$url = $request->input('webhook_url');
$response = Http::get($url); // Attacker could pass: http://169.254.169.254/latest/meta-data/
// SAFE — validate against allowlist of approved domains
$allowedDomains = ['api.stripe.com', 'api.sendgrid.com'];
$parsed = parse_url($request->input('webhook_url'));
if (!in_array($parsed['host'] ?? '', $allowedDomains)) {
abort(422, 'Webhook URL not permitted.');
}
// ALSO SAFE — block private IP ranges explicitly
// Use a library like symfony/http-foundation's IpUtils to validate
// that the resolved IP is not in RFC1918 private ranges
Scan your application now
Detect .env exposure, debug leaks, CORS misconfigurations, missing security headers, and production hardening gaps.
Run Free Security Scan