A Production Launch Checklist for Django Applications
The exact pre-launch checklist we run before shipping a Django app — settings and security, database, static files, migrations, monitoring, backups, and CI/CD — with a copy-paste runbook table.
On this page
A production launch checklist for Django applications is what separates a calm go-live from a 2 a.m. incident call. Before you set DEBUG = False and point a real domain at your server, a specific set of settings, database safeguards, and monitoring hooks has to be verified in a fixed order — because the failures that hurt most in production are rarely bugs in your Python. They are misconfigured hosts, leaked secrets, missing HTTPS, unhandled migrations, and static files that quietly stop loading.
This article is the exact pre-launch checklist we run on Django projects before they ship: settings and security, database readiness, static and media files, migrations, monitoring, backups, rate limiting, and CI/CD. Every item is verifiable today, and many map directly to the output of Django's built-in python manage.py check --deploy command.
Treat it as a go/no-go list. If an item is not green, you are not ready to launch — and holding the line there is a feature, not a delay.
- Settings first.
DEBUG = False, an explicitALLOWED_HOSTS, and a secret key loaded from the environment are non-negotiable before any traffic reaches the app. - HTTPS is a checklist, not a checkbox. SSL redirect, secure cookies, and HSTS each have to be set deliberately and then tested from a real browser.
- Databases and migrations need backups, point-in-time recovery, and backwards-compatible schema changes — proven with a real restore, not a hope.
- Static and media files stop working the moment
DEBUGis off unless you runcollectstaticand serve them through WhiteNoise or a CDN. - Observability and rollback — error tracking, health checks, and a one-command way back — turn incidents into non-events.

