Skip to main content

shows a 503 error page when maintenance-mode is on.

Project description

OpenSSF Scorecard

django-maintenance-mode

django-maintenance-mode shows a 503 error page when maintenance-mode is on.

It works at application level, so your django instance should be up.

It doesn't use database and doesn't prevent database access.

Installation

  1. Run pip install django-maintenance-mode or download django-maintenance-mode and add the maintenance_mode package to your project
  2. Add maintenance_mode to settings.INSTALLED_APPS before custom applications
  3. Add maintenance_mode.middleware.MaintenanceModeMiddleware to settings.MIDDLEWARE as last middleware (see note below)
  4. Add your custom templates/503.html file
  5. Restart your application server

[!NOTE] Middleware position: by default the middleware runs last, so preceding middleware (session, auth, etc.) still executes during maintenance mode. To skip that (eg. during DB migrations or automated deployments), move MaintenanceModeMiddleware higher up and use MAINTENANCE_MODE_IGNORE_URLS to keep specific URLs accessible. Note: skipping middleware also disables its features (eg. request.user), so user-based settings (MAINTENANCE_MODE_IGNORE_*USER) won't work.

Configuration (optional)

Settings

All these settings are optional, if not defined in settings.py the default values (listed below) will be used.

# if True the maintenance-mode will be activated
MAINTENANCE_MODE = None
# by default, to get/set the state value a local file backend is used
# if you want to use the db or cache, you can create a custom backend
# custom backends must extend 'maintenance_mode.backends.AbstractStateBackend' class
# and implement get_value(self) and set_value(self, val) methods
# (use the 'from_state_to_str_value' / 'from_str_to_state_value' class methods
# to serialize/deserialize the state value, they support both plain bool values
# and scheduled maintenance state)
MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.LocalFileBackend"

# alternatively it is possible to use the default storage backend
MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.DefaultStorageBackend"

# alternatively it is possible to use the static storage backend
# make sure that STATIC_ROOT and STATIC_URL are also set
MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.StaticStorageBackend"

# alternatively it is possible to use the cache backend
# you can use a custom cache backend by adding a `maintenance_mode` entry to `settings.CACHES`,
# otherwise the default cache backend will be used.
MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.CacheBackend"
# if set, it specifies the name of the cache (from settings.CACHES) to use with CacheBackend.
# if None, a cache named "maintenance_mode" will be used if present in settings.CACHES,
# otherwise the default cache will be used.
MAINTENANCE_MODE_CACHE_BACKEND = None
# the fallback value that backends will return in case of failure
# (actually this is only used by "maintenance_mode.backends.CacheBackend")
MAINTENANCE_MODE_STATE_BACKEND_FALLBACK_VALUE = False
# by default, a file named "maintenance_mode_state.txt" will be created in the settings.py directory
# you can customize the state file path in case the default one is not writable
MAINTENANCE_MODE_STATE_FILE_PATH = "maintenance_mode_state.txt"
# if True admin site will not be affected by the maintenance-mode page
MAINTENANCE_MODE_IGNORE_ADMIN_SITE = False
# if True anonymous users will not see the maintenance-mode page
MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER = False
# if True authenticated users will not see the maintenance-mode page
MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER = False
# if True the staff will not see the maintenance-mode page
MAINTENANCE_MODE_IGNORE_STAFF = False
# if True the superuser will not see the maintenance-mode page
MAINTENANCE_MODE_IGNORE_SUPERUSER = False
# list of ip-addresses that will not be affected by the maintenance-mode
# ip-addresses will be used to compile regular expressions objects
MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = ()
# the path of the function that will return the client IP address given the request object -> 'myapp.mymodule.myfunction'
# the default function ('maintenance_mode.utils.get_client_ip_address') returns request.META['REMOTE_ADDR']
# in some cases the default function returns None, to avoid this scenario just use 'django-ipware'
MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = None

Retrieve user's real IP address using django-ipware:

MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = "ipware.ip.get_ip"
# the path of the function that will return the response context -> 'myapp.mymodule.myfunction'
MAINTENANCE_MODE_GET_CONTEXT = None
# the path of the function that will return the authenticated user given the request object -> 'myapp.mymodule.myfunction'
# useful when the authentication is not session-based (eg. JWT or token authentication with django-rest-framework)
# and 'request.user' is not populated by the authentication middleware,
# the function must return a user instance or None (in this case 'request.user' will be used)
# eg. using 'djangorestframework-simplejwt':
# def get_authenticated_user(request):
#     from rest_framework_simplejwt.authentication import JWTAuthentication
#     from rest_framework_simplejwt.exceptions import InvalidToken
#     try:
#         result = JWTAuthentication().authenticate(request)
#         return result[0] if result else None
#     except InvalidToken:
#         return None
MAINTENANCE_MODE_GET_AUTHENTICATED_USER = None
# list of urls that will not be affected by the maintenance-mode
# urls will be used to compile regular expressions objects
MAINTENANCE_MODE_IGNORE_URLS = ()
# if True the maintenance mode will not return 503 response while running tests
# useful for running tests while maintenance mode is on, before opening the site to public use
MAINTENANCE_MODE_IGNORE_TESTS = False
# if True all authenticated users (staff users and superusers included) will be logged out from their current session
MAINTENANCE_MODE_LOGOUT_AUTHENTICATED_USER = False
# if None (default) staff users inherit the MAINTENANCE_MODE_LOGOUT_AUTHENTICATED_USER behavior,
# set it explicitly to True/False to override it
MAINTENANCE_MODE_LOGOUT_STAFF_USER = None
# if None (default) superusers inherit the MAINTENANCE_MODE_LOGOUT_AUTHENTICATED_USER behavior,
# set it explicitly to True/False to override it
# eg. logout all users except superusers:
# MAINTENANCE_MODE_LOGOUT_AUTHENTICATED_USER = True
# MAINTENANCE_MODE_LOGOUT_SUPERUSER = False
MAINTENANCE_MODE_LOGOUT_SUPERUSER = None
# the absolute url where users will be redirected to during maintenance-mode
MAINTENANCE_MODE_REDIRECT_URL = None
# the type of the response returned during maintenance mode, can be either "html" or "json",
# or the path of a function that will be called with the request as argument
# and must return "html" or "json" -> 'myapp.mymodule.myfunction'
# eg. return a json response for api urls and an html response for all the others:
# def get_response_type(request):
#     return "json" if request.path_info.startswith("/api") else "html"
MAINTENANCE_MODE_RESPONSE_TYPE = "html"
# the template that will be shown by the maintenance-mode page
MAINTENANCE_MODE_TEMPLATE = "503.html"
# the HTTP status code to send
MAINTENANCE_MODE_STATUS_CODE = 503
# the value in seconds of the Retry-After header during maintenance-mode
MAINTENANCE_MODE_RETRY_AFTER = 3600 # 1 hour

Context Processors

Add maintenance_mode.context_processors.maintenance_mode to your context_processors list in settings.py if you want to access the maintenance_mode status in your templates.

TEMPLATES = [
    {
        # ...
        "OPTIONS": {
            "context_processors": [
                # ...
                "maintenance_mode.context_processors.maintenance_mode",
                # ...
            ],
        },
        # ...
    },
]

Logging

You can disable emailing 503 errors to admins while maintenance mode is enabled:

LOGGING = {
    "filters": {
        "require_not_maintenance_mode_503": {
            "()": "maintenance_mode.logging.RequireNotMaintenanceMode503",
        },
        ...
    },
    "handlers": {
        ...
    },
    ...
}

Context Managers

You can force a block of code execution to run under maintenance mode or not using context managers:

from maintenance_mode.core import maintenance_mode_off, maintenance_mode_on

with maintenance_mode_on():
    # do stuff
    pass

with maintenance_mode_off():
    # do stuff
    pass

URLs

Add maintenance_mode.urls to urls.py if you want superusers able to set maintenance_mode using urls.

urlpatterns = [
    # ...
    re_path(r"^maintenance-mode/", include("maintenance_mode.urls")),
    # ...
]

Views

You can force maintenance mode on/off at view level using view decorators:

Function-based views

from maintenance_mode.decorators import force_maintenance_mode_off, force_maintenance_mode_on

@force_maintenance_mode_off
def my_view_a(request):
    # never return 503 response
    pass

@force_maintenance_mode_on
def my_view_b(request):
    # always return 503 response
    pass

Class-based views

from maintenance_mode.decorators import force_maintenance_mode_off, force_maintenance_mode_on

urlpatterns = [
    # never return 503 response
    path("", force_maintenance_mode_off(YourView.as_view()), name="my_view"),

    # always return 503 response
    path("", force_maintenance_mode_on(YourView.as_view()), name="my_view"),
]

Usage

Python

from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode

set_maintenance_mode(True)

if get_maintenance_mode():
    set_maintenance_mode(False)

It is also possible to schedule maintenance mode with start and/or end datetimes (datetime objects or ISO 8601 strings, naive datetimes are interpreted using the project time zone), maintenance mode will automatically activate/deactivate itself accordingly:

from datetime import timedelta

from django.utils import timezone

from maintenance_mode.core import set_maintenance_mode

# maintenance mode from 01:00 to 03:00 (auto on/off)
set_maintenance_mode(True, start="2026-07-14 01:00", end="2026-07-14 03:00")

