HTTP Error Triage
Use this guide when staging or production returns a 4xx/5xx you do not immediately recognize. The goal is to classify the failure by status code + first log fragment + layer, then fix the right thing instead of guessing.
What you’ll learn
Section titled “What you’ll learn”You will learn the first-response path for common deployed Laravel failures: 500 app crashes, 419 CSRF/session failures, 404 and 403 layer problems, and quick 429 / 422 paths. When you diagnose a new pattern, add it to this living ledger so the next operator starts with the answer.
flowchart TD R["4xx / 5xx response"] --> S{Status code} S -->|500| L["Tail laravel.log"] L --> C["Classify first error fragment"] S -->|419| X["Run curl CSRF test"] X --> K["302 = server OK<br/>browser-only 419 = cookie collision"] S -->|404 / 403| W["Find the layer"] W --> A["Laravel route → .htaccess → Cloudflare WAF → symlink/perms"] S -->|429 / 422| Q["Use quick path"] Q --> E["Rate limiter or validation body"]Before you start
Section titled “Before you start”You need SSH access to the environment, the domain, and the app path. Treat server output as evidence: capture the command and the first useful log fragment in the incident note or project tracker.
500 — server error
Section titled “500 — server error”Start with the log, not the browser page. The first exception fragment usually tells you whether this is schema drift, an environment mismatch, stale autoload, or a vendor-family seeder issue.
1. Tail laravel.log
Section titled “1. Tail laravel.log”ssh <host> "tail -3 ~/domains/<domain>/deploy/current/storage/logs/laravel.log | \ grep -oE 'ERROR.*[A-Z][a-z]+: [^{]{1,200}'"# Expected: a short exception fragment you can classify belowIf the log is empty, check both release and shared storage:
ssh <host> "ls -la ~/domains/<domain>/deploy/current/storage/logs/ && \ ls -la ~/domains/<domain>/deploy/shared/storage/logs/"# Expected: log files exist in one of the two locations, or logging is routed to stderr/Sentry2. Classify the first fragment
Section titled “2. Classify the first fragment”| Error fragment | Likely cause | Where to fix |
|---|---|---|
Table '[x]' doesn't exist | Migration or seeder did not run | Post-installer DB check; verify php artisan migrate:status |
Attempt to read property "[x]" on null | Seeder order bug or expected vendor row is missing | Use the vendor-family rule reference_codecanyon_vendor_families.md and rerun prerequisite-aware seeders |
DecryptException | APP_KEY mismatch or encrypted column copied across environments | Use the production copy skip list; rotate APP_KEY only if compromise is suspected |
Connection refused | Redis/MySQL down or .env host wrong | Check Redis ping and mysql -h 127.0.0.1 -e 'SELECT 1' |
View [x] not found | Vite build missing or view cache stale | Build assets, redeploy, then run php artisan optimize:clear |
class '[X]' not found | Composer autoload stale or package excluded under --no-dev | composer dump-autoload --optimize; verify production dependencies |
Unable to resolve NULL parameter / [Y] not found | Container binding or provider registration issue | Check App\Providers\ and interface bindings |
Broken pipe / max execution time exceeded | Long first install or migration timeout | Raise .user.ini execution limits temporarily |
SQLSTATE[HY000]: General error: 1366 Incorrect string value | Charset mismatch (utf8 vs utf8mb4) | Verify DB charset matches config/database.php |
3. Escalate only after classification fails
Section titled “3. Escalate only after classification fails”ssh <host> "tail -100 ~/domains/<domain>/deploy/current/storage/logs/laravel.log"# Expected: enough stack trace to attach to the incident noteIf it is a repeatable vendor-family bug, add the pattern to Templates/Rules/reference_codecanyon_vendor_families.md.
419 — page expired
Section titled “419 — page expired”A browser-only 419 is often a cookie collision, not a Laravel server failure. Prove that with a fresh curl session before changing app code.
1. Run the curl CSRF test
Section titled “1. Run the curl CSRF test”curl -c /tmp/cookies.txt -s https://<domain>/login | \ grep -oE 'name="_token" value="[^"]+"' | sed 's/.*value="//;s/"$//' > /tmp/csrf.txt
curl -b /tmp/cookies.txt -c /tmp/cookies.txt \ -X POST https://<domain>/login \ -d "_token=$(cat /tmp/csrf.txt)&email=test@example.com&password=wrong" \ -sI | head -1# Expected: HTTP/1.1 302. Auth failed, but the server accepted the form token.If curl returns 302 and only browsers return 419, inspect duplicate session cookies in DevTools. Use Templates/Rules/feedback_session_domain_collision.md for the full cookie-collision recipe. If curl also returns 419, check session storage and driver permissions:
ssh <host> "ls -la ~/domains/<domain>/deploy/shared/storage/framework/sessions/ | tail -5"ssh <host> "grep '^SESSION_DRIVER=' ~/domains/<domain>/deploy/shared/.env"# Expected: recent session files, or a driver setting you can explain404 and 403 — find the layer
Section titled “404 and 403 — find the layer”For 404 and 403, the question is not just “is the route real?” It is “which layer answered?” Walk Laravel route registration, Apache .htaccess, Cloudflare WAF, and symlink/permissions in that order.
| Layer | Symptom | Check |
|---|---|---|
| Laravel route | Route missing, or middleware aborts | `php artisan route:list |
Apache .htaccess | Explicit forbidden route or rewrite-level 404 | grep -n 'RewriteRule.*\\[F' public/.htaccess |
| Cloudflare WAF | Origin looks open but edge returns 403 | Cloudflare → Security → WAF → matching rule |
| Symlink/perms | Asset or path 403/404, especially public/storage | ls -la public_html and target file permissions |
1. Check Laravel first
Section titled “1. Check Laravel first”ssh <host> "cd ~/domains/<domain>/deploy/current && php artisan route:list | grep -i '<route-fragment>'"# Expected: route is listed, or you have found the Laravel-side reason for a 404If the route exists but still returns 404, inspect middleware such as canInstall, auth, or vendor custom abort logic.
2. Check .htaccess, WAF, and symlinks
Section titled “2. Check .htaccess, WAF, and symlinks”ssh <host> "grep -n 'RewriteRule.*\\[F' ~/domains/<domain>/deploy/current/public/.htaccess"curl -I https://<domain>/<route> 2>&1 | head -3ssh <host> "ls -la ~/domains/<domain>/public_html"# Expected: a layer you can name: Laravel, Apache, Cloudflare, or filesystemFor /install, a 403 after setup is intentional. During installer windows, unblock both .htaccess and Cloudflare WAF, finish the wizard, then re-block and verify 403 again.
429 and 422 — quick paths
Section titled “429 and 422 — quick paths”429 means a limiter fired. 422 means validation returned a body. Do not debug either from the status code alone.
| Status | First check | Common fix |
|---|---|---|
429 | Cloudflare rate limits, then Laravel RateLimiter::for rules | Narrow the edge rule or adjust the app throttle |
422 | DevTools → Network → failing request → Response JSON | Match form field names, CSRF state, file upload limits, or FormRequest rules |
For Laravel throttles:
ssh <host> "grep -rn 'RateLimiter::for' ~/domains/<domain>/deploy/current/app/Providers/ ~/domains/<domain>/deploy/current/config/ 2>/dev/null"# Expected: the limiter names and limits that can explain the 429Living ledger
Section titled “Living ledger”When a 4xx/5xx does not fit this page and you had to diagnose it from scratch, add a row to the relevant section. The value of this guide is the accumulated mapping from fragment → cause → fix location.
Summary
Section titled “Summary”Classify before fixing: 500 starts with laravel.log, 419 starts with the curl CSRF test, 404/403 starts by naming the layer, and 429/422 starts with the limiter or validation body. The faster you name the layer, the less likely you are to rotate keys, redeploy, or change code when the real fix is a route block, cookie collision, or schema baseline mismatch.