If you’re running an AI-built Linux website, a 500 error is usually less mysterious than it looks. It means something in the request path broke, but the browser only sees “Internal Server Error.” The trick is narrowing down where it broke: nginx, your app server, your framework, a missing environment variable, or the file system.
This guide is for people using tools like Claude Code to build and maintain real sites on Linux. I’ll show you a repeatable way to debug 500 errors on an AI-built Linux site without guessing, restarting random services, or rewriting half the app because the symptom looked dramatic.
What a 500 error usually means
A 500 is a server-side failure. The request made it to your stack, but something downstream failed before a valid response was sent back.
On a typical Linux-hosted app, the failure is often in one of these places:
- nginx can’t reach upstream gunicorn/uWSGI
- gunicorn is running, but the app crashed on import
- framework code throws an exception during request handling
- permissions prevent reading templates, media, or sockets
- environment variables are missing or wrong
- database or cache is unavailable
The fastest way to debug 500 errors on an AI-built Linux site is to check the error at each layer, starting with the layer closest to the browser.
Start with the logs, not the code
When something breaks, the logs usually tell you more than the source tree does. If you’re using a setup like Vibesies, where each site runs in its own sandboxed Linux environment, you can inspect the container directly and keep the investigation focused on that site instead of the whole server.
1. Check the web server error log
If nginx is in front of your app, start there. Common signs include:
connect() failed (111: Connection refused)upstream prematurely closed connectionpermission deniedno live upstreams
Those messages tell you whether nginx could talk to the app server at all. If it couldn’t, the problem is probably not in your view logic yet.
2. Check the app server logs
Gunicorn, uWSGI, or your framework’s server process often logs the real exception. Look for:
- Python tracebacks
- module import failures
- syntax errors
- missing packages
- binding issues on the socket or port
If the app server isn’t even staying up, you may see a crash loop instead of a request-time error.
3. Check the application error output
Some frameworks write request exceptions to their own log channel. Django, Flask, Rails, and Node apps can all surface the actual failure here, even when nginx only shows a generic 500.
A good habit is to always look for the first error in the chain, not the last one. A request can generate several secondary failures after the real root cause.
A practical workflow to debug 500 errors on an AI-built Linux site
Here’s a sequence that saves time and keeps you from chasing ghosts.
Step 1: Reproduce the error with one request
Hit the same URL in your browser or with curl. If possible, reproduce it in the simplest way possible, with no auth, no cache, and no extra query params.
For example:
curl -i https://yourdomain.com/problem-pageIf the response is consistently 500, you have a stable target. If it only happens sometimes, you may be dealing with a race condition, timeout, or flaky dependency.
Step 2: Compare the failing route with a working one
Find a route that works and compare it to the failing route. Ask:
- Does the failing route hit a different view or controller?
- Does it use a different template or serializer?
- Does it query the database differently?
- Does it load files from disk?
- Does it require a background service or API?
This comparison often reveals the trigger faster than staring at the traceback alone.
Step 3: Read the traceback from top to bottom
The important part is usually the final exception line, but the call stack above it shows the path that got there. Pay attention to the first point where your code appears in the stack.
Common examples:
KeyErrororAttributeErrorfrom bad assumptions about dataOperationalErrorfrom the databaseTemplateDoesNotExistor missing partialsImportErrorfrom a dependency problemFileNotFoundErrorfrom a bad path or missing asset
Step 4: Check config and environment first
AI-generated code often assumes environment variables exist because the model “expects” them to. Real Linux apps are less forgiving.
Look for missing values like:
- database URL
- secret key
- API tokens
- storage bucket names
- debug flags
- allowed hosts / callback URLs
A typo in one env var can produce a 500 that looks like a framework bug but is really just bad config.
Step 5: Verify permissions and file ownership
Permission problems are easy to miss because the app may start fine and fail only when a request touches a protected path.
Check for:
- log files the process can’t write to
- upload directories owned by the wrong user
- media or static directories with restrictive mode bits
- mismatched Unix socket permissions between nginx and gunicorn
On Linux, “works in my shell” does not mean “works as the service user.”
High-frequency causes of 500 errors on AI-built Linux sites
Some issues show up again and again when people build with Claude Code, Codex, or similar tools. These are worth checking early.
Missing dependency after a deploy
The code imports a package that isn’t installed in the environment. This often happens when a generated feature adds a library but the deployment step didn’t update the lockfile or install step.
Fix: confirm the package is in your dependency manifest and reinstall cleanly.
Template or asset path mismatch
An AI assistant may rename a file, move a template, or reference a path that doesn’t exist in production.
Fix: compare the filesystem to the code’s expected path. Watch for case sensitivity issues on Linux.
Database schema drift
Your app expects a new column or table, but migrations never ran, or ran against the wrong database.
Fix: inspect migrations and verify the schema matches the deployed code.
Bad secret or API response
If the site depends on an external API, a 500 can come from malformed JSON, expired credentials, or a provider outage that your code doesn’t handle gracefully.
Fix: log external API status and make failures explicit instead of letting them bubble up as generic server errors.
Production-only settings
Many 500s appear only in production because debug mode hides them in development. You may also have different allowed hosts, CORS settings, or cache behavior.
Fix: mirror production env vars locally or in staging as closely as possible.
Use a simple triage checklist
If you want a fast pass before diving deeper, use this checklist.
- Does nginx return 500 or is the upstream unreachable?
- Is the app server process running?
- Is there a traceback in the app logs?
- Did any env vars change?
- Were dependencies updated recently?
- Did migrations run successfully?
- Are file paths and permissions correct?
- Does the issue affect one route or the whole site?
If you can answer those eight questions, you’ll eliminate most of the common causes of 500 errors on an AI-built Linux site.
How to work with Claude Code without making the problem worse
One advantage of using an AI engineer is speed, but speed is only useful if you keep the debugging loop tight. Give the agent the exact error, the relevant log excerpt, and the route that fails. Don’t ask it to “fix the site” from a screenshot.
A better prompt looks like this:
“This route returns 500. Here’s the nginx error line, the app traceback, and the recent deploy diff. Diagnose the root cause and propose the smallest safe fix.”
That framing helps the model focus on evidence instead of broad refactors. It also makes rollback easier if the first fix doesn’t hold.
If you’re on a platform like Vibesies, where each site gets its own sandboxed Linux environment and persistent storage, you can let the agent inspect the real runtime state instead of guessing from a build artifact alone.
When to roll back instead of patching
Sometimes the cleanest answer is to revert the last change and regroup. Roll back if:
- the 500 started immediately after deploy
- the traceback points to a new code path you haven’t tested
- the app is failing before it can serve any requests
- you don’t yet know whether the issue is in code, config, or infra
Rollback buys you time. Once the site is stable again, you can test the fix in a controlled way instead of under pressure.
What “good” looks like after the fix
Don’t stop at “the page loads.” A proper resolution usually includes:
- a verified root cause
- a log entry that explains future failures more clearly
- a test or check that would catch the same bug again
- a note on what changed in config, dependencies, or permissions
If you’ve ever had to debug the same 500 twice, you know the second fix is usually the one that adds the missing guardrail.
Conclusion: keep the debugging loop short
The best way to debug 500 errors on an AI-built Linux site is to move from symptom to layer to cause, in that order. Start with logs, verify the request path, check config and permissions, then inspect the code only after the runtime evidence tells you where to look.
That workflow works whether you’re maintaining a small app, a content site, or a larger production project. And if you’re using a host like Vibesies to run Claude Code inside a real Linux environment, you can keep that loop tight: inspect the logs, reproduce the error, fix the root cause, and validate the result in the same container where the problem happened.
When a 500 shows up, don’t treat it like a mystery. Treat it like a breadcrumb trail.