# maintenance mode on, automatically off after 2 hours (fail-safe, no cron needed)
set_maintenance_mode(True, end=timezone.now() + timedelta(hours=2))

# any set call overwrites the current state/schedule (last-write-wins)
set_maintenance_mode(False)

or

from django.core.management import call_command
from django.core.management.base import BaseCommand


class Command(BaseCommand):

    def handle(self, *args, **options):

        call_command("maintenance_mode", "on")

        # call your command(s)

        call_command("maintenance_mode", "off")

Templates

{% if maintenance_mode %}
<!-- html -->
{% endif %}

Terminal

Run python manage.py maintenance_mode <on|off>

The on state supports optional --start and --end datetime options (ISO 8601 format):

# maintenance mode from 01:00 to 03:00 (auto on/off)
python manage.py maintenance_mode on --start "2026-07-14 01:00" --end "2026-07-14 03:00"

# maintenance mode on, automatically off at 03:00 (fail-safe, no extra cron job needed)
python manage.py maintenance_mode on --end "2026-07-14 03:00"

For recurring maintenance windows, combine this command with a cron job:

# maintenance mode every sunday from 01:00 to 02:00 (single job, auto-off)
0 1 * * sun python manage.py maintenance_mode on --end "$(date +\%Y-\%m-\%dT02:00:00)"

(This is not Heroku-friendly because any execution of heroku run manage.py will be run on a separate worker dyno, not the web one. Therefore the state-file is set but on the wrong machine. You should use a custom MAINTENANCE_MODE_STATE_BACKEND.)

URLs

Superusers can change maintenance-mode using the following urls:

/maintenance-mode/off/

/maintenance-mode/on/

Testing

# clone repository
git clone https://github.com/fabiocaccamo/django-maintenance-mode.git && cd django-maintenance-mode

# create virtualenv and activate it
python -m venv venv && . venv/bin/activate

# upgrade pip
python -m pip install --upgrade pip

# install requirements
pip install -r requirements.txt -r requirements-test.txt

# install pre-commit to run formatters and linters
pre-commit install --install-hooks

# run tests
tox
# or
python runtests.py
# or
python -m django test --settings "tests.settings"

License

Released under MIT License.


Supporting

  • :star: Star this project on GitHub
  • :octocat: Follow me on GitHub
  • :blue_heart: Follow me on Bluesky
  • :moneybag: Sponsor me on Github

See also

  • django-admin-interface - the default admin interface made customizable by the admin itself. popup windows replaced by modals. 🧙 ⚡

  • django-cache-cleaner - clear the entire cache or individual caches easily using the admin panel or management command. 🧹✨

  • django-colorfield - simple color field for models with a nice color-picker in the admin. 🎨

  • django-email-validators - no more invalid or disposable emails in your database. ✉️ ✅

  • django-extra-settings - config and manage typed extra settings using just the django admin. ⚙️

  • django-redirects - redirects with full control. ↪️

  • django-treenode - probably the best abstract model / admin for your tree based stuff. 🌳

  • python-benedict - dict subclass with keylist/keypath support, I/O shortcuts (base64, csv, json, pickle, plist, query-string, toml, xml, yaml) and many utilities. 📘

  • python-codicefiscale - encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. 🇮🇹 💳

  • python-fontbro - friendly font operations. 🧢

  • python-fsutil - file-system utilities for lazy devs. 🧟‍♂️

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_maintenance_mode-0.23.0.tar.gz (23.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_maintenance_mode-0.23.0-py3-none-any.whl (20.0 kB view details)

Uploaded Python 3

File details

Details for the file django_maintenance_mode-0.23.0.tar.gz.

File metadata

  • Download URL: django_maintenance_mode-0.23.0.tar.gz
  • Upload date:
  • Size: 23.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for django_maintenance_mode-0.23.0.tar.gz
Algorithm Hash digest
SHA256 221a958a912137d0809fe75bc077a6437d2c4e4f212e4bf6a3856c484e2bba6f
MD5 f8ae31cddc1a9c78c36474348370bb80
BLAKE2b-256 389ee5dc98de3e17f19cfe7dc50641d476b441b8ef2b34000a2015c05ab76066

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_maintenance_mode-0.23.0.tar.gz:

Publisher: create-release.yml on fabiocaccamo/django-maintenance-mode

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_maintenance_mode-0.23.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_maintenance_mode-0.23.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a589ace5b73efa08d26ad9114394d8b0d3127a3ba50ea767820bfda6b7ca0f65
MD5 c8f4eac0cda4556c06b65a49afc244a3
BLAKE2b-256 b3341d5a0ac802804803c29740d7707f698b46b28f9338aa3a535ef0b30dd8e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_maintenance_mode-0.23.0-py3-none-any.whl:

Publisher: create-release.yml on fabiocaccamo/django-maintenance-mode

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page