System Checks

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Django 6.1 (6.1.1, released 2026-09-02; 6.1.2 is scheduled for 2026-10-06) on Python 3.12 to 3.14, with the 5.2 LTS differences noted where they matter, and was checked on 2026-10-03. The examples ran on Django 6.1.1 and Python 3.13 with SQLite, in a throw-away project with a small catalog app (the Bookshelf catalogue that grows across this section). Nothing here needs PostgreSQL or Redis; the checks that talk to a database ran against SQLite.

Django validates a project before it does most things with it. The system check framework is a set of static checks that detect common problems, such as a field without max_length, a URL pattern that can never match, or DEBUG = True in production, and that come with hints for fixing them. The framework is open: your own apps, fields, models and reusable packages can add checks, and check --deploy turns the same machinery into a pre-flight audit of your production settings. This page covers when checks run, the check command, the message and tag model, writing and testing custom checks, silencing, the deployment checks and how to use them in CI.

What runs, and when

Checks are triggered explicitly by the check command and implicitly before most other management commands, including runserver and migrate. Serious problems (level ERROR or higher) stop the command from running; minor ones are printed and the command carries on. Two facts are worth remembering.

  • Checks do not run in the WSGI or ASGI stack that serves requests in deployment, for performance reasons. A gunicorn or Uvicorn worker starts happily with a configuration that check would reject. If you want checks on the deployment server, run check explicitly, for example as a CI step or an entrypoint step (see Using the checks in CI and deployment).

  • A management command can opt out. --skip-checks skips the checks that run before the command (it exists when the command’s requires_system_checks is not empty), and a command can narrow the checks it needs by listing tags (see Checks and your own management commands).

The framework sits between the application registry and the command: Django loads settings and apps (calling each AppConfig.ready()), the checks registered by then are collected, and the command runs only if none of them reports a serious issue.

flowchart LR A[manage.py command] --> B[Load settings and apps] B --> C[AppConfig.ready registers checks] C --> D{requires_system_checks?} D -->|yes| E[run_checks] D -->|no, or skip-checks| G[Run command] E --> F{Any ERROR or above, not silenced?} F -->|yes| H[SystemCheckError, exit 1] F -->|no| I[Print warnings] --> G J[WSGI or ASGI worker] -.->|never runs checks| K[Serve requests]

The check command

check runs the registered checks and prints what it found, grouped by severity. With no issues the output is a single line, and the exit status is zero.

python manage.py check
python manage.py check --list-tags
python manage.py check catalog auth
python manage.py check --tag models --tag compatibility
python manage.py check --database default
python manage.py check --deploy --settings=config.production
python manage.py check --fail-level WARNING
System check identified no issues (0 silenced).
Table 1. Options of the check command
Option Effect

app_label …​

Restricts the checks to the given apps (the check function receives them as app_configs). By default all apps are checked.

--tag, -t

Runs only the checks registered with the tag; repeat the option for several tags. An unknown tag is an error: CommandError: There is no system check with the "nope" tag.

--list-tags

Prints the available tags. Add --deploy to include the tags of deployment-only checks.

--database

Runs the checks that need database access against the given alias; repeat for several aliases. Without it, checks tagged database are skipped.

--deploy

Adds the checks that only make sense for a production settings file.

--fail-level

The lowest level that makes the command exit with a non-zero status: CRITICAL, ERROR (the default), WARNING, INFO or DEBUG.

The default exit status is non-zero only for ERROR and CRITICAL, which is why a CI job that must also fail on warnings needs --fail-level WARNING (see Using the checks in CI and deployment).

Messages, levels and identifiers

Every finding is a CheckMessage with a level, a short message (under 80 characters, no newlines), an optional one-line hint, an optional object that gives context, and an identifier. The levels are DEBUG (10), INFO (20), WARNING (30), ERROR (40) and CRITICAL (50), and the shortcut classes Debug, Info, Warning, Error and Critical in django.core.checks set the level for you. Identifiers follow the pattern applabel.X001, where the letter is one of C, E, W, I or D for the severity: models.E…, security.W004, catalog.E001. The identifier is what you use to silence a check and to assert on it in tests, so keep it stable and unique within your app.

Messages with a level of ERROR or higher make Django refuse to run management commands; lower ones are reported and can be silenced.

Built-in tags

Checks are labelled with tags so that a subset can be run. The built-in tags are available as django.core.checks.Tags.

Table 2. Built-in check tags
Tag What it covers

admin

Admin site declarations.

async_support

