Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

A Django site that returns a 500 or 502 immediately after a reboot is usually failing at one layer of the deployment chain: systemd did not start Gunicorn, Gunicorn is listening somewhere Nginx is not checking, Nginx cannot access the socket, or Django loaded incomplete production settings. Isolate each layer instead of changing permissions or enabling debug blindly.

Start with systemctl status and the current-boot journal, test Django/Gunicorn locally, verify the exact upstream endpoint and socket permissions, then inspect Nginx and Django logs together. The procedure below is designed to leave a reboot-safe configuration rather than a temporary manual fix.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What a 500 or 502 tells you

HTTP status codes identify where to look first. A 500 Internal Server Error normally means the request reached Django or a Gunicorn worker and application code, configuration, a database connection, or an import failed. A 502 Bad Gateway normally means Nginx could not obtain a valid response from its configured upstream. The upstream may be stopped, listening on a different address, blocked by socket permissions, or timing out.

These boundaries are not absolute. Django can reject a host name after Nginx successfully connects, and Nginx can return 502 while Gunicorn logs only worker-start messages. Gunicorn’s documentation specifically warns that reverse proxies may show 502 responses while Gunicorn records only new worker startups. Correlate timestamps in the systemd journal, Nginx error log, Nginx access log, and Gunicorn output before deciding which layer is broken.

First checks after the reboot

  1. Record the state of both services without following a stale terminal session: sudo systemctl status gunicorn --no-pager and sudo systemctl status nginx --no-pager.
  2. Read only entries from the current boot. Replace gunicorn with your unit name if it is project-specific: sudo journalctl -u gunicorn -b --no-pager and sudo journalctl -u nginx -b --no-pager. Add -n 100 for a short first pass or -f while reproducing the request.
  3. Check whether the units are enabled for future boots: systemctl is-enabled gunicorn and systemctl is-enabled nginx. A service started manually can appear healthy until the next restart if it was never enabled.
  4. Test the application without the public URL. Use a local Gunicorn HTTP listener, the configured Unix socket, or a direct Django development-style check with production settings. This separates application failures from proxy failures.

Do not reload Nginx or restart repeatedly before capturing these outputs. A restart loop can erase the first, most useful error from view.

Layer 1: prove that systemd starts the intended Gunicorn process

Inspect the complete unit, not just its status summary

Display the effective unit and any drop-in files with sudo systemctl cat gunicorn. Confirm the following values match the files that exist on disk:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • WorkingDirectory points to the project directory containing the Django package.
  • ExecStart uses the intended virtual-environment executable, such as /srv/example/venv/bin/gunicorn, rather than a shell’s unrelated global binary.
  • The WSGI target has the correct module name, for example project.wsgi:application.
  • User, Group, and any Environment or EnvironmentFile entries are present and readable.
  • The bind address or socket in ExecStart is the same endpoint referenced by Nginx.

A post-restart failure commonly comes from a moved virtual environment, an invalid working directory, an omitted environment variable, a Python import error, or a unit that was never enabled. The journal usually states which category applies: “No such file or directory” points to a path, “ModuleNotFoundError” to the application environment, and an immediate exit with repeated restarts to a process that cannot complete startup.

Reproduce startup as the service account

Run the exact executable and module as the account named by the unit, not as root. For example:

sudo -u appuser /srv/example/venv/bin/gunicorn --chdir /srv/example project.wsgi:application --bind 127.0.0.1:8000

Use a temporary foreground run only for diagnosis, then stop it before starting the systemd unit. If this command fails, fix Python imports, environment variables, migrations, or database connectivity before touching Nginx. If it succeeds manually but systemd fails, compare the unit’s working directory, PATH, settings module, and environment file with the shell in which the manual command succeeded. Systemd does not inherit your interactive shell profile.

Layer 2: make the Nginx upstream and Gunicorn listener identical

Compare the endpoint character for character

Read the Nginx server block and the Gunicorn unit side by side. The proxy_pass value must use the same scheme, host, port, and socket path as Gunicorn’s --bind option or a socket unit’s ListenStream. A process can be running while Nginx still fails if one side uses /run/gunicorn.sock and the other creates /srv/example/gunicorn.sock.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a TCP listener, check the actual socket table with sudo ss -ltnp | grep 8000 and test it locally with curl -i http://127.0.0.1:8000/. For a Unix socket, check its existence and metadata:

sudo stat /run/gunicorn.sock
sudo namei -l /run/gunicorn.sock

namei reveals permissions on every parent directory. A socket can have the right owner and mode while a parent directory denies Nginx traversal, producing “permission denied.”

Test the socket as Nginx, not as root

Distribution defaults differ: the web-server account may be www-data, nginx, or another local user. Confirm it in the Nginx service or configuration, then test with that identity:

sudo -u www-data curl --unix-socket /run/gunicorn.sock http://localhost/ -i

Replace www-data with the actual account. A successful response here proves that the socket is reachable and Gunicorn can answer; an access error identifies ownership, group, mode, or directory traversal as the fault.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use deliberate socket ownership and mode

Do not run the whole application as the web-server identity merely to make a socket readable. Keep a dedicated application account where practical, assign the socket group to the Nginx group, and set a mode that grants only the required access. In a systemd socket unit, the documented controls are ListenStream=/run/gunicorn.sock, SocketUser, SocketGroup, and SocketMode. The paired service can require that socket with Requires=gunicorn.socket.

[Socket]
ListenStream=/run/gunicorn.sock
SocketUser=appuser
SocketGroup=www-data
SocketMode=0660

[Install]
WantedBy=sockets.target

