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:
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:
pwd
ls -la
git rev-parse --short HEAD
If you use release directories:
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:
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:
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:
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:
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:
sudo systemctl cat yourapp
Check these fields carefully:
WorkingDirectoryExecStartEnvironmentFile
Example correct unit:
[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:
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.
8. Verify symlink-based release switching
If you deploy with versioned releases and a current symlink, make sure the symlink points to the new release:
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:
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:
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:
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:
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:
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:
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:
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:
from flask import Flask
import os
app = Flask(__name__)
@app.route("/version")
def version():
return {
"release": os.getenv("APP_RELEASE", "unknown")
}
Then verify production:
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
WorkingDirectoryor virtualenv → service starts successfully but from the wrong release → update the unit file, runsystemctl daemon-reload, and restart. - Symlink-based release did not switch to the latest release → active process still uses the previous target → update the
currentsymlink and restart the service. - Nginx still points to an old socket or upstream → requests go to the wrong backend process → correct
proxy_passand reload Nginx. - Environment variables changed but service was not restarted → new config is not loaded into the process → restart the service after updating
EnvironmentFileor 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
WorkingDirectoryor 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:
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:
sudo systemctl cat yourapp
sudo systemctl show yourapp | grep -E 'Environment=|EnvironmentFiles='
Verify the running process path:
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:
sudo ss -ltnp | grep gunicorn
sudo ss -lxnp | grep gunicorn
Validate Nginx configuration:
sudo nginx -t
sudo grep -R "proxy_pass\|root\|alias" /etc/nginx/sites-enabled /etc/nginx/conf.d
Inspect release directories:
ls -la /var/www/yourapp
readlink -f /var/www/yourapp/current
If using Docker:
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, andEnvironmentFilepaths 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.
Related Guides
- Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)
- Flask Gunicorn Service Failed to Start
- Flask Environment Variables Not Loading in Production
- Flask Production Checklist (Everything You Must Do)
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.