Asynchronous-related configuration, such as DJANGO_ALLOW_ASYNC_UNSAFE in deployment.

caches

The CACHES setting.

compatibility

Potential problems after a Django upgrade.

commands

Configuration of custom management commands.

database

Database-related configuration. These checks do more than static analysis, so they are not run by default: only by migrate, or when --database or --tag database asks for them.

files

File-related configuration.

mail

Email configuration.

models

Model, field and manager definitions.

security

Security-related configuration (most deployment checks).

signals

Signal declarations and handler registrations.

sites

django.contrib.sites configuration.

staticfiles

django.contrib.staticfiles configuration.

templates

Template-related configuration.

translation

Translation configuration.

urls

URL configuration.

A check may carry several tags, and any string is a valid tag, so your own checks can use an app-specific tag such as catalog. check --list-tags prints the tags actually registered in your project, including custom ones.

Source: Built-in tags.

Writing a custom check

A check is a function that accepts app_configs and **kwargs and returns a list of messages, empty when everything is fine. app_configs is the list of apps to inspect, or None meaning all installed apps. **kwargs is required for future expansion; it currently carries databases. Register the function with the register decorator, optionally with tags.

Checks must be registered in a module that is loaded when the application loads. The convention is to import the module from AppConfig.ready().

catalog/apps.py
from django.apps import AppConfig


class CatalogConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "catalog"

    def ready(self):
        from . import checks  # noqa: F401  (registers the checks)
catalog/checks.py
from django.conf import settings
from django.core.checks import Error, Tags, Warning, register


@register("catalog")
def check_page_size(app_configs, **kwargs):
    errors = []
    size = getattr(settings, "BOOKSHELF_PAGE_SIZE", None)
    if not isinstance(size, int) or isinstance(size, bool) or size < 1:
        errors.append(
            Error(
                "BOOKSHELF_PAGE_SIZE must be a positive integer.",
                hint="Set BOOKSHELF_PAGE_SIZE to a value such as 20.",
                obj=settings,
                id="catalog.E001",
            )
        )
    return errors


@register("catalog", Tags.security, deploy=True)
def check_cdn_url(app_configs, **kwargs):
    errors = []
    if not getattr(settings, "BOOKSHELF_CDN_URL", ""):
        errors.append(
            Warning(
                "BOOKSHELF_CDN_URL is empty, so cover images are served by Django.",
                hint="Point BOOKSHELF_CDN_URL at the CDN that fronts MEDIA_URL.",
                id="catalog.W001",
            )
        )
    return errors

The first check always runs. With BOOKSHELF_PAGE_SIZE = 0 in the settings it stops every command, and the output points at the object and carries the hint.

SystemCheckError: System check identified some issues:

ERRORS:
<Settings "config.settings">: (catalog.E001) BOOKSHELF_PAGE_SIZE must be a positive integer.
	HINT: Set BOOKSHELF_PAGE_SIZE to a value such as 20.

System check identified 1 issue (0 silenced).

The second one is registered with deploy=True, so it is invisible to a plain check and runs only with --deploy:

$ python manage.py check --deploy --tag catalog
System check identified some issues:

WARNINGS:
?: (catalog.W001) BOOKSHELF_CDN_URL is empty, so cover images are served by Django.
	HINT: Point BOOKSHELF_CDN_URL at the CDN that fronts MEDIA_URL.

System check identified 1 issue (0 silenced).

register can also be called as a plain function, register(my_check, Tags.compatibility), which is equivalent to decorating it; that is handy for registering a callable you do not own. Tags can be Tags members or any string.

Checks that use the database

A check function receives a databases keyword argument: the list of aliases whose connections it may use to inspect database-level configuration. Register such checks with Tags.database so they only run when someone asks for database checks (migrate, --database, or --tag database).

@register(Tags.database)
def check_database_vendor(app_configs, databases=None, **kwargs):
    if databases is None:
        return []
    from django.db import connections

    return [
        Warning(
            f"Database {alias!r} uses SQLite, which is not meant for production.",
            id="catalog.W002",
        )
        for alias in databases
        if connections[alias].vendor == "sqlite"
    ]
$ python manage.py check
System check identified no issues (0 silenced).
$ python manage.py check --database default
System check identified some issues:

WARNINGS:
?: (catalog.W002) Database 'default' uses SQLite, which is not meant for production.

System check identified 1 issue (0 silenced).

