Skip to content

Django app integration

Add pulumi-django-azure to the Django project that runs on App Service.

Minimal settings wiring

from pulumi_django_azure.settings import *  # noqa: F403
from pulumi_django_azure.settings import patch_django_settings_for_azure

INSTALLED_APPS = [
    # ... your apps ...
    "django.contrib.staticfiles",
]

MIDDLEWARE = [
    # ... your middleware ...
]

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "OPTIONS": {
            "context_processors": [
                # ...
            ],
        },
    },
]

patch_django_settings_for_azure(INSTALLED_APPS, MIDDLEWARE, TEMPLATES)

patch_django_settings_for_azure will:

  • Insert collectfasta before django.contrib.staticfiles
  • Append pulumi_django_azure, django_rq, and django_tasks_rq if missing
  • Insert SuspiciousRequestBlockMiddleware before SecurityMiddleware on Azure when probe blocking is enabled (default)
  • Insert SuspiciousRequestAccessBlockMiddleware before SecurityMiddleware on Azure when ACCESS_BLOCK_ENABLED=true and ACCESS_BLOCK_QUEUE_URL is set (Pulumi default: add_django_website(..., access_block=True))
  • Insert RequestLogContextMiddleware before SecurityMiddleware on Azure, outermost of the package middleware (adds client IP and Host to Django log lines, including after probe blocking)
  • Insert HealthCheckMiddleware before SecurityMiddleware on Azure (append when not on Azure)
  • Insert WagtailHostAliasMiddleware before Wagtail’s redirect middleware when present
  • Append the add_build_info context processor

Scanner probe blocking

On Azure, SuspiciousRequestBlockMiddleware rejects common vulnerability-scanner traffic before SSL redirect and URL routing. This reduces log noise and worker load from automated probes (.php shells, WordPress paths, empty User-Agent, and similar).

Blocked by default (hardcoded rules):

  • Any path segment starting with . (e.g. /.env, /.git/) — except /.well-known/
  • Paths ending in .php, .asp, .aspx, .jsp, .cgi, .sql, .bak
  • Paths starting with /wp-, /cgi-bin/, /phpmyadmin, /administrator/, /vendor/phpunit, and other non-Django CMS/framework prefixes (see Settings and env)
  • Paths containing /phpunit/
  • Requests with a missing, empty, or whitespace-only User-Agent header

Not blocked: /health-check, /.well-known/…, /admin/ (Django/Wagtail), and normal site URLs.

Disable individually via App Settings:

Variable Default on Azure Purpose
BLOCK_SUSPICIOUS_PATHS true Path-based probe blocking
BLOCK_EMPTY_USER_AGENT true Reject requests without a User-Agent

Set either to false to disable that check. When both are false, the middleware is not registered.

Blocked requests return 404 immediately and are logged at DEBUG on pulumi_django_azure.security (not django.request WARNING).

When automated access block is enabled, the same suspicious requests are also enqueued for App Service IP Deny rules after a rate threshold (no Redis required).

Manual alternative

Without the patch helper:

from pulumi_django_azure.settings import *  # noqa: F403

INSTALLED_APPS += ["collectfasta", "pulumi_django_azure", "django_rq", "django_tasks_rq"]
MIDDLEWARE += ["pulumi_django_azure.middleware.HealthCheckMiddleware"]

Place collectfasta before django.contrib.staticfiles.

What settings do on Azure

When App Service sets IS_AZURE_ENVIRONMENT=true (done by Pulumi), the imported settings configure Postgres with Entra tokens, Azure Storage/CDN, secure cookies, optional Redis and tasks, optional ACS email via MAILERS when AZURE_COMMUNICATION_SERVICE_ENDPOINT is set, and logging. See Settings and env and Local vs production.

Health check

Pulumi configures App Service health checks at /health-check. On Azure, HealthCheckMiddleware is registered before SecurityMiddleware so probes are answered without SSL redirect or Wagtail routing.

The middleware:

  • Serves /health-check: probes the database and can recycle Gunicorn workers on failure. (DB tokens are not refreshed here: the DB engine acquires one per connection, see the database guide.)
  • Serves Azure Always-On pings (GET / from loopback with User-Agent: AlwaysOn) with a lightweight 200 OK — no DB probe.

Override freely

Import Azure defaults first, then override any setting in your module afterward.