A Django project that works on runserver is not ready for public traffic. The development server is built for local coding, not HTTPS, process recovery, static assets, database backups, or hostile requests.
To deploy Django app code safely, separate application settings, use a real database, run an application server behind a proxy, and make every release repeatable. A production deployment is an operating process, not a one-time upload.
Start by defining the environment before you point a domain at it.
DEFINE THE PRODUCTION DEPLOYMENT BEFORE YOU RUN COMMANDS
A production stack needs clear responsibilities. Django handles requests and business logic. An application server runs Django. A reverse proxy handles TLS, public HTTP traffic, and often static files. PostgreSQL stores data.
Do not use SQLite for a multi-user production system unless you understand its locking limits and have a narrow use case. Most Django applications should use a managed PostgreSQL service or a maintained PostgreSQL instance with backups.
Separate development and production settings
Local development can use DEBUG = True, a local database, and python manage.py runserver. Production cannot.
Create an environment-specific settings module, such as config.settings.production. Keep secrets, domain names, database credentials, email credentials, and storage keys in environment variables or your host’s secrets manager.
Your production settings need these baseline values:
- Set
DEBUG = False. - Set
SECRET_KEYfrom a protected environment variable. - Add only approved public domains to
ALLOWED_HOSTS. - Set
STATIC_ROOTto a deployment directory. - Configure PostgreSQL through environment-based credentials.
- Configure error reporting before users find the errors first.
Django’s production deployment guide is the source of truth when a setting changes between Django versions. Django 6.1 supports Python 3.12 through 3.14. Django 5.2 is the current LTS line for teams that need a longer support window.
Decide where each component runs
You do not need one universal hosting choice. A managed platform can reduce server work. A virtual machine gives you more control. Containers work well when your team already has image builds, registries, and monitoring.
Use this minimum architecture:
| Component | Production job | Common options |
|---|---|---|
| Django application | Processes requests and runs business logic | Gunicorn, Uvicorn, Daphne |
| Reverse proxy | Terminates HTTPS and forwards requests | Nginx, Caddy, platform load balancer |
| Database | Stores application data | Managed PostgreSQL or self-managed PostgreSQL |
| Static file storage | Delivers CSS, JavaScript, and images | Nginx, object storage, CDN |
| Background worker | Runs queued tasks | Celery worker with Redis or another broker |
Keep the web process and background worker separate. A slow email task should not block an account signup request.
PREPARE THE REPOSITORY AND RUNTIME
A deployment should build the same application every time. Pin dependencies, track migrations in Git, and write down the runtime version.
Create a virtual environment or container image from a locked dependency file. Your release must install the same Django, database driver, and application-server versions that you tested.
Add a production process command
For a standard WSGI application, Gunicorn can start Django with a command like gunicorn config.wsgi:application --bind 127.0.0.1:8000.
For an ASGI application that uses WebSockets or async views, use an ASGI server such as Uvicorn with a command like uvicorn config.asgi:application --host 127.0.0.1 --port 8000.
Replace config with the Python package that contains your settings.py, wsgi.py, and asgi.py files.
Do not expose Gunicorn or Uvicorn directly to the public internet unless your platform manages that boundary. Bind it to localhost or a private network. Put Nginx, Caddy, or a managed load balancer in front.
A typical Nginx, Gunicorn, and PostgreSQL setup is covered in this Django production stack walkthrough. Treat provider instructions as provider-specific. Check your host’s current documentation before copying service files, firewall rules, or deployment commands.
Add a process manager and logs
Your application server must restart after a crash or server reboot. On a Linux virtual machine, systemd commonly manages the web process. Managed platforms usually provide their own process supervisor.
Record these items in your deployment runbook:
- The command that starts the web process.
- The service name or platform process name.
- The log location or logging dashboard.
- The health-check URL.
- The person responsible for the last known good release.
- The exact rollback method.
A deployment is not complete when code reaches the server. It is complete when the public domain, logs, health check, database, and background jobs all pass review.
CONFIGURE DJANGO FOR A PUBLIC DOMAIN
Most first production failures come from settings that were harmless on localhost. Configure the domain, HTTPS behavior, proxy headers, and trusted origins before launch.
Set hosts, CSRF origins, and HTTPS rules
Set ALLOWED_HOSTS to exact names such as app.example.com and www.example.com. Include the load balancer hostname only when users or probes send requests through it.
For forms that submit from your public site, set CSRF_TRUSTED_ORIGINS with complete HTTPS origins, such as https://app.example.com.
Redirect HTTP to HTTPS after TLS works. Use SECURE_SSL_REDIRECT = True when Django receives the original request scheme correctly. If a reverse proxy terminates TLS, configure SECURE_PROXY_SSL_HEADER only if the proxy controls and strips incoming forwarding headers.
Do not turn on one-year HSTS settings before you verify every public subdomain supports HTTPS. A mistaken HSTS configuration can lock browsers into a broken route.
Run Django’s deployment checklist with python manage.py check --deploy. Fix every warning you understand before launch. Do not treat the command as a substitute for security review, backups, or access control.
Keep secrets out of Git
Never commit .env files, production database passwords, Django secret keys, cloud access keys, or email provider credentials.
Use your platform’s encrypted environment variables or a managed secrets store. Limit access to people and services that require it. Rotate credentials after an accidental exposure, even if you remove the value from Git history.
Use different databases and secret keys for local, staging, and production environments. A staging test should never write to production customer data.
HANDLE DATABASES, MIGRATIONS, AND STATIC FILES
Database changes and static assets are release dependencies. They need the same discipline as application code.
Use PostgreSQL and test the real connection
Create a dedicated database and database user with only the permissions your application needs. Store the hostname, port, database name, username, password, and SSL requirements in protected configuration.
Before a release, test the connection from the application environment. A working connection from your laptop proves little. DNS, firewall rules, private networking, SSL settings, and database allowlists often differ in production.
Run migrations with python manage.py migrate as part of the release process. Review destructive migrations before running them. For large tables, plan schema changes that work across multiple releases. Adding a required column, backfilling data, and enforcing the constraint may need separate steps.
Back up the database before risky migrations. Test restoration, not only backup creation.
Collect static assets outside Django
Django should not serve CSS, JavaScript, fonts, or admin assets in production. Configure STATIC_ROOT, then run python manage.py collectstatic --noinput during the build or release.
Django’s static files documentation explains how collectstatic gathers assets into one deployment location. Your reverse proxy, object storage provider, CDN, or an approved static-file middleware then serves that location.
User uploads are different from static files. Product photos, attachments, and generated reports need durable media storage. Do not rely on a container filesystem or temporary platform disk unless your provider documents it as persistent storage.
RELEASE IN A FIXED ORDER
A fixed release sequence makes failures easier to find and rollback. It also stops the common mistake of running migrations after code already depends on them.
Use this order for a normal release:
- Build the release from a tagged Git commit and install locked dependencies.
- Load production secrets and confirm
DJANGO_SETTINGS_MODULEpoints to production settings. - Run automated tests and
python manage.py check --deploy. - Run
python manage.py migrateafter reviewing the migration plan. - Run
python manage.py collectstatic --noinput. - Restart or release the application process.
- Check the public health endpoint, a logged-in flow, static assets, and error logs.
- Record the release commit, deployment time, migration status, and reviewer.
Use health checks that test useful behavior
A /health/ endpoint can return a simple successful response. Keep it fast and free of authentication. Your load balancer can use it to decide whether a web process is available.
For deeper checks, add a protected operational endpoint or monitoring job that verifies the database, queue, and critical third-party dependencies. Do not make every public health request run expensive database queries.
Track failed requests, response time, process restarts, database connections, disk use, and queue depth. A site can return HTTP 200 while emails, payments, or scheduled jobs are failing.
PRODUCTION DEPLOYMENT CHECKLIST
Use this checklist every time you deploy a Django app to production:
- Confirm
DEBUGis false andSECRET_KEYis not in source control. - Confirm
ALLOWED_HOSTS,CSRF_TRUSTED_ORIGINS, and public DNS records match. - Confirm HTTPS works before forcing redirects or HSTS.
- Confirm PostgreSQL backups run and at least one restore has been tested.
- Run
python manage.py check --deploy, tests, migrations, andcollectstatic. - Confirm static assets load with a browser cache disabled.
- Confirm the web process restarts automatically.
- Confirm application, proxy, worker, and database logs are available.
- Test a public page, login flow, form submission, and admin access.
- Record the release version and keep the previous working release available.
Do not skip the rollback plan. If a migration is not reversible, document that before it runs.
FIX COMMON DJANGO DEPLOYMENT FAILURES
Production errors usually have a narrow cause. Start with the request path, process logs, and the last deployment change. Do not change five settings at once.
A 502 Bad Gateway error
A 502 usually means the reverse proxy cannot reach Gunicorn, Uvicorn, or another application process. Check whether the process is running, listening on the expected port or socket, and using the correct project module.
Review application-server logs for import failures, missing environment variables, database errors, and permission errors. Then check the reverse proxy upstream address. A proxy targeting 127.0.0.1:8000 cannot reach an app listening on a Unix socket or a different port.
Also check firewall rules and socket ownership. Restarting Nginx alone will not repair a crashed Django process.
collectstatic fails or assets return 404
First, confirm django.contrib.staticfiles is installed and STATIC_ROOT points to a writable deployment directory. The release user needs permission to create and update files there.
If the command works but files return 404, check the reverse proxy or storage configuration. The public static URL must map to the same location where collectstatic wrote the files.
Do not set STATIC_ROOT to the same folder used for source static files. Keep collected output separate.
The database will not connect
Read the full error. Authentication failures, missing database names, blocked network traffic, and SSL mismatches require different fixes.
Check production environment variables from the running process, not only from your local shell. Confirm the database allows connections from the application network. Then verify the PostgreSQL user has access to the target database and schema.
A database connection pool can also exhaust available connections. Check worker count and database connection limits before scaling web processes.
Django returns a DisallowedHost error
This error means the incoming Host header is not in ALLOWED_HOSTS. Add the exact approved hostname, redeploy, and retry through that hostname.
Do not solve it with ALLOWED_HOSTS = ["*"]. That removes an important request validation control.
Permissions fail after deployment
A process can start under a different user than the user who ran the release. That difference causes failures with static directories, Unix sockets, uploaded media, log files, and local cache paths.
Check ownership and read-write permissions for each path. Fix the service user, group, and deployment directory policy. Avoid broad permissions such as 777, which hide the real access problem and create a security risk.
FINAL DEPLOYMENT RULE
A reliable Django release uses controlled settings, repeatable commands, protected secrets, and visible logs. The local development server has one job, helping you build. Production infrastructure has another job, keeping the application available and recoverable.
When you deploy Django app code, treat migrations, static files, process health, and rollback records as part of one release. That discipline prevents most launch-day failures before users see them.