Django 6.1 changed what a check that is not tagged database receives. The release notes ("System checks", backwards-incompatible changes) say the check command now supplies all databases when none is specified; in the framework code, when no alias is given, databases is the list of every configured alias instead of None. Earlier versions documented databases=None as "do not use database connections". A check that reads databases should therefore still tag itself database if it opens connections, and callers of run_checks() should expect connections to be touched. Nothing changes for checks that ignore databases.

Piggybacking on fields, models, managers and more

Fields, constraints, models, model managers, template engines, task backends and database backends already implement a check() method that is registered with the framework, so you do not call register for them. Extend the method, call super(), append your own messages and return the list; put each check in a separate helper. A custom field is the classic case.

catalog/fields.py
from django.core import checks
from django.db import models


class RatingField(models.PositiveSmallIntegerField):
    """An integer between ``min_value`` and ``max_value`` (inclusive)."""

    def __init__(self, min_value=1, max_value=5, **kwargs):
        self.min_value = min_value
        self.max_value = max_value
        super().__init__(**kwargs)

    def check(self, **kwargs):
        errors = super().check(**kwargs)
        errors.extend(self._check_min_max_values())
        return errors

    def _check_min_max_values(self):
        if self.min_value > self.max_value:
            return [
                checks.Error(
                    "min_value must not be greater than max_value.",
                    hint="Decrease min_value or increase max_value.",
                    obj=self,
                    id="catalog.E002",
                )
            ]
        return []

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        kwargs["min_value"] = self.min_value
        kwargs["max_value"] = self.max_value
        return name, path, args, kwargs

Using RatingField(min_value=5, max_value=1) in a Review model makes every command fail until it is fixed:

SystemCheckError: System check identified some issues:

ERRORS:
catalog.Review.rating: (catalog.E002) min_value must not be greater than max_value.
	HINT: Decrease min_value or increase max_value.

System check identified 1 issue (0 silenced).

On a model the method is a class method, not an instance method. The same approach works on a Manager subclass.

from django.core import checks
from django.db import models


class Book(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)

    @classmethod
    def check(cls, **kwargs):
        errors = super().check(**kwargs)
        if not any(f.name == "slug" and f.unique for f in cls._meta.get_fields()):
            errors.append(
                checks.Error("Book needs a unique slug.", obj=cls, id="catalog.E003")
            )
        return errors

Template engines gained a check() method in Django 5.1, and constraints in 6.0 (in older versions constraints did not implement check()); the task backends of the Tasks framework, new in 6.0, have one too. The overridable-check() approach therefore covers more objects than most tutorials show.

Testing checks

Messages are comparable, so a unit test can build the expected message and compare lists. Use SimpleTestCase when no database is needed, override_settings to vary the setting, and call_command("check") for an integration test that proves the check is registered and reaches the command. The tests below ran with python manage.py test catalog.

catalog/tests.py
from io import StringIO

from django.core import checks
from django.core.management import call_command
from django.core.management.base import SystemCheckError
from django.test import SimpleTestCase, override_settings

from .checks import check_cdn_url, check_page_size
from .fields import RatingField
from .models import Book


class PageSizeCheckTests(SimpleTestCase):
    @override_settings(BOOKSHELF_PAGE_SIZE=0)
    def test_rejects_non_positive_page_size(self):
        errors = check_page_size(None)
        self.assertEqual([e.id for e in errors], ["catalog.E001"])

    @override_settings(BOOKSHELF_PAGE_SIZE=20)
    def test_accepts_positive_page_size(self):
        self.assertEqual(check_page_size(None), [])

    @override_settings(BOOKSHELF_PAGE_SIZE=0)
    def test_integration_through_check_command(self):
        message = "(catalog.E001) BOOKSHELF_PAGE_SIZE must be a positive integer."
        with self.assertRaisesMessage(SystemCheckError, message):
            call_command("check", "--tag", "catalog")


class CdnDeployCheckTests(SimpleTestCase):
    @override_settings(BOOKSHELF_CDN_URL="")
    def test_warns_when_cdn_url_is_empty(self):
        stderr = StringIO()
        call_command("check", "--tag", "catalog", "--deploy", stderr=stderr)
        self.assertIn("(catalog.W001)", stderr.getvalue())

    @override_settings(BOOKSHELF_CDN_URL="https://cdn.example.com/")
    def test_silent_when_cdn_url_is_set(self):
        self.assertEqual(check_cdn_url(None), [])