What does a production launch checklist for Django applications actually cover?
A good checklist is organized by failure domain, not by file. When something breaks in production, it breaks in one of a small number of predictable places: the application configuration, the transport layer, the database, static assets, or the pipeline that ships your code. Group your checks the same way and nothing falls through the cracks.
Django ships with a remarkably good starting point. Running python manage.py check --deploy against your production settings audits the most dangerous configuration mistakes and prints warnings you can clear one by one. We wire that command into CI so a misconfiguration fails the build rather than reaching a customer. It is the cheapest safety net in the entire framework, and it should be the first thing you automate.
The rest of this guide walks each failure domain in the order we verify it, ending with a single table you can copy into your own runbook. If you are handing an application off to a client or an internal team, this same list doubles as the acceptance criteria for “done.” It pairs naturally with our guide to web app maintenance after launch, which picks up where this checklist ends.
Which Django settings must change before DEBUG goes off?
The single most consequential moment in a Django deployment is the transition from development defaults to production settings. Get this wrong and you can leak your entire configuration, your database queries, and your traceback stack to anyone who triggers an error.
DEBUG, ALLOWED_HOSTS, and the secret key
DEBUG must be False. With debug on, Django renders a detailed error page that exposes local variables, settings, SQL, and environment details on any unhandled exception. That page is invaluable in development and catastrophic in production.
ALLOWED_HOSTS must be an explicit list of the domains you serve, never ['*']. This is Django's defense against HTTP Host header attacks, where a forged Host header poisons absolute URLs in password-reset emails and cache keys. List your apex domain, your www subdomain, and any internal health-check host, and nothing else.
SECRET_KEY must come from the environment or a secrets manager, never a literal in settings.py committed to git. The key signs sessions, CSRF tokens, password-reset links, and signed cookies; if it leaks, an attacker can forge all of them. If you suspect a key was ever committed, rotate it — and remember that rotating it logs every user out, so plan the timing.
# settings/production.py
import os
DEBUG = False
ALLOWED_HOSTS = ["app.example.com", "www.example.com"]
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] # fail loudly if unset
Reading SECRET_KEY with os.environ["..."] instead of os.environ.get(...) is deliberate: you want the process to refuse to boot if the secret is missing, rather than silently falling back to an insecure default. A crash on startup is a good failure. A running app signing tokens with a placeholder key is a breach waiting to happen.
Run the deploy check and read every warning
After you set the values above, run python manage.py check --deploy against the production settings module. It flags an insecure SECRET_KEY, a missing SSL redirect, cookies that are not marked secure, a short or absent HSTS policy, and more. Each warning links to the relevant docs. Do not silence them — clear them. In the projects we ship, a clean deploy check is a hard gate before a first launch and before every subsequent release.
In production, the most dangerous line of code is the one that quietly runs with DEBUG left on.
How do you lock down HTTPS, cookies, and CSRF in Django?
Transport security is where teams most often confuse “we have a certificate” with “we are secure.” A TLS certificate at the load balancer is necessary but not sufficient. Django needs to be told to require HTTPS, to mark cookies so browsers only send them over TLS, and to advertise a strict transport policy.
Force HTTPS and secure your cookies
Set SECURE_SSL_REDIRECT = True so plain HTTP requests are redirected to HTTPS. If Django sits behind a reverse proxy or load balancer that terminates TLS, also set SECURE_PROXY_SSL_HEADER so the app trusts the proxy's forwarded protocol header — but only when that proxy is the sole entry point, or a client can spoof it.
Mark session and CSRF cookies as secure with SESSION_COOKIE_SECURE = True and CSRF_COOKIE_SECURE = True, so browsers never transmit them over an unencrypted connection. On modern Django you should also confirm CSRF_TRUSTED_ORIGINS includes your exact production origin, including the scheme, or valid POST requests from your own forms will be rejected.
Turn on HSTS carefully
HTTP Strict Transport Security tells browsers to refuse plain HTTP for your domain for a set duration. It is one of the highest-value headers you can send and one of the easiest to misfire. Set SECURE_HSTS_SECONDS to a real value; a common production choice is one year (31536000). Add SECURE_HSTS_INCLUDE_SUBDOMAINS and SECURE_HSTS_PRELOAD only once you are certain every subdomain can and always will serve HTTPS.
Roll HSTS out in stages. Start with a short SECURE_HSTS_SECONDS such as a few hundred seconds, confirm nothing breaks, then raise it to a day, then a year. Because browsers cache the policy for the full duration, a mistake with includeSubDomains turned on can make a forgotten subdomain unreachable until the timer expires. Increase the value; do not gamble it.
Is your database ready for production traffic?
Development on SQLite is fine; launching on it is not. Production Django runs on a managed relational database — PostgreSQL in nearly every project we take on — because you need concurrency, durability, and backups that a single file cannot give you. Our Django development engagements standardize on managed Postgres for exactly these reasons.
Connections, pooling, and timeouts
By default Django opens a fresh database connection per request, which is wasteful under load. Set CONN_MAX_AGE to keep connections alive between requests, or put a pooler such as PgBouncer in front of the database. Whichever you choose, cap total connections so a traffic spike cannot exhaust the database's connection limit. Add a sensible statement timeout so one runaway query cannot hold a connection hostage.
Backups and point-in-time recovery
A backup you have never restored is a rumor. Confirm three things before launch: automated backups are enabled, point-in-time recovery covers the window of data you can tolerate losing, and you have personally run a restore into a scratch environment. The restore drill is the part everyone skips and the part that matters — it is how you learn your backup is complete and how long recovery actually takes.

