Reference Guide Detailed deployment notes with production context and concrete examples.

Flask App Not Reloading After Deploy

If your Flask app is still serving old code after deployment, this guide shows you how to force the correct production reload path and verify that the new release is actually running. It covers Gunicorn, systemd, Nginx, static assets, and common deployment mistakes that prevent updated code from appearing.

Quick Fix / Quick Setup

Run the standard restart and verification path first:

bash
sudo systemctl daemon-reload
sudo systemctl restart yourapp
sudo systemctl status yourapp --no-pager
sudo nginx -t && sudo systemctl reload nginx
ps aux | grep gunicorn
readlink -f /proc/$(pgrep -n -f 'gunicorn.*yourapp')/cwd

Replace yourapp with your actual systemd service name.

This fixes the most common case: code was deployed, but Gunicorn or Nginx is still serving the previous release or the service is pointing at the wrong directory.

What’s Happening

In production, Flask code changes do not apply automatically unless the serving process is restarted or reloaded correctly.

Typical failure points:

  • Gunicorn workers are still running code already loaded in memory
  • systemd is starting Gunicorn from an old release path or virtualenv
  • Nginx is proxying to the wrong socket or serving stale static files
  • a containerized deployment is still running the previous image or mount

Step-by-Step Guide

1. Confirm the new code exists on disk

Check that the latest release is actually present in the path you expect:

bash
pwd
ls -la
git rev-parse --short HEAD

If you use release directories:

bash
ls -la /var/www/yourapp
readlink -f /var/www/yourapp/current

Verify that the current release contains the expected code changes.

2. Verify which process is serving traffic

Find the active Gunicorn process:

bash
ps aux | grep gunicorn

Identify the Gunicorn master and worker processes that are serving the live app.

3. Check the running Gunicorn working directory

Inspect the current working directory of the running Gunicorn process:

bash
readlink -f /proc/$(pgrep -n -f 'gunicorn.*yourapp')/cwd
ls -l /proc/$(pgrep -n -f 'gunicorn.*yourapp')/exe

If this points to an old release, old virtualenv, or unexpected path, the service definition is wrong or the app was not restarted correctly.

4. Restart the Flask app service

Restart the systemd service that runs Gunicorn:

bash
sudo systemctl restart yourapp
sudo systemctl status yourapp --no-pager

If the restart fails, check Flask Gunicorn Service Failed to Start.

5. Reload systemd if the unit file changed

If you changed the unit file, drop-ins, or environment file references:

bash
sudo systemctl daemon-reload
sudo systemctl restart yourapp

Without daemon-reload, systemd may continue using the previous unit definition.

6. Validate the systemd unit paths

Inspect the live unit configuration:

bash
sudo systemctl cat yourapp

Check these fields carefully:

  • WorkingDirectory
  • ExecStart
  • EnvironmentFile

Example correct unit:

ini
[Unit]
Description=Gunicorn instance for yourapp
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/yourapp/current
EnvironmentFile=/etc/yourapp.env
ExecStart=/var/www/yourapp/venv/bin/gunicorn --workers 3 --bind unix:/run/yourapp.sock wsgi:app

[Install]
WantedBy=multi-user.target

Make sure the WorkingDirectory points to the active release and ExecStart uses the correct Gunicorn binary.

7. Confirm environment variables are current

If deploy-time settings changed, confirm the service is loading the right environment file:

bash
sudo systemctl show yourapp | grep -E 'Environment=|EnvironmentFiles='

If variables changed, restart the service after updating the environment file. For deeper environment debugging, see Flask Environment Variables Not Loading in Production.

If you deploy with versioned releases and a current symlink, make sure the symlink points to the new release:

bash
readlink -f /var/www/yourapp/current
ls -la /var/www/yourapp

If the symlink changed but Gunicorn is still serving the old release, restart the service so workers start from the new target.

9. Reload Nginx if upstream or static paths changed

Validate and reload Nginx:

bash
sudo nginx -t
sudo systemctl reload nginx

If you changed proxy_pass, a Unix socket path, root, or alias, Nginx must be reloaded.

To inspect active upstream configuration:

bash
sudo grep -R "proxy_pass\|root\|alias" /etc/nginx/sites-enabled /etc/nginx/conf.d

10. Rebuild or refresh static assets

If templates or Python code changed but the UI still looks old, static files may be stale.

Validate the static path configured in Nginx and compare timestamps:

bash
find /var/www/yourapp/current/static -type f | head
curl -I https://your-domain/static/app.css

If your build pipeline generates assets, rebuild them before restarting the app.

If Nginx static handling is the issue, also review the static file setup in your main deployment path: Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide).

11. Eliminate browser and proxy caching during validation

Test without browser cache:

bash
curl -I https://your-domain
curl -I "https://your-domain/?v=$(date +%s)"

Use a private browser window and, if applicable, purge CDN or reverse proxy cache.

12. Rebuild and replace containers if using Docker

