Django On IIS

Adrian Jenkins
7 min readFeb 17, 2023

--

The aim of this article is to deploy Django application on Internet Information Services (IIS).

I am no Django nor Python expert. I had to spend a bit of time reading about Django in order for this to work with IIS. If you are well-versed in these technologies, then I guess there are some tweaks you could make, and app will still run.

There are two ways in which we can deploy Django on IIS:

  • HttpPlatform handler
  • FastCGI Handler (WFastCGI)

Microsoft recommends using HttpPlatform handler, as wFastCGI is no longer maintained.

Configure Python web apps for IIS — Visual Studio (Windows) | Microsoft Learn

I will show you how to setup Django with both HttpPlatform handler and WFastCGI. First, I will do all required things that, regardless of how we set it up in IIS, we need to do.

As for my environment, this is a brand-new Windows Server 2019 VM.

  1. Install Python & Django

Download Python

Download Django

pip install Django==4.1.7

Check python and Django installation:

# Python
python--version

# Django
py -m django --version
C:\Users\azureuser>python --version
Python 3.11.2

C:\Users\azureuser>py -m django --version
4.1.7

Up to this point we have python and Django installed.

We now need the actual application!

Again, not an expert on Django, So I will follow this documentation to create a basic Django application.

Writing your first Django app, part 1 | Django documentation | Django (djangoproject.com)

I will create this application under “C:\sites\site01”

Let us familiarize ourselves with the basic structure:

  • The outer mysite/ root directory is a container for your project. Its name doesn’t matter to Django; you can rename it to anything you like.
  • manage.py: A command-line utility that lets you interact with this Django project in various ways. You can read all the details about manage.py in django-admin and manage.py.
  • The inner mysite/ directory is the actual Python package for your project. Its name is the Python package name you’ll need to use to import anything inside it (e.g. mysite.urls).
  • mysite/__init__.py: An empty file that tells Python that this directory should be considered a Python package. If you’re a Python beginner, read more about packages in the official Python docs.
  • mysite/settings.py: Settings/configuration for this Django project. Django settings will tell you all about how settings work.
  • mysite/urls.py: The URL declarations for this Django project; a “table of contents” of your Django-powered site. You can read more about URLs in URL dispatcher.
  • mysite/asgi.py: An entry-point for ASGI-compatible web servers to serve your project. See How to deploy with ASGI for more details.
  • mysite/wsgi.py: An entry-point for WSGI-compatible web servers to serve your project. See How to deploy with WSGI for more details.

Start manage.py. This will run under “http://localhost:8000”. Although you can specify the port.

py manage.py runserver

If all goes well, you should have three routes:

The “Not Found” route:

/polls:

/admin:

Great, now we have our Django application working. Well, not completely, but this will suffice for our tutorial.

The only thing that I will add to the current folder structure is that I am going to create a folder called “logs” for later use.

This is my folder structure for “C:\sites\site01”:

C:\sites\site01>tree /F
C:.
└───mysite
│ db.sqlite3
│ manage.py

├───logs
├───mysite
│ │ asgi.py
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
│ │ __init__.py
│ │
│ └───__pycache__
│ settings.cpython-311.pyc
│ urls.cpython-311.pyc
│ wsgi.cpython-311.pyc
│ __init__.cpython-311.pyc

└───polls
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ urls.py
│ views.py
│ __init__.py

├───migrations
│ __init__.py

└───__pycache__
urls.cpython-311.pyc
views.cpython-311.pyc
__init__.cpython-311.pyc

Now I want to make two Django Apps, one will be using HttpPlatform and the other one will be using WFastCGI.

Made a copy of “C:\sites\site01” and renamed it as “C:\sites\site02”.

Like so:

C:\sites>tree
C:.
├───site01
│ └───mysite
│ ├───logs
│ ├───mysite
│ │ └───__pycache__
│ └───polls
│ ├───migrations
│ └───__pycache__
└───site02
└───mysite
├───logs
├───mysite
│ └───__pycache__
└───polls
├───migrations
└───__pycache__

C:\

IIS

Both methods require IIS. Default installation through Server Manager is fine.

With PS:


Install-WindowsFeature -name Web-Server -IncludeManagementTools

For both methods we need to edit come Handler Mappings. However, by default, this has only read permissions. We need to have Read/Write.

IIS Manager > Server Level > Feature Delegation > Handler Mappings > Read/Write

Option 1 — HttpPlatform Handler

I will create a new website called “site01” accessible under “http://localhost:8001”.

Download HttpPlatformHandler

The HttpPlatformHandler v1.2 is an IIS Module which enables process management of HTTP Listeners and proxies requests to the process it manages.