class RatingFieldCheckTests(SimpleTestCase):
    def test_min_greater_than_max_is_an_error(self):
        field = RatingField(min_value=5, max_value=1)
        self.assertEqual(
            field.check(),
            [
                checks.Error(
                    "min_value must not be greater than max_value.",
                    hint="Decrease min_value or increase max_value.",
                    obj=field,
                    id="catalog.E002",
                )
            ],
        )

    def test_default_bounds_are_valid(self):
        self.assertEqual(RatingField().check(), [])

    def test_book_model_has_no_check_messages(self):
        self.assertEqual(Book.check(), [])
Ran 8 tests in 0.002s

OK

Two details matter. A warning does not raise SystemCheckError, so a warning is asserted by capturing stderr and searching it, as the deploy test does; an error is asserted with assertRaisesMessage. And --tag catalog keeps the integration test focused on your own check instead of every check in the project.

Checks and your own management commands

A custom command controls which checks run before it. requires_system_checks is a list or tuple of tags (for example [Tags.models, "catalog"]) or '__all__', which is the default. In Django 5.2, BaseCommand.get_check_kwargs() was added so a command can change the arguments passed to check(), for instance to opt into database-related checks.

catalog/management/commands/export_titles.py
from django.core.checks import Tags
from django.core.management.base import BaseCommand

from catalog.models import Book


class Command(BaseCommand):
    help = "Print every book title."
    # Run only the model checks and the catalog-tagged checks first.
    requires_system_checks = [Tags.models, "catalog"]

    def add_arguments(self, parser):
        parser.add_argument("--database", default="default")

    def get_check_kwargs(self, options):
        kwargs = super().get_check_kwargs(options)
        return {**kwargs, "databases": [options["database"]]}

    def handle(self, *args, **options):
        for title in Book.objects.using(options["database"]).values_list("title", flat=True):
            self.stdout.write(title)

With a valid configuration the command prints the titles; with BOOKSHELF_PAGE_SIZE = 0 it exits with the catalog.E001 error before handle() runs, and --skip-checks bypasses the checks and runs it anyway. The same error prevents runserver from starting, and runserver --skip-checks starts the server (the request returned 200 in the run). Use --skip-checks for a deliberate, temporary reason, not to make a failing project start.

Silencing checks

A warning that you have inspected and accepted can be hidden by listing its identifier in SILENCED_SYSTEM_CHECKS. The count in the summary line records how many messages were hidden, so silenced checks stay visible in aggregate.

# config/settings.py
SILENCED_SYSTEM_CHECKS = ["security.W005", "security.W021"]

The best place for a silence is the settings module of the environment where the decision applies, together with a comment saying why. Silencing security.W008 (“SECURE_SSL_REDIRECT` is not `True”) is reasonable when a load balancer already redirects HTTP to HTTPS; silencing it because the warning is noisy is not. Prefer fixing the setting.

The reference says messages below ERROR are the ones that can be silenced. In the scratch project, listing the identifier of an error (catalog.E002) also hid it: check reported "System check identified no issues (1 silenced)" and the exit status was zero. That is behaviour of the current implementation rather than a documented feature, and hiding an error only hides a bug (the field above was still invalid); treat silencing as a tool for warnings.

check --deploy

check --deploy adds the checks registered with deploy=True. Most of them are in the security tag and form an automated, low-hanging-fruit checklist for production settings; Django’s reference is explicit that they do not make a site secure, audit code or detect intrusions. Some may not suit your deployment, which is what SILENCED_SYSTEM_CHECKS is for.

You can run it with your development settings, but those usually lack the production values, so point it at the production settings module with --settings (or DJANGO_SETTINGS_MODULE), or run it on the staging or production host itself with the real environment. Because checks are not part of the request stack, this is the only moment they see the deployed configuration.

Running it with the development settings of a project generated by startproject produces many findings, and in 6.1 one is an error. The generated 6.1 settings file defines MAILERS with the console backend, and the new deployment-only check mail.E001 rejects a development-only backend in the default entry:

$ python manage.py check --deploy
SystemCheckError: System check identified some issues:

ERRORS:
?: (mail.E001) Your MAILERS setting uses a development-only email backend in the 'default' entry (django.core.mail.backends.console.EmailBackend).
	HINT: Use a production-ready email backend, such as the SMTP backend, otherwise email will not be sent.

