A Django site that returns HTTP 500 or 502 immediately after a reboot is usually failing at one layer of the deployment stack, not everywhere at once. Check systemd, Gunicorn, the socket or TCP listener, Nginx, and Django separately, in that order. A manual Gunicorn command can work while the systemd service fails because it uses a different user, working directory, virtual environment, settings module, or environment.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
What a post-restart 500 or 502 usually means
The status code tells you where to start, but not necessarily where the original fault is. A 500 Internal Server Error generally means the request reached Django or an application component and that code raised an exception. A 502 Bad Gateway means Nginx, acting as the reverse proxy, could not obtain a valid response from its configured upstream. The upstream may be stopped, listening somewhere else, inaccessible because of permissions, or terminating before it answers.
As an Amazon Associate I earn from qualifying purchases.
Do not assume that a running Gunicorn process proves the deployment is healthy. Nginx may be configured for /run/gunicorn.sock while Gunicorn binds to a socket inside the project directory, or Nginx may be unable to traverse a parent directory. Conversely, Django may be rejecting the request even though the socket connection is fine.
Use this isolation sequence:
- Inspect the current-boot status and journal for the Gunicorn and Nginx units.
- Run Django checks and a local Gunicorn request without involving the public URL.
- Confirm that the Nginx upstream endpoint exactly matches Gunicorn’s bind or systemd socket.
- Test the endpoint as the actual Nginx account.
- Only then troubleshoot Django host validation, database access, migrations, or static files.
Start with systemd and the current boot’s journal
Check both services
Run the status commands first; they show whether a unit is active, failed, restarting, or never started:
#1 Best Overall
sudo systemctl status gunicorn
sudo systemctl status nginx
sudo journalctl -u gunicorn -b
sudo journalctl -u nginx -b
The -b option limits the journal to the current boot, which prevents an old failure from being mistaken for the cause of the present outage. Read the complete output, including the command systemd attempted to execute and the final exit status. A failure such as “failed at step CHDIR” points to an invalid WorkingDirectory; “No such file or directory” may indicate a missing virtual-environment executable, settings file, WSGI module, or socket path.
Inspect the unit systemd is actually using
Distribution packages and local overrides can produce a unit different from the file you edited. Display the loaded definition and any drop-ins:
Free tools Windows power users keep installed
One-click scans. No signup required.
sudo systemctl cat gunicorn
sudo systemctl show gunicorn -p User -p Group -p WorkingDirectory -p ExecStart -p Environment -p EnvironmentFile
sudo systemctl show nginx -p User -p Group
Compare ExecStart, WorkingDirectory, the service account, the virtual-environment path, the WSGI module, and every environment file with the command that worked manually. A shell session often has a different PATH, current directory, Python installation, and exported settings variables than a systemd service.
Check whether the unit is enabled for future reboots
A service started manually can disappear at the next reboot. Once the configuration is correct, enable the appropriate units:
sudo systemctl enable gunicorn
sudo systemctl enable nginx
If you use socket activation, enable the socket rather than relying on a manually launched Gunicorn process. Do not treat enabling a broken unit as a repair; first make a clean start succeed and verify the listener.
Make the manual test match the service environment
Verify the executable and WSGI import
Run checks as the same account and from the same directory declared by the unit. Substitute the real application user, project directory, virtual-environment path, and settings module:
Recommended Free Tools
sudo -u appuser -H sh -c 'cd /srv/example && /srv/example/venv/bin/python manage.py check --deploy'
sudo -u appuser -H sh -c 'cd /srv/example && /srv/example/venv/bin/gunicorn --check-config example.wsgi:application'
The first command applies Django’s deployment checks using the environment available to that shell. The second catches a bad Gunicorn configuration or WSGI import before Nginx is involved. If either command works only as your login user, the service is not using equivalent permissions or environment.
Run Gunicorn on a temporary local endpoint
For a controlled test, bind Gunicorn to an unused localhost port, then request it directly:
Rank #2
cd /srv/example
/srv/example/venv/bin/gunicorn --bind 127.0.0.1:8001 example.wsgi:application
curl -i -H 'Host: your-real-hostname.example' http://127.0.0.1:8001/
Stop the foreground process with Ctrl+C after the test. A valid HTTP response here proves that Django can import and serve the application in that environment. A traceback, missing module, database error, or settings error belongs to the application layer, not Nginx.
If the direct request returns a Django response but the public request is 502, focus on the upstream endpoint, socket permissions, Nginx configuration, and service ordering. Gunicorn’s documentation notes that a reverse proxy can show 502 responses while Gunicorn logs only new worker-start messages, so the absence of an obvious Gunicorn traceback does not clear Nginx.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Match Nginx’s upstream to Gunicorn’s listener exactly
Compare the three endpoint declarations
There must be one identical endpoint in all relevant places:
| Layer | What to inspect | What must match |
|---|---|---|
| Nginx | proxy_pass in the active server configuration |
Unix-socket filename or TCP address and port |
| Gunicorn command | --bind |
The same socket path or host and port |
| systemd socket unit | ListenStream |
The path consumed by Nginx |
For example, if Nginx proxies to unix:/run/gunicorn.sock but Gunicorn creates /srv/example/gunicorn.sock, Nginx reports that the upstream does not exist even though a Gunicorn process is present. Correct the declarations to one path; do not create a second socket as a workaround.
Check whether the endpoint exists and is listening
sudo ss -ltnp
sudo ss -lx
sudo stat /run/gunicorn.sock
The TCP listing should show the expected localhost address and port. The Unix-socket listing should show the expected socket name. If a stale socket remains after a crash, remove it only after confirming no active service is using it, then restart the owning unit. A socket file by itself is not proof that a process is accepting connections.
Test the Unix socket as Nginx
Use the account named in the local Nginx configuration; common distributions use different names. A direct request through the socket distinguishes application failure from proxy access failure:
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallsudo -u nginx curl --unix-socket /run/gunicorn.sock -i http://localhost/
Replace nginx with the configured Nginx user, such as www-data, when appropriate. If the request succeeds as the Nginx account, the socket and application are reachable and the remaining problem is likely Nginx routing, headers, or server selection. “Permission denied” identifies an access problem; “No such file or directory” identifies a path, startup-order, or endpoint mismatch.
Fix Unix-socket ownership and directory traversal
Inspect every permission boundary
Check the socket and each parent directory, not just the final file:
stat /run/gunicorn.sock
namei -l /run/gunicorn.sock
ls -ld /run /run/gunicorn.sock
Nginx needs permission to connect to the socket and execute (traverse) every parent directory in its path. A socket with mode 660 can still fail if the Nginx account is not in the socket’s group, or if a parent directory denies traversal.
Use deliberate service and socket settings
Keep the application service account separate from the web-server account where practical. Configure the socket unit with an explicit owner, group, and mode rather than running Django as the Nginx identity. A documented socket-activation pattern uses:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
[Socket]
ListenStream=/run/gunicorn.sock
SocketUser=appuser
SocketGroup=nginx
SocketMode=0660
The matching service declares a dependency on the socket:
[Unit]
Requires=gunicorn.socket
The exact unit also needs the correct application user, working directory, executable, environment, and WSGI target for your project. After editing a unit, reload systemd’s definitions and restart in dependency order:
sudo systemctl daemon-reload
sudo systemctl restart gunicorn.socket
sudo systemctl restart gunicorn
sudo systemctl reload nginx
Use the actual Nginx group from your installation. Do not solve an outage with a broad, permanent chmod 777, by making the whole project world-readable, or by changing the application to run as the web-server identity. Those changes hide the ownership design problem and expand the impact of a compromise.
Enable socket activation at boot
sudo systemctl enable --now gunicorn.socket
Confirm that the socket is active and that the service starts when a request arrives, if that is how your unit is designed. Verify the same arrangement after a controlled reboot rather than assuming a successful restart proves boot ordering is correct.
Read Nginx, Gunicorn, and systemd logs together
Nginx error and access logs
The Nginx error log records connection failures, permission errors, upstream timeouts, and configuration problems. The access log records what clients received, including the status code and request path. Find the compiled default error-log location with:
nginx -V 2>&1
Then inspect the path configured by your active server or global Nginx configuration. Run:
Rank #4
sudo nginx -t
before any reload. A syntax test that fails means the old configuration remains in effect; do not reload until it passes.
Gunicorn output
Gunicorn may write errors to a configured file or to standard error, which systemd normally captures in the journal. If a file is configured, its directory and file must be writable by the Gunicorn worker process. Check the effective setting in the unit and Gunicorn configuration, then inspect the matching output:
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorssudo journalctl -u gunicorn -b -n 200 --no-pager
Worker-start messages without request errors do not rule out an Nginx-to-upstream failure. Correlate timestamps with the Nginx error log and the client request.
Build a timestamped incident picture
Capture these streams before repeatedly restarting:
systemctl status gunicornandsystemctl status nginxjournalctl -u gunicorn -bandjournalctl -u nginx -b- Nginx error and access entries for the failed request
- Gunicorn’s journal or configured error output
One log answers whether systemd launched the process, another answers whether Nginx connected, and another answers what Django did with the request. Treating a single log as authoritative is a common reason these incidents take longer to resolve.
Validate Django’s production settings
Set ALLOWED_HOSTS for the real request host
With DEBUG = False, Django requires a suitable ALLOWED_HOSTS value. Add every hostname that clients actually use, including the canonical domain and any deliberately supported alternate name. A request whose Host header is not matched raises Django’s host-validation exception and can look like a proxy failure when a site has just moved to a new domain or IP address.
ALLOWED_HOSTS = ["your-real-hostname.example", "www.your-real-hostname.example"]
Do not add a wildcard merely to conceal an unknown host. Confirm which settings module systemd loads and whether the environment variable that selects it is present in the unit. A manual shell may be loading a development settings file while the service loads production settings.
Run deployment checks without enabling debug
sudo -u appuser -H sh -c 'cd /srv/example && /srv/example/venv/bin/python manage.py check --deploy'
Check the production secret key, database variables, allowed hosts, secure settings appropriate to your deployment, and logging configuration. Never enable DEBUG on a public production site as a diagnostic shortcut; it can expose settings, tracebacks, and sensitive request data.
Best Value
Check database connectivity and migrations
A worker can start successfully and still return 500 when a view first queries an unavailable database or expects a migration that has not been applied. Verify database credentials and network access using the production environment, then inspect migration state:
sudo -u appuser -H sh -c 'cd /srv/example && /srv/example/venv/bin/python manage.py showmigrations'
sudo -u appuser -H sh -c 'cd /srv/example && /srv/example/venv/bin/python manage.py migrate --plan'
Apply migrations according to your change-control process; do not run an unreviewed migration automatically during an outage. If the database service also starts at boot, confirm its availability and ordering before declaring Gunicorn faulty.
Separate static and media problems from dynamic failures
Static files require their own production path
Missing CSS, JavaScript, or images is not evidence that Gunicorn’s upstream is broken. In production, configure STATIC_ROOT and collect the files:
sudo -u appuser -H sh -c 'cd /srv/example && /srv/example/venv/bin/python manage.py collectstatic'
Nginx should serve the collected directory directly, while dynamic application URLs are proxied to Gunicorn. Check the Nginx location blocks, filesystem ownership, and the browser’s requested URL. A page that returns 200 but has missing assets has a different repair path from a page that returns 502 before Django receives the request.
Protect uploaded media
Django’s deployment guidance treats uploaded media as untrusted. Serve it from a controlled location and ensure the web server cannot execute uploaded content. Do not place user uploads inside a directory where executable application code is interpreted, and do not use a static-file fix to mask a dynamic request failure.
Use this recovery sequence safely
- Capture evidence. Save both service statuses, current-boot journals, Nginx error and access entries, and Gunicorn output before changing files.
- Test Nginx syntax. Run
sudo nginx -t; reload only when it passes. - Verify the Gunicorn unit. Check its executable, working directory, WSGI module, user, environment, and bind address.
- Verify the listener. Confirm the expected Unix socket or TCP port exists and accepts a local request.
- Test as Nginx. Use the actual Nginx account to connect to the Unix socket or localhost endpoint.
- Align endpoints. Make Nginx’s
proxy_passidentical to Gunicorn’s--bindor the socket unit’sListenStream. - Repair permissions durably. Set socket owner, group, mode, and parent-directory traversal in the unit and filesystem layout.
- Validate Django. Check
ALLOWED_HOSTS, settings and secrets, database access, migration state, and deployment checks. - Handle static files separately. Confirm
STATIC_ROOT, runcollectstatic, and verify Nginx’s static location. - Restart in order and retest. Start the socket or Gunicorn service, reload Nginx after a passing syntax test, test locally, then test through Nginx and the public hostname.
- Make the repair reboot-safe. Enable the required units and document paths, service accounts, groups, environment files, and the endpoint contract.
Choosing a durable repair instead of a temporary workaround
| Decision | Durable choice | Risky temporary choice |
|---|---|---|
| Process startup | Systemd-managed service or socket enabled at boot | Starting Gunicorn manually in a shell |
| Upstream endpoint | One documented Unix socket or localhost TCP listener | Changing Nginx until one guessed path happens to work |
| Privilege separation | Dedicated app user and controlled socket group/mode | Running the app as Nginx or using world-writable permissions |
| Observability | Journal plus Nginx access and error logs and Gunicorn output | Looking only at the browser’s 500/502 page |
| Django diagnostics | Production checks with correct environment and hostnames | Enabling DEBUG on the public site |
The objective is not merely to make the next request succeed. It is to ensure the same executable, environment, endpoint, permissions, and logging work after the next reboot. Record the final values in the service and deployment documentation so another administrator can reproduce the startup path without relying on an interactive shell.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →How to identify the failing layer quickly
- Gunicorn unit is failed or in a restart loop: inspect
ExecStart,WorkingDirectory, virtual-environment path, import target, environment files, and service-user permissions. - Gunicorn works on localhost but Nginx returns 502: compare
proxy_passwith--bindorListenStream, then test the endpoint as Nginx. - Nginx says “permission denied”: inspect socket owner, group, mode, and traversal permissions on every parent directory.
- Nginx says the upstream file does not exist: check startup order, stale or wrong socket paths, and whether systemd created the socket you expect.
- The direct Gunicorn request returns a Django error: inspect
ALLOWED_HOSTS, settings selection, secrets, database connectivity, migrations, and the application traceback. - The HTML loads but assets fail: troubleshoot
STATIC_ROOT,collectstatic, Nginx static locations, and media permissions independently.
Once each layer passes its own test, send a request through Nginx using the real hostname and inspect both the access and error logs. That final check confirms that DNS, server selection, host validation, proxying, and Django are using the same names and paths.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

