Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
FREEDOM251

Freedom251 › Blog › 500 errors

Django, Gunicorn and Nginx: Fixing 500 and 502 Errors After a Restart

By Freedom251 Team · · 11 min read

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

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.

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.

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

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:

  1. Inspect the current-boot status and journal for the Gunicorn and Nginx units.
  2. Run Django checks and a local Gunicorn request without involving the public URL.
  3. Confirm that the Nginx upstream endpoint exactly matches Gunicorn’s bind or systemd socket.
  4. Test the endpoint as the actual Nginx account.
  5. 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:

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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:

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.

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

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
sudo -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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
[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.

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

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:

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
sudo 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 gunicorn and systemctl status nginx
  • journalctl -u gunicorn -b and journalctl -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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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

  1. Capture evidence. Save both service statuses, current-boot journals, Nginx error and access entries, and Gunicorn output before changing files.
  2. Test Nginx syntax. Run sudo nginx -t; reload only when it passes.
  3. Verify the Gunicorn unit. Check its executable, working directory, WSGI module, user, environment, and bind address.
  4. Verify the listener. Confirm the expected Unix socket or TCP port exists and accepts a local request.
  5. Test as Nginx. Use the actual Nginx account to connect to the Unix socket or localhost endpoint.
  6. Align endpoints. Make Nginx’s proxy_pass identical to Gunicorn’s --bind or the socket unit’s ListenStream.
  7. Repair permissions durably. Set socket owner, group, mode, and parent-directory traversal in the unit and filesystem layout.
  8. Validate Django. Check ALLOWED_HOSTS, settings and secrets, database access, migration state, and deployment checks.
  9. Handle static files separately. Confirm STATIC_ROOT, run collectstatic, and verify Nginx’s static location.
  10. 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.
  11. 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.

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

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_pass with --bind or ListenStream, 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.

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.

500 errors502 errorsDjangoGunicornLinux server troubleshootingNGINXsystemd

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.