The HttpPlatformHandler v1.2 is an IIS module which does two things:

  1. Process management of HTTP Listeners — this could be any process that can listen on a port for HTTP requests, for example Tomcat, Jetty, Node.exe, Ruby etc.
  2. Proxy requests to the process it manages.

Configure httpPlatform

IIS Manager > Sites > Site01 > Configuration Editor > system.webServer/httpPlatform

  • Arguments: path where “manage.py” is. Run it with “manage.py runserver %HTTP_PLATFORM_PORT%”
  • environmentVariables: an environment variable that will dynamically pass the value of the server variable “SERVER_PORT” into. %HTTP_PLATFORM_PORT%
  • processPath: path where “python.exe” is.
  • stdoutLogEnabled: True
  • stdoutLogFile: path where logs will be stored.

Now, Django needs some specific path settings:

  • PYTHONPATH: path to where you app is. In my case is pointing to mysite<OUTER>
  • WSGI_HANDLER: django.core.wsgi.get_wsgi_application()
  • DJANGO_SETTINGS_MODULE: path where “settings.py” is. Thanks to PYTHONPATH we just need to access “mysite<INNER>.settings”. Hence value is: “mysite.settings”

IIS manager > Sites > Sites01 > Configuration Editor > appSettings

Add module mapping:

IIS manager > Sites > Sites01 > Handler Mappings > Add Module Mapping

  • Request path: *
  • Module: httpPlatformHandler
  • Name: -

Then, click “Request Restrictions” and unchecked “Invoked handler only if requests is mapped to”

Our “web.config” should look somewhat like this:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<httpPlatform processPath="C:\Python311\python.exe" arguments="C:\sites\site01\mysite\manage.py runserver %HTTP_PLATFORM_PORT%" stdoutLogEnabled="true" stdoutLogFile="C:\sites\site01\mysite\logs">
<environmentVariables>
<environmentVariable name="SERVER_PORT" value="%HTTP_PLATFORM_PORT%" />
</environmentVariables>
</httpPlatform>
<handlers>
<add name="MyPyHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" />
</handlers>
</system.webServer>
<appSettings>
<add key="PYTHONPATH" value="C:\sites\site01\mysite" />
<add key="WSGI_HANDLER" value="django.core.wsgi.get_wsgi_application()" />
<add key="DJANGO_SETTINGS_MODULE" value="mysite.settings" />
</appSettings>
</configuration>

Navigate to “http://localhost:8001/polls”:

Option 2 — WFastCGI

I will create a new website called “site02” accessible under “http://localhost:8002”.

Install WFastCGI

pip install wfastcgi

Install CGI module for IIS

PS:

Install-WindowsFeature -name Web-CGI

Close IIS Manager and open it again. Now we should be able to see a new module.

IIS Manger > Server Level > FastCGI > Add Application.

  • Full Path: C:\Python311\python.exe
  • Arguments: C:\Python311\Lib\site-packages\wfastcgi.py

Now we need to add our handler mapping.

IIS Manager >Sites > Site02 > Handler Mappings > Add Module Mapping

  • Request Path: *
  • Module: FastCgiModule
  • Executable: C:\Python311\python.exe|C:\Python311\Lib\site-packages\wfastcgi.py
  • Name: -

Again, “Request Restrictions” and unchecked “Invoked handler only if request is mapped to”.

Click Ok and it will prompt you if you want to create a FastCGI, but we already did it, so click no.

Last step, we need the same appsettings.

IIS Manager > Sites > Site02 > Configuration Editor > appSettings:

Browse to “http://localhost:8002/polls”

So, our “web.config” should look somewhat like this:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="MyPyHandler2" path="*" verb="*" modules="FastCgiModule" scriptProcessor="C:\Python311\python.exe|C:\Python311\Lib\site-packages\wfastcgi.py" resourceType="Unspecified" requireAccess="Script" />
</handlers>
</system.webServer>
<appSettings>
<add key="PYTHONPATH" value="C:\sites\site02\mysite" />
<add key="WSGI_HANDLER" value="django.core.wsgi.get_wsgi_application()" />
<add key="DJANGO_SETTINGS_MODULE" value="mysite.settings" />
</appSettings>
</configuration>

And the FastCGI section stored in “C:\Windows\System32\inetsrv\Config\applicationHost.config” file:

<system.webServer>

<!-- -->


<fastCgi>
<application fullPath="C:\Python311\python.exe" arguments="C:\Python311\Lib\site-packages\wfastcgi.py" monitorChangesTo="" stderrMode="ReturnStdErrIn500" maxInstances="4" idleTimeout="300" activityTimeout="30" requestTimeout="90" instanceMaxRequests="200" signalBeforeTerminateSeconds="0" protocol="NamedPipe" queueLength="1000" flushNamedPipe="false" rapidFailsPerMinute="10" />
</fastCgi>


<!-- -->

</system.webServer>

--

--