How do you run Django migrations without downtime?
Migrations are where a routine deploy turns into an outage. The core principle is that schema changes must be backwards compatible with the code that is currently running, because for a brief window during any rolling deploy both the old and new versions of your app talk to the same database.
Split risky changes into safe steps
Adding a nullable column or a new table is safe: old code ignores it. Dropping or renaming a column is not, because old code still references it. The pattern that avoids downtime is the expand-and-contract migration:
- Expand: add the new column or table and deploy code that writes to both the old and new shapes.
- Migrate data in the background, in batches, so you never lock a large table for long.
- Contract: once all code reads the new shape, drop the old column in a later release.
Always back up immediately before a migration that alters data, and rehearse the migration against a copy of production data so you know how long it locks and whether it completes. A migration that runs in two seconds on an empty dev database can run for many minutes on a large table.
Run migrations as a distinct, gated step in your deploy pipeline — not automatically on application startup. Startup migrations race each other when several web processes boot at once, and they make rollbacks far harder because the schema has already moved ahead of the code.
How should you serve static and media files in production?
This is the classic “it worked yesterday” surprise. Django's development server serves static files for you; production does not. The moment DEBUG is False, your CSS, JavaScript, and images will 404 unless you have set up static handling.
collectstatic and WhiteNoise
Set STATIC_ROOT and run python manage.py collectstatic as part of every deploy; it gathers assets from all your apps into one directory. For small and mid-sized apps, WhiteNoise lets Django serve compressed, cache-busted static files efficiently without a separate web server, which keeps your infrastructure simple. For larger or global audiences, push the collected files to object storage and front them with a CDN so assets load close to the user and never touch your application servers.
Media files belong on object storage
User-uploaded media is not static. Never store uploads on the application server's local disk in production: containers are ephemeral, and multiple instances will not share the same filesystem. Point your file storage backend at object storage such as S3-compatible buckets, keep buckets private, and serve files through signed URLs so access stays controlled. This split — public static on a CDN, private media on access-controlled storage — is one we apply on essentially every web application we build.