Use the group name that exists on your host. After changing a unit, run sudo systemctl daemon-reload, then restart the socket and service in the appropriate order. Enable socket activation for future boots with sudo systemctl enable --now gunicorn.socket.

Layer 3: read Nginx’s own evidence

Validate configuration before reloading

Run sudo nginx -t. Fix syntax, include-file, certificate, or upstream-name errors before issuing systemctl reload nginx. A reload applies a valid configuration without unnecessarily dropping existing connections; a failed test should not be followed by a reload.

Find the effective log paths

Nginx error logs contain upstream connection failures, permission errors, timeouts, and configuration problems. Access logs show the status returned to clients and help match a failing request to an exact timestamp. If the distribution path is unclear, inspect the compiled defaults with:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
nginx -V 2>&1 | grep -- --error-log-path

Also inspect the active configuration for per-server error_log and access_log directives. A message such as “connect() to unix:/run/gunicorn.sock failed (2: No such file or directory)” means the path is wrong or the socket was not created. “(13: Permission denied)” means the path exists but Nginx cannot access it. Upstream timeout messages require checking worker health, database calls, and proxy timeout settings after connectivity is established.

Layer 4: validate Django’s production settings

Set a valid ALLOWED_HOSTS value

With DEBUG = False, Django requires a suitable ALLOWED_HOSTS value. Add every real host name used by the request, including the new domain or an IP address used for testing. Django validates the request’s Host header; an unmatched value raises SuspiciousOperation. From behind Nginx, that rejection can look like a proxy problem even though the upstream connection works.

ALLOWED_HOSTS = ["example.com", "www.example.com"]

Do not set DEBUG = True on a public production site as a diagnostic shortcut. It can expose sensitive settings and tracebacks. Instead, run the deployment checks with the same settings module used by systemd:

sudo -u appuser /srv/example/venv/bin/python /srv/example/manage.py check --deploy --settings=project.settings.production

Confirm the environment seen by systemd

Verify the secret key, database URL, allowed hosts, settings module, and any storage credentials are available to the service account. A shell export used during a manual test is not automatically available to systemd. Check the unit’s EnvironmentFile path, ownership, and permissions, and compare its values with the environment used in the successful local test.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Then check database reachability, pending migrations, and application imports. A Gunicorn worker that starts but dies on its first request can leave the proxy reporting 502 or 500 while the initial startup lines look normal. Review Django’s configured logging and confirm that messages appear after a real request.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Keep static and media failures separate

Missing CSS, JavaScript, or images is not evidence that Gunicorn is down. In production, set STATIC_ROOT and run:

sudo -u appuser /srv/example/venv/bin/python /srv/example/manage.py collectstatic --noinput

Configure Nginx to serve the collected directory directly, while dynamic URLs continue to proxy to Gunicorn. Uploaded media needs its own location and permissions. Treat media as untrusted input; do not configure the web server to execute files uploaded by users.

End-to-end recovery runbook

  1. Save systemctl status output, current-boot journals for Gunicorn and Nginx, Nginx error and access entries, and Gunicorn output before making changes.
  2. Run nginx -t; reload only after it passes.
  3. Inspect the Gunicorn unit’s executable, working directory, WSGI module, settings environment, user, and bind address.
  4. Run the same Gunicorn command as the service account, or test the configured socket/TCP listener locally.
  5. Compare Nginx proxy_pass with Gunicorn --bind or socket ListenStream exactly.
  6. Check the socket, every parent directory, group membership, and mode as the real Nginx account.
  7. Run Django’s deployment checks, then verify host validation, secret key, database connectivity, migrations, and static collection.
  8. Restart or reload in dependency order, test locally, and finally test through Nginx with the intended host name.
  9. Enable the socket or service and Nginx at boot, and document paths, unit names, service accounts, and environment files so the next reboot is reproducible.

Choose a repair that survives the next reboot

Decision Advantages Risks and checks
systemd-managed service Starts at boot, records failures in the journal, and preserves the exact executable, directory, user, and environment. Unit paths and environment files must remain valid; reload the daemon after edits.
Manual Gunicorn command Useful for isolating an application or import error immediately. Not persistent across reboot and easy to run with different settings than production.
Unix socket Local proxy communication with explicit user, group, mode, and no exposed TCP port. Every path component and socket permission must allow Nginx access; stale or mismatched paths cause 502.
Localhost TCP Simple to inspect with ss and curl, and avoids Unix-socket traversal rules. Bind only to the intended local address and keep Nginx’s port identical to Gunicorn’s.

The durable fix is the one that preserves endpoint identity, privilege separation, observability, and boot ordering. Temporary broad chmod changes, running everything as root, and enabling Django debug mode may hide symptoms while creating a larger security or maintenance problem.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How to interpret common symptoms

Symptom Most likely layer Next action
Nginx says “No such file or directory” for the socket Endpoint or startup ordering Compare the exact socket path, check whether the socket unit created it, and inspect the current-boot journal.
Nginx says “Permission denied” Socket ownership or directory traversal Use stat and namei -l, then test with the actual Nginx account.
Gunicorn exits before workers are ready systemd unit or Python environment Run the exact command as the service user and correct paths, imports, or environment variables.
Gunicorn starts, but Nginx returns 502 Proxy endpoint or worker response Test the socket/TCP listener directly, then correlate Nginx error timestamps with Gunicorn worker logs.
Direct upstream works, public request fails with a host error Django settings Add the requested host to ALLOWED_HOSTS and retest with production settings.
Pages load but styling or uploads fail Static/media configuration Verify STATIC_ROOT, run collectstatic, and check separate media locations and permissions.

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.