If the app runs in containers, rebuild and recreate the service:

bash
docker compose build --no-cache
docker compose up -d
docker ps
docker compose logs --tail=100

Check that the running container uses the latest image and correct mount paths:

bash
docker inspect <container_name_or_id>

13. Use a controlled Gunicorn reload only if your setup supports it

If your setup uses a Gunicorn PID file, you can reload workers without a full service restart:

bash
sudo kill -HUP $(cat /run/gunicorn.pid)

Use this only if your process management is already configured for it. Otherwise use systemctl restart yourapp.

14. Add a visible release marker

Expose a version value so you can verify the active release directly.

Example Flask route:

python
from flask import Flask
import os

app = Flask(__name__)

@app.route("/version")
def version():
    return {
        "release": os.getenv("APP_RELEASE", "unknown")
    }

Then verify production:

bash
curl https://your-domain/version

This is the fastest way to prove the new release is actually live.

Common Causes

  • Gunicorn service was not restarted after deploy → workers continue serving code already loaded in memory → restart the systemd service or send a controlled Gunicorn reload.
  • systemd unit points to an old WorkingDirectory or virtualenv → service starts successfully but from the wrong release → update the unit file, run systemctl daemon-reload, and restart.
  • Symlink-based release did not switch to the latest release → active process still uses the previous target → update the current symlink and restart the service.
  • Nginx still points to an old socket or upstream → requests go to the wrong backend process → correct proxy_pass and reload Nginx.
  • Environment variables changed but service was not restarted → new config is not loaded into the process → restart the service after updating EnvironmentFile or drop-ins.
  • Static files were changed but not rebuilt or refreshed → pages appear partially outdated → rebuild assets and verify the Nginx static root.
  • Browser, CDN, or reverse proxy cache serves old responses → code is updated but the client sees stale content → bypass or purge caches and test with curl.
  • Docker container was not rebuilt or recreated → running image still contains old code → rebuild the image and redeploy the container.
  • Deployment copied files to a different directory than the service uses → new code exists on disk but is never executed → align the deploy path with WorkingDirectory or container mount path.
  • Reload command targeted the wrong service name → deployment completed without affecting the live app → identify the actual service serving traffic and restart that unit.

Debugging Section

Check service state:

bash
sudo systemctl status yourapp --no-pager
sudo journalctl -u yourapp -n 100 --no-pager
sudo journalctl -u yourapp -f

Inspect the rendered unit and environment:

bash
sudo systemctl cat yourapp
sudo systemctl show yourapp | grep -E 'Environment=|EnvironmentFiles='

Verify the running process path:

bash
ps aux | grep gunicorn
readlink -f /proc/$(pgrep -n -f 'gunicorn.*yourapp')/cwd
ls -l /proc/$(pgrep -n -f 'gunicorn.*yourapp')/exe

Check listening ports and sockets:

bash
sudo ss -ltnp | grep gunicorn
sudo ss -lxnp | grep gunicorn

Validate Nginx configuration:

bash
sudo nginx -t
sudo grep -R "proxy_pass\|root\|alias" /etc/nginx/sites-enabled /etc/nginx/conf.d

Inspect release directories:

bash
ls -la /var/www/yourapp
readlink -f /var/www/yourapp/current

If using Docker:

bash
docker ps
docker compose logs --tail=100
docker inspect <container_name_or_id>

What to look for:

  • Gunicorn cwd points to the wrong release
  • systemd still references an old path
  • Nginx proxies to an outdated socket
  • static files on disk do not match what clients receive
  • container image or bind mount is stale
  • restart failed silently because the wrong service was targeted

Checklist

  • The latest code is present in the expected deployment directory.
  • The running Gunicorn process points to the correct working directory.
  • ExecStart, WorkingDirectory, and EnvironmentFile paths are correct in systemd.
  • The service was restarted or reloaded after deployment.
  • Nginx configuration is valid and reloaded if upstream or static settings changed.
  • Static assets were rebuilt or refreshed if needed.
  • Browser, CDN, or proxy caching is not hiding the new release.
  • If using Docker, the running container was rebuilt and recreated from the latest image.
  • A live test confirms the expected version or code change is now served.

FAQ

Why is my Flask app showing old code after deploy?

The live Gunicorn process is usually still running the previous release, environment, or container image.

Should I use Gunicorn --reload in production?

No. Use a controlled restart or reload through systemd or your process manager.

Do environment variable changes require a restart?

Yes. systemd-managed services must be restarted to load updated environment values.

Can Nginx cause this problem?

Yes. If Nginx points to the wrong upstream or serves outdated static files, the app can appear unchanged.

How can I prove the new release is live?

Add a version endpoint or commit hash output and verify it with curl against production.

Final Takeaway

If Flask is not showing deployed changes, the active serving process is usually still tied to the previous code, environment, container, or asset set. Verify the live process path, restart the correct service, reload Nginx only when needed, and confirm the running release with a direct version check.