WARNINGS:
?: (catalog.W001) BOOKSHELF_CDN_URL is empty, so cover images are served by Django.
?: (security.W004) You have not set a value for the SECURE_HSTS_SECONDS setting. …
?: (security.W008) Your SECURE_SSL_REDIRECT setting is not set to True. …
?: (security.W012) SESSION_COOKIE_SECURE is not set to True. …
?: (security.W016) You have 'django.middleware.csrf.CsrfViewMiddleware' in your MIDDLEWARE, but you have not set CSRF_COOKIE_SECURE …
?: (security.W018) You should not have DEBUG set to True in deployment.
?: (security.W020) ALLOWED_HOSTS must not be empty in deployment.

System check identified 8 issues (0 silenced).

The remedy is a production settings module that reads secrets and hosts from the environment. Secrets never appear in the file: SECRET_KEY comes from the environment, generated once with get_random_secret_key() or secrets.token_urlsafe() and stored in your secret manager.

config/production.py
import os

from .settings import *  # noqa: F403

DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
ALLOWED_HOSTS = os.environ["DJANGO_ALLOWED_HOSTS"].split(",")

MAILERS = {
    "default": {
        "BACKEND": "django.core.mail.backends.smtp.EmailBackend",
        "OPTIONS": {"host": os.environ.get("SMTP_HOST", "localhost")},
    }
}

SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 3600  # raise gradually once HTTPS works everywhere
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
BOOKSHELF_CDN_URL = os.environ.get("BOOKSHELF_CDN_URL", "")

# Reviewed and accepted: not every subdomain is HTTPS-only, and we do not
# want to join the HSTS preload list.
SILENCED_SYSTEM_CHECKS = ["security.W005", "security.W021"]
$ export DJANGO_SECRET_KEY=$(python -c "import secrets; print(secrets.token_urlsafe(64))")
$ export DJANGO_ALLOWED_HOSTS=books.example.com
$ BOOKSHELF_CDN_URL=https://cdn.example.com/ \
    python manage.py check --deploy --settings=config.production --fail-level WARNING
System check identified no issues (2 silenced).

With a weak key the matching check warns, which is a reminder that SECRET_KEY quality is one of the things --deploy actually verifies (at least 50 characters, at least 5 unique characters, and not the django-insecure- prefix that startproject generates):

$ DJANGO_SECRET_KEY=$(python -c "import secrets; print(secrets.token_hex(8))") \
    python manage.py check --deploy --settings=config.production
System check identified some issues:

WARNINGS:
?: (security.W009) Your SECRET_KEY has less than 50 characters, less than 5 unique characters, or it's prefixed with 'django-insecure-' …

System check identified 1 issue (1 silenced).

The deployment security checks

The identifiers below are the ones the reference lists for check --deploy. Several settings are explained in depth on Security and Content Security Policy.

Table 3. Security checks run by check --deploy
Check Meaning Usual fix

security.W001, W002, W003

SecurityMiddleware, XFrameOptionsMiddleware or CsrfViewMiddleware missing from MIDDLEWARE.

Add the middleware; W001 also means the SECURE_* settings below would have no effect.

security.W004, W005, W021

SECURE_HSTS_SECONDS not set, SECURE_HSTS_INCLUDE_SUBDOMAINS not True, SECURE_HSTS_PRELOAD not True.

Enable HSTS gradually once all of the site is HTTPS; only include subdomains and preload when every subdomain should be HTTPS-only.

security.W006

SECURE_CONTENT_TYPE_NOSNIFF not True, so no X-Content-Type-Options: nosniff header.

Enable it.

security.W008

SECURE_SSL_REDIRECT not True.

Enable it, or silence it when a load balancer or reverse proxy redirects to HTTPS.

security.W009, W025

SECRET_KEY (or an entry of SECRET_KEY_FALLBACKS) is short, has few unique characters, or starts with django-insecure-.

Generate a long random key and read it from the environment.

security.W010 to W012

SESSION_COOKIE_SECURE not True (three variants depending on how sessions are enabled).

Set SESSION_COOKIE_SECURE = True.

security.W013 to W015

SESSION_COOKIE_HTTPONLY not True (three variants).

Keep the default True.

security.W016

CSRF_COOKIE_SECURE not True.

Set it when the CSRF token lives in a cookie.

security.W018

DEBUG is True.

Set DEBUG = False.

security.W019

X_FRAME_OPTIONS is not 'DENY' while the clickjacking middleware is on.

Use 'DENY' unless parts of the site must be framed.

security.W020

ALLOWED_HOSTS is empty.

List the real host names.

security.W022, E023

SECURE_REFERRER_POLICY unset, or set to an invalid value.

Choose a valid policy.

security.E024

SECURE_CROSS_ORIGIN_OPENER_POLICY has an invalid value.

