The practical Laravel security checklist
By Andrea Pollastri · Last updated: · free to read, no paywall
Most Laravel apps don't get hacked by clever zero-days. They get hacked by exposed .env files, debug mode left on, unpatched PHP and weak SSH. This checklist covers the boring, high-impact work first — server, application, dependencies, secrets and response — with a way to automate each step.
What actually gets Laravel apps hacked
Ranked by how often they show up in real incidents, not by how interesting they are:
- Exposed debug output.
APP_DEBUG=truein production leaks environment variables, database credentials and file paths to anyone who triggers an error. - Readable
.envor.git/. Misconfigured document roots that serve dotfiles hand over your APP_KEY and credentials. - Unpatched PHP, framework or packages with known CVEs.
- Weak SSH: password auth, root login enabled, no brute-force protection.
- Unsafe file uploads that allow executable content into web-served paths.
- Injection in the escape hatches: raw SQL without bindings, unescaped
{!! !!}Blade output. - Admin panels without MFA and without rate limiting.
Boring beats clever: if you close these seven doors, you're ahead of the vast majority of production PHP deployments.
Server hardening checklist
- SSH: keys only, no root. Disable password authentication and root login; use a dedicated admin user. Add Fail2ban to ban repeated failures. (A fresh Cipi install applies exactly this by default: root login disabled, key-only admin access, Fail2ban and UFW enabled.)
- Firewall default-deny. Only 22, 80 and 443 should answer. Databases and Valkey/Redis
listen on localhost — verify with
ss -tlnp. - TLS everywhere, forced. Free Let's Encrypt certificates, HTTP redirected to HTTPS
(
cipi ssl installthencipi ssl force), HSTS enabled. - Patch on a schedule, not on memory. Unattended security updates for the OS; a
deliberate mechanism for PHP (Cipi runs a weekly PHP security-patch check —
cipi php upgrade). - Reduce fingerprinting:
expose_php = Off,server_tokens off. - Isolate apps from each other. Per-app system users and PHP-FPM pools limit the blast radius when one app is compromised — Cipi provisions this isolation per app automatically.
- Remove the leftovers: phpinfo files, Adminer, stale subdomains pointing at old boxes.
Application hardening checklist
APP_ENV=production,APP_DEBUG=false— non-negotiable.- Force HTTPS at the app level too (trusted proxies + forced scheme) so signed URLs and cookies behave.
- Validate everything with Form Requests; never trust client input, including headers and file names.
- Mass-assignment guards: explicit
$fillableon every model. - Authorize with policies on every route that touches someone else's data; add
Route::can()/ middleware, not ad-hoc ifs. - Rate limit login, registration, password reset and public APIs with Laravel's RateLimiter.
- Session & cookie flags:
secure,http_only,same_siteconfigured inconfig/session.php. - Stay inside Eloquent bindings. If you must use
whereRaw, pass bindings — never interpolate input into SQL strings. - Escape by default. Treat
{!! !!}as a code smell requiring justification and sanitization. - File uploads: validate MIME type and size, store outside
public/with random names, serve via signed URLs or a controller. - Security headers: X-Frame-Options, X-Content-Type-Options, Referrer-Policy and a CSP appropriate to your frontend.
Dependencies & supply chain
Your app is mostly other people's code, so treat dependency hygiene as a first-class security control:
- Commit lockfiles (
composer.lock,package-lock.json) so production runs exactly what you tested. - Run
composer auditin CI on every pull request — it fails the build when a dependency has a known advisory. (Wiring this into a pipeline takes five minutes — see our CI/CD guide.) - Automate update PRs with Renovate or Dependabot; small weekly bumps beat quarterly mega-upgrades.
- Scan for Laravel-specific misconfigurations. Checkpoint is an open-source Laravel security scanner you can add to your toolchain to catch framework-level mistakes before they ship.
- Scan from the outside too. A platform like Hackly runs recurring vulnerability scans against your public surface — the attacker's view of your stack, on a schedule.
Secrets & data protection
.envnever enters Git. Distribute secrets through your deploy tooling, not the repository.- Least-privilege database users: one user per app, no
GRANT ALLon*.*. (Cipi creates a dedicated database and user per app.) - Encrypt infrastructure config at rest. Cipi stores its server and app configuration encrypted with AES-256 on your VPS — infrastructure metadata never leaves the machine.
- Scope API tokens (Sanctum abilities) and rotate credentials when people leave.
- Anonymize production data before sharing. Never hand production dumps to staging or external devs raw; the Cipi Agent Laravel package includes a database anonymizer for exactly this workflow.
- Encrypted, off-site backups — and test the restore path (see the deployment guide for the backup setup).
Detection, monitoring & response
- Centralize exceptions. A spike in 500s is often the first sign of probing. Boogle gives you a self-hosted exception tracker, so error payloads (which often contain user data) stay on your infrastructure.
- Health checks + uptime alerts. Cipi 5 ships app health checks; pair them with an external uptime monitor for the outside view.
- Watch auth logs. Fail2ban reports and
lastbtell you who's knocking. - Have a response plan before you need it: isolate the box, rotate every secret
(including APP_KEY implications for encrypted data), restore from a known-good snapshot
(
cipi backup runcovers scheduled snapshots; Cipi 5 adds pre-deploy database snapshots), identify the entry point, then write the post-mortem. Never patch-and-pray on a live compromised host.
Put this into practice with Cipi
Cipi is the free, open-source deploy CLI referenced throughout this guide: one command turns a fresh Ubuntu VPS into a hardened production server for Laravel — Nginx, PHP-FPM or Octane, MariaDB or PostgreSQL, queues, scheduler, SSL and zero-downtime Git deploys included.
Frequently asked questions
Is Laravel secure by default?
The framework ships strong defaults: CSRF protection, hashed passwords, SQL injection protection through the query builder, and escaped Blade output. Most real-world incidents come from deployment mistakes — debug mode on, exposed .env files, unpatched PHP, weak SSH — which is why server hardening matters as much as application code.
What is the single most important Laravel security setting?
APP_DEBUG=false in production. An exposed debug page leaks environment variables, credentials and paths, and it is still the most common self-inflicted Laravel breach. Verify it now, then automate the check in your deploy pipeline.
How often should I update PHP and my dependencies?
Apply security patches as soon as practical — automate detection with composer audit in CI and Renovate or Dependabot for update PRs. For PHP itself, use a managed mechanism rather than memory: Cipi, for example, checks weekly and applies PHP security patches via cipi php upgrade.
Do I need a WAF for a Laravel app?
A WAF is a useful extra layer, not a substitute for the basics. Close the fundamentals first: TLS, patched software, hardened SSH, validated input, rate limiting. After that, a CDN-level WAF adds defense in depth against automated attacks.
What should I do first if I suspect a compromised server?
Isolate the machine from traffic, rotate every secret it held (database passwords, API tokens, APP_KEY), restore the application onto a clean server from a known-good backup, and only then investigate the entry point. Patching the live compromised host and hoping is how attackers keep their foothold.