What monitoring and backups do you need on day one?
You cannot fix what you cannot see. The goal of pre-launch observability is simple: when something goes wrong, you find out before your customers tell you, and you have enough context to act.
Error tracking, logging, and health checks
Wire in an error tracking service such as Sentry so unhandled exceptions are captured with stack traces, request context, and release tags instead of vanishing into a log file. Configure Django's LOGGING to emit structured logs to stdout so your platform can aggregate them. Expose a lightweight health-check endpoint that verifies the database and cache are reachable, and point an uptime monitor at it so you are paged when the app stops responding.
Rate limiting and abuse protection
Any public endpoint that authenticates, sends email, or costs you money on each call needs a rate limit. Login and password-reset routes should throttle by IP and by account to blunt credential-stuffing attempts; expensive or third-party-backed API endpoints should throttle to protect both your bill and your upstream quotas. Django REST Framework has built-in throttling, and django-ratelimit covers plain views. If your product exposes a public API, treat this as part of your API development and integration work rather than an afterthought.
What belongs in your CI/CD pipeline and rollback plan?
The last failure domain is the pipeline itself. A launch is not a one-time event; it is the first of many deploys, and the checklist only stays true if every future release re-runs it automatically.
Automate the gates
A production-grade pipeline runs your test suite, executes python manage.py check --deploy, applies migrations as a gated step, runs collectstatic, and only then swaps traffic to the new release. Keep secrets in the CI provider's encrypted store, never in the repository. This is the same discipline we bring to business automation work: encode the checklist once so a human never has to remember it under pressure.
Make rollback boring
Before you launch, answer one question out loud: how do we get back to the last good version in under five minutes? Immutable, versioned releases and a one-command rollback are what turn a bad deploy into a shrug. Because migrations move the schema forward, your rollback plan has to account for them — which is exactly why backwards-compatible migrations and pre-migration backups earn their place on this list. Skipping this step is one of the quiet reasons many developer projects fail after handoff.
The complete Django launch checklist and settings comparison
Before the full checklist, here is the side-by-side that catches the most incidents. If any “production requirement” cell still holds a development default, stop and fix it.
| Setting | Development default | Production requirement | Why it matters |
|---|---|---|---|
DEBUG | True | False | Stops leaking tracebacks, settings, and SQL |
ALLOWED_HOSTS | empty / ['*'] | explicit domain list | Blocks Host header attacks |
SECRET_KEY | literal in settings | from env / secret manager | Signs sessions, CSRF, reset tokens |
SECURE_SSL_REDIRECT | False | True | Forces every request onto HTTPS |
| Session / CSRF cookies | not secure | Secure flag on | Cookies only travel over TLS |
SECURE_HSTS_SECONDS | 0 | real value, phased in | Browser refuses plain HTTP |
| Database | SQLite | managed Postgres + backups | Concurrency and durability |
| Static files | dev server | collectstatic + WhiteNoise/CDN | Assets 404 with DEBUG off otherwise |
| Email backend | console | real SMTP / API provider | Reset and notification mail actually sends |
With the defaults corrected, copy the checklist below into your runbook and require a green mark on every row before you flip the switch. It is grouped by the failure domains above.
| Phase | Checklist item | Why it matters |
|---|---|---|
| Settings | DEBUG = False and check --deploy is clean | Prevents information disclosure |
| Settings | Explicit ALLOWED_HOSTS; secret from env | Blocks host spoofing and secret leaks |
| Transport | SSL redirect, secure cookies, HSTS set and tested | Enforces encrypted transport |
| Transport | CSRF_TRUSTED_ORIGINS includes prod origin | Own-form POSTs succeed under HTTPS |
| Database | Managed Postgres, pooling, statement timeout | Survives concurrency and spikes |
| Database | Automated backups plus a rehearsed restore | Recovery is proven, not assumed |
| Migrations | Backwards-compatible; gated deploy step | Zero-downtime rolling releases |
| Static/Media | collectstatic automated; media on object storage | Assets load; uploads persist |
| Observability | Error tracking, structured logs, health check | You see failures before users do |
| Abuse | Rate limits on auth and costly endpoints | Contains credential stuffing and cost |
| Pipeline | Tests, checks, and one-command rollback | Every release re-runs the checklist |
A first launch that clears every row is a strong start, but production readiness is a moving target. New endpoints add new rate-limit needs; new tables add new migration risk; dependencies drift out of support. Keeping the list green over time is the ongoing work our application maintenance and support service exists to handle, and it is the same rigor behind our case study on shipping a SaaS platform in eight weeks.
Frequently asked questions
What is the single most important item on a Django launch checklist?
Setting DEBUG = False with an explicit ALLOWED_HOSTS and a secret key loaded from the environment. Those three eliminate the most severe and most common production exposures. The fastest way to confirm them — and to catch the settings you forgot — is to run python manage.py check --deploy against your production settings and clear every warning it prints.
Does DEBUG = False break my static files?
Effectively yes, if you have not set up static handling. Django only serves static files itself while DEBUG is on. In production you must set STATIC_ROOT, run collectstatic on every deploy, and serve the result through WhiteNoise or a CDN. If your CSS disappears right after launch, this is almost always the cause.
How do I run migrations safely on a live database?
Make schema changes backwards compatible with the currently running code, split risky changes into expand-then-contract steps, migrate large data sets in batches, and back up immediately before any data-altering migration. Run migrations as a gated pipeline step rather than on application startup so parallel web processes do not race one another.
Can I just run Django's development server in production?
No. manage.py runserver is single-threaded, unoptimized, and explicitly not built for production traffic or security. Serve the app with an application server such as Gunicorn or Uvicorn behind a reverse proxy or load balancer that terminates TLS, and let that layer handle concurrency and HTTPS.
How often should I run the deployment checklist?
Every release, not just the first launch. The value of encoding check --deploy, tests, migrations, and collectstatic into CI/CD is that the checklist runs itself on each deploy, so a regression in configuration fails the build instead of reaching users.
What is the minimum monitoring I need before going live?
Three things: an error-tracking service capturing exceptions with context, a health-check endpoint watched by an uptime monitor, and structured application logs your platform can aggregate. That trio tells you that the app is up, why a request failed, and what changed — enough to diagnose most day-one incidents.
Planning a Django go-live and want a second set of eyes on your settings, database, and deploy pipeline before you flip the switch? Talk to our team — we run this exact checklist on every application we launch and maintain, and we are happy to run it on yours.
Sources
Have a project like this in mind?
Tell us what you're building and we'll map out the scope, timeline and a fixed starting quote — no obligation.
Start your project