Choose a valid value.

mail.E001

A development-only backend in the default MAILERS entry (new in 6.1).

Use the SMTP backend or a provider backend.

Other security-tagged checks run without --deploy because they validate configuration instead of auditing hardening: security.E101 and E102 for the CSRF failure view, security.E026 (a CSP setting that is not a dictionary) and security.W027 (new in 6.1: the CSP nonce is used without django.template.context_processors.csp). Likewise mail.W001 (new in 6.1) warns about a MAILERS setting without a default entry on every run, while async.E001 (DJANGO_ALLOW_ASYNC_UNSAFE set in deployment) is a deployment-only check in the async_support tag, as the run with the variable set confirmed.

Source: Security checks.

Using the checks in CI and deployment

Checks are cheap, deterministic and need no running server, so they suit a pipeline. The usual arrangement is one job step that fails the build on any finding in the production settings, and one that fails on errors only in the development settings.

# Fail the pipeline on any warning or error in the production configuration.
python manage.py check --deploy --settings=config.production --fail-level WARNING

# Also fail when a model change has no migration.
python manage.py makemigrations --check --dry-run

Run the first step in an environment that has the production environment variables, otherwise os.environ[…​] raises KeyError before any check runs. --fail-level WARNING makes the exit status non-zero for warnings as well as errors, so any warning you keep must be silenced explicitly with a comment, which turns "known and accepted" into code review material instead of tribal knowledge. For a container image, the same command is a good last build step or entrypoint pre-check.

What changed recently

Table 4. System check changes by version
Version Change

3.2

requires_system_checks accepts a list of tags; boolean values are deprecated in favour of '__all__' and []. Before, a command ran either all or none of the checks.

4.0

runserver supports --skip-checks. CSRF_TRUSTED_ORIGINS values must start with a scheme, and check 4_0.E001 detects the required changes.

4.1

Support for a boolean requires_system_checks is removed.

5.1

Template engines implement a registered check() method.

5.2

BaseCommand.get_check_kwargs() lets a command control how checks run, for example to opt into database checks.

6.0

Constraints implement a registered check() method. A ForeignObject with several from_fields in indexes, constraints or unique_together now emits a check error. django.contrib.postgres fields, indexes and constraints check that the app is installed. The built-in CSP support comes with checks such as security.E026, and the task backends of the new Tasks framework implement check().

6.1

check supplies all databases when none is given (see above). New: security.W027 (CSP nonce without the context processor), the deployment-only mail.E001 and the always-on mail.W001 for MAILERS. 6.1 also adopts calendar versioning for deprecation warnings: RemovedInDjango70Warning becomes RemovedInDjango2028Warning, which matters if your CI turns deprecation warnings into errors.

The versions come from the 3.2, 4.0, 4.1, 5.1, 5.2, 6.0 and 6.1 release notes and the 6.1 check reference.

Where older material is outdated

Books and tutorials written for Django 1.11 and 2.x describe check --deploy and SILENCED_SYSTEM_CHECKS correctly in spirit, but the details moved on.

  • The list of deployment warnings is different today. security.W007 (SECURE_BROWSER_XSS_FILTER) was removed in 3.0 because browsers no longer honour the X-XSS-Protection header, and security.W017 (CSRF_COOKIE_HTTPONLY) was removed in 1.11. Newer ones exist: security.W021 (HSTS preload), W022/E023 (Referrer-Policy), E024 (Cross-Origin-Opener-Policy), W025 (SECRET_KEY_FALLBACKS, 4.1), and in 6.x the CSP and MAILERS checks.

  • Compatibility checks have turned into history. 2_0.W001 (patterns migrated from url() to path()) and 4_0.E001 (scheme-less CSRF_TRUSTED_ORIGINS) are the ones that remain in the reference; templates.E001 was removed in 5.1 because the conflict now raises ImproperlyConfigured, and mysql.E001 became mysql.W003 in 3.1.

  • requires_system_checks = True or False in a custom command no longer works: boolean values were deprecated in 3.2 and removed in 4.1. Use '__all__', [], or a list of tags.

  • The tutorial habit of running check --deploy once by hand is not a process. The warnings that earlier guides tell you to skip (for example HSTS, SSL redirect or secure cookies) are the ones that matter most behind HTTPS, so silence them deliberately with a comment, not by ignoring the output; and remember the checks never run in the serving stack.

  • Older guides run check --deploy against development settings, where DEBUG and the generated key make the output noisy. Use a production settings module, as shown above.