Django for the real world: Security, absolute paths and multiple environments/files


You wan't to make your project's flexible enough to run on various environments and add the minimum set of variables to run in the real world.


In switch the DEBUG variable to FALSE. Define the ALLOWED_HOSTS variable to include a host value or list of host values that reflect the domain or domains an application will run as. Be careful of exposing the SECRET_KEY variable as it can lead to security exploits. If you suspect the SECRET_KEY variable has been compromised, change it immediatly. Define the ADMINS and MANAGERS variables with a list of tuple values. Each tuple should include a name & email.

Define all variables associated with directories to use dynamically determined absolute paths using os.path.dirname and os.path.abspath. Then substitute the variables in Django variables that require absolute directories (e.g. STATIC_ROOT, DIRS in TEMPLATES).

If you want to use a single file for multiple environments, read the host name of the server at the top of Based on the host name, define a control variable (e.g. DJANGO_HOST) to load different environment variables based on a server's host name. You can also use configparser in to read configuration variables from files that use a data structure similar to the one used in Microsoft Windows INI files.

If you want to use multiple configuration files based on Django's configuration syntax, create multiple files named differently than the default file. To configure Django to load variables from a file not named, set the OS environment variable DJANGO_SETTINGS_MODULE.

How it works

Switch DEBUG to False

One of the first things that's necessary to launch a Django application into the real world is switch the DEBUG variable to False. I've already mentioned in a couple of previous recipes how Django's behavior changes when switching DEBUG=False to DEBUG=True. All these behavioral changes associated with changing the DEBUG variable have the intent of enhancing a project's security. Table 1 illustrates the default differences between switching DEBUG=False to DEBUG=True.

Table 1 - Django behavior differences between DEBUG=True and DEBUG=False
FunctionalityDEBUG=True behaviorDEBUG=False behavior
Error handling and notification
(See the 'Define administrators for ADMINS and MANAGERS' section in this recipe for more details)
Displays full stack of errors on request pages for quick analysisDisplays default 'vanilla' or custom error pages without any stack details to limit security threats or embarrasments.
Also emails project administrators of errors.
Static resources
(See the Set up static web page resources -- Images, CSS, JavaScript -- on Django templates recipe for more details)
Set up by default on a project's /static/ URL for simplicity.Disables automatic set up to avoid security vulnerabilities and requires consolidation on a separate directory to run static resources on a separate web server.
Host/site qualifier
(See the 'Define ALLOWED_HOSTS' section in this recipe for more details)
Requests for all hosts/sites are accepted for processingIt's necessary to qualify for which hosts/sites a project can handle requests. If a site/host is not qualified, all requests are denied.

As you can see in table 1, the changes enforced by changing DEBUG=True to DEBUG=False are all associated with a more secure Django project intended for the public (i.e. a production environment). You may not like the hassle of adapting and re-testing to these changes, but all these changes are enforced to maintain a heightened level of security on all Django projects that run in the real world.


By default, the ALLOWED_HOSTS variable is present in but it's empty. The purpose of ALLOWED_HOSTS is to validate a request's (HTTP) Host header. Validation is done to prevent rogue users from sending fake HTTP Host headers that can potentially poison caches and password reset emails with links to malicious hosts. Since this issue can only present itself under an uncontrolled user environment (i.e. public/production servers), validation is only done when DEBUG=False.

If you switch to DEBUG=False and ALLOWED_HOSTS is left empty, Django will refuse to serve requests and instead report HTTP 400 bad request pages because it can't validate incoming HTTP Host headers against any allowed value. Listing 1 illustrates a sample definition of ALLOWED_HOSTS.

Listing 1 - Django ALLOWED_HOSTS definition


As you can see in listing 1, the ALLOWED_HOSTS value is a list of strings. In this case we define two host domains, which permits to act as an alias of The leading .(dot) for each domain indicates a sub-domain is also an allowed host domain (e.g. or is valid for

If you wanted to accept a single and fully qualified domain (FQDN) you would define ALLOWED_HOSTS=[''], which would only accept requests with an HTTP host In a similar fashion, if you wanted to accept any HTTP host -- effectively bypassing the verification -- you would define ALLOWED_HOSTS=['*'] which indicates a wildcard.

Be careful with the SECRET_KEY value

SECRET_KEY is another security related variable like ALLOWED_HOSTS. However, unlike ALLOWED_HOSTS, SECRET_KEY is assigned a default value and a very long value at that (e.g. 'oubrz5ado&%+t(qu^fqo_#uhn7*+q*#9b3gje0-yj7^#g#ronn').

The purpose of the SECRET_KEY value is to digitally sign certain data structures that are sensitive to tampering. Specifically, Django uses the SECRET_KEY on things like session identifiers, cookies and password reset tokens, among other things.

The one thing these signed data structures with SECRET_KEY have in common is they're sent to users on the wider Internet and are then sent back to the application to trigger actions on behalf of users. It's in this scenario we enter into a trust issue. Can the data sent back to the application be trusted ? What if a malicious user modifies his cookie to try and hijack another user's data ? This is what digitally signed data prevents.

Before Django sends any of these sensitive data structures to users on the Internet, it signs them with a project's SECRET_KEY. When the data structures come back to fulfill an action, Django re-checks these sensitive data structures against the SECRET_KEY again. If there was any tampering on the data structures, the signature check fails and Django stops the action.

The only remote possibility a rogue user has of successfully pulling an attack of this kind is if the SECRET_KEY is compromised -- since an attacker can potentially create an altered data structure that will match against a project's SECRET_KEY. Therefore you should be careful about exposing your project's SECRET_KEY. If for any reason you suspect your project's SECRET_KEY has been compromised you should replace it immediately -- only a few ephemeral data structures (i.e. sessions, cookies) become invalid with this change, until users re-login again and the new SECRET_KEY is used to re-generate these data structures.

Define administrators for ADMINS and MANAGERS

Once a Django project is made accessible to end users, you'll want some way to receive notifications of important events related to security or other critical factors. Django has two sets of administrative groups defined in ADMINS and MANAGERS. By default, both ADMINS and MANAGERS are assigned an empty tuple. The values assigned to both variables also need to be tuples, where the first value of the tuple is a name and the second part of the tuple is an email. Listing 2 shows a sample definition of ADMINS and MANAGERS.

Listing 2 - Django ADMINS and MANAGERS definitions

ADMINS = (('Webmaster',''),('Administrator',''))


As you can see is listing 2, the ADMINS variable is assigned two tuples with different administrators. Next, you can see the ADMINS value is assigned to the MANAGERS variable. You can of course define different values for MANAGERS using the same syntax as ADMINS, but in this case I just gave both variables the same values for simplicity.

The purpose of having these two administrative groups in is for Django to send email notifications of project events. By default, these events are limited and happen under certain circumstances. After all, you don't want to send administrators 10 email notifications every minute 24/7.

By default, ADMINS are sent email notifications of errors associated with the django.request or packages, if and only if DEBUG=False. This is a pretty narrow criteria, as it's intended to notify only the most serious errors -- for requests and security -- and only for production environments which is when DEBUG=False. For no other events or conditions are the ADMINS notified by email.

By default, MANAGERS are sent email notifications of broken links (i.e. HTTP 404 page requests), if and only if DEBUG=False and the Django middleware django.middleware.common.BrokenLinkEmailsMiddleware is enabled. Because HTTP 404 page requests aren't a serious problem, by default BrokenLinkEmailsMiddleware is disabled. This is an even narrower criteria than for ADMINS, because irrespective of a project being in development (DEBUG=True) or production (DEBUG=False) the BrokenLinkEmailsMiddleware class needs to be added to MIDDLEWARE_CLASSES in for MANAGERS to get notifications. For no other events or conditions are the MANAGERS notified by email.

Now that you know the purpose of ADMINS and MANAGERS, add users and emails as you see fit to your project. Remember that you can always leverage the values in ADMINS and MANAGERS for other custom logic in a Django project (e.g. notify administrators of user sign ups).

Note Modify LOGGING values to stop email notifications of errors to ADMINS

By default, users in ADMINS will start receiving error emails as soon as you switch to DEBUG=False -- this is unlike MANAGERS which will never receive email unless you add the BrokenLinkEmailsMiddleware to MIDDLEWARE_CLASSES.

To stop email notifications of errors to ADMINS even when DEBUG=False you can modify Django's logging settings, see the recipe Set up logging for a Django project for more details. You could also leave ADMINS undefined and no emails would be sent out, but that would leave your project with no ADMINS definition that you might use for other purposes.

Use dynamic absolute paths

There are some Django variables in that rely on directory locations, such is the case for STATIC_ROOT which defines a consolidation directory for a project's static files or the DIRS list of the TEMPLATES variable which defines the location of a project's templates, among other variables.

The problem with variables that rely on directory locations is that if you run the project on different servers or share it with other users, it can be difficult to keep track or reserve the same directories across a series of environments. To solve this issue you can define variables to dynamically determine the absolute paths of a project. Listing 3 illustrates a Django project directory structure, deployed to the /www/ system directory.

Listing 3 - Django project structure deployed to /www/

                 |                 |
                 |                 +-(Consolidated static resources) 

Typically a Django file would define the values for STATIC_ROOT and TEMPLATE_DIRS as illustrated in listing 4.

Listing 4 - Django with absolute path values

# Other configuration variables omitted for brevity
STATIC_ROOT = '/www/STORE/coffeestatic/'

# Other configuration variables omitted for brevity
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['/www/STORE/coffeehouse/templates/',],

The issue with the setup in listing 4 is if you deploy the application to another server in which you can't deploy the application to /www/ (e.g. due to restrictions or a Windows OS where directories start with a leading letter C:/) it becomes difficult to deploy the application.

An easier approach illustrated in listing 5 is to define variables to dynamically determine the absolute paths of a project.

Listing 5 - Django with dynamically determined absolute path

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

# Other configuration variables omitted for brevity
STATIC_ROOT = '%s/coffeestatic/' % (BASE_DIR)

# Other configuration variables omitted for brevity
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['%s/templates/'% (PROJECT_DIR),],

The variables defined at the top of listing 5 rely on the Python os module to dynamically determine the absolute system path relative to the file.The PROJECT_DIR=os.path.dirname(os.path.abspath(__file__)) statement gets translated into the /www/STORE/coffeehouse/ value, which is the absolute system directory of files like To access the parent of /www/STORE/coffeehouse/ we simply wrap the same statement with another call to os.path.dirname and define the BASE_DIR variable so it gets translated into the /www/STORE/ value.

The remaining statements in listing 5 use standard Python string substitution to use the PROJECT_DIR and BASE_DIR to set the absolute paths in the STATIC_ROOT and TEMPLATE_DIRS variables. In this manner you don't need to hard code the absolute paths for any Django configuration variable, the variables automatically adjust to any absolute directory irrespective of the application deployment directory.

Use multiple environments or configuration files for Django

In every Django project you'll eventually come to the realization that you have to split into multiple environments or files. This will be either because the values in need to change between development and production servers, there are multiple people working on the same project with different requirements (e.g. Windows and Linux) or you need to keep sensitive information (e.g. passwords) in a local file that's not shared with others.

In Django there is no best or standard way to split into multiple environments or files. In fact, there are many techniques and libraries to make a Django project run with a split file. Next, I'll present the three most popular options I've used in my projects. Depending on your needs you may feel more comfortable using one option over another or inclusively mixing two or all three of these techniques to achieve an end solution.

Option 1) Multiple environments in the same file with a control variable

The file is read as an ordinary Python file, so there's no limitation to using Python libraries or conditionals to obtain certain behaviors. This means you can easily introduce a control variable based on a fixed value (e.g. server host name) to conditionally set up certain variable values. For example, changing the DATABASES variable -- because passwords and the database name change between development and production -- changing the EMAIL_BACKEND variable -- since you don't need to send actual emails in development as you do in production -- or changing the CACHES variable -- since you don't need a cache to speed up performance in development as you need in production.

Listing 6 illustrates the setup of a control variable called DJANGO_HOST based on Python's socket module, the variable is then used to load different sets of Django variables based on a server's host name.

Listing 6 - Django with control variable with host name to load different sets of variables.

# Import socket to read host name
import socket
# If the host name starts with 'live', DJANGO_HOST = "production"
if socket.gethostname().startswith('live'):
    DJANGO_HOST = "production"
# Else if host name starts with 'test', set DJANGO_HOST = "test"
elif socket.gethostname().startswith('test'): 
    DJANGO_HOST = "testing"
# If host doesn't match, assume it's a development server, set DJANGO_HOST = "development"
    DJANGO_HOST = "development"

# Define general behavior variables for DJANGO_HOST and all others
if DJANGO_HOST == "production":
    DEBUG = False
    STATIC_URL = ''
    DEBUG = True
    STATIC_URL = '/static/'

# Define DATABASES variable for DJANGO_HOST and all others
if HOST == "production":
   # Use mysql for live host
    'default': {
        'NAME': 'housecoffee',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'coffee',
        'PASSWORD': 'secretpass'
   # Use sqlite for non live host
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'coffee.sqlite3'),

# Define EMAIL_BACKEND variable for HOST
if HOST == "production":
    # Output to SMTP server on HOST production
    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
elif HOST == "testing":
    # Nullify output on HOST test
    EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
    # Output to console for all others
    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

# Define CACHES variable for HOST production and all other hosts 
if HOST == "production":
   # Set cache
   CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': '',
   # No cache for all other hosts

The first line in listing 6 imports the Python socket module to gain access to the host name. Next, a series of conditionals are declared using socket.gethostname() to determine the value of the control variable DJANGO_HOST. If the host name starts with the letters live the DJANGO_HOST variable is set to "production", if the host name starts with test then DJANGO_HOST is set to "testing" and if the host name starts with neither of the previous options then DJANGO_HOST is set to "development".

In this scenario, the string method startswith is used to determine how to set the control variable based on the host name. However, you can just as easily use any other Python library or even criteria (e.g. IP address) to set the control variable. In addition, since the control variable is based on a string, you can introduce as many configuration variations as needed. In this case we use three different variations to set variables -- "production","testing" and "development" -- but you could easily define five or a dozen variations if you require such an amount of different set ups.

Option 2) Multiple environment files using configparser

Another variation to split is to rely on Python's built-in configparser module. configparser allows Django to read configuration variables from files that use a data structure similar to the one used in Microsoft Windows INI files. Listing 7 illustrates a sample configparser file.

Listing 7 - Python configparser sample file production.cfg.

DEBUG: false

NAME: housecoffee
ENGINE: django.db.backends.mysql 
USER: coffee
PASSWORD: secretpass 

SECRET_KEY: %%ea)cjy@v9(7!b(20gl+4-6iur28dy=tc4f$-zbm-v=!t

As you can see in Listing 7, the format for a configparser file is structured in various sections declared between brackets (e.g. [general], [databases]) and below each section are the different keys and values. The variables in Listing 7 represent a production environment placed in a file named production.cfg. I chose the .cfg extension for this file, but you could use the .config or .ini extensions if you like, the extension is irrelevant to Python, the only thing that matters is the data format in the file itself.

Similar to the contents in production.cfg, you can create other files with different variables for other environments (e.g.testing.cfg, development.cfg). Once you have the configparser file or files, then you can import them into a Django Listing 8 shows a sample that uses values from a configparser file.

Listing 8 - Django with configparser import.

# Access configparser to load variable values
from django.utils.six.moves import configparser
config = configparser.SafeConfigParser(allow_no_value=True)

# Import socket to read host name
import socket
# If the host name starts with 'live', load configparser from "production.cfg"
if socket.gethostname().startswith('live'):'%s/production.cfg' % (PROJECT_DIR))
# Else if host name starts with 'test', load configparser from "testing.cfg"
elif socket.gethostname().startswith('test'):'%s/testing.cfg' % (PROJECT_DIR))
# If host doesn't match, assume it's a development server, load configparser from "development.cfg"'%s/development.cfg' % (PROJECT_DIR))

DEBUG = config.get('databases', 'DEBUG')
STATIC_URL = config.get('general', 'STATIC_URL')

    'default': {
        'NAME': config.get('databases', 'NAME'),
        'ENGINE': config.get('databases', 'ENGINE'),
        'USER': config.get('databases', 'USER'),
        'PASSWORD': config.get('databases', 'PASSWORD')

SECRET_KEY = config.get('security', 'SECRET_KEY')

As you can see in Listing 8, configparser is loaded into Django via django.utils.six.moves, which is a utility to allow cross-imports between Python 2 and Python 3. In Python 2 the configparser package is actually named ConfigParser, but this utility allows us to use the same import statement using either Python 2 and Python 3. See the Install Django recipe and side-note 'Django and Python 3' for more details on six and using Django with Python 3. After the import, we use the SafeConfigParser class with the argument allow_no_value=True to allow processing of empty values in configparser keys.

Then we rely on the same prior technique using Python's socket module to gain access to the host name and determine which configparser file to load. The configparser file is loaded using the read method of the SafeConfigParser instance. At this juncture all configparser variables are loaded and ready for access. The remainder of listing 8 shows a series of standard Django variables that are assigned their value using the get method of the SafeConfigParser instance, where the first argument is the configparser section and the second argument is the key variable.

So there you have another option on how to split the variables in into multiple environments. Like I mentioned at the start, there's no best or standard way of doing this. Some people may like configparser better because it splits values into separate files and avoids the many conditionals of option 1, but other people may hate configparser because of the need to deal with the special syntax and separate files. Choose whatever feels best for your project.

Option 3) Multiple files with different names for each environment

Finally, another option to split Django variables into multiple environments is to create multiple files with different names. By default, Django looks for configuration variables in the file in a project's base directory. However, it's possible to tell Django to load a configuration file with a different name. Django uses the OS variable DJANGO_SETTINGS_MODULE for this purpose. By default, Django sets this OS variable to <project_name>.settings in the file located in the base directory of any Django project. And since the file is used to bootstrap Django applications, the DJANGO_SETTINGS_MODULE value in this file guarantees configuration variables are always loaded from the file inside the <project_name> sub-directory.

So let's suppose you create different files for a Django application -- placed in the same directory as -- named, and You have two options to load these different files. One option is to change the DJANGO_SETTINGS_MODULE definition in a project's file to the file with the desired configuration (e.g. os.environ.setdefault("DJANGO_SETTINGS_MODULE", "coffeehouse.production" to load the configuration file). However, hard coding this value is inflexible because you would need to constantly change the value in based on the desired configuration. Here you could use a control variable in to dynamically determine the DJANGO_SETTINGS_MODULE value based on a host name -- similar to the process described in the previous option 1 for

Yet another possibility to set DJANGO_SETTINGS_MODULE without altering is to define DJANGO_SETTINGS_MODULE at the operating system level so it overrides the definition in

Listing 9 illustrates how to set the DJANGO_SETTINGS_MODULE variable on a Linux/Unix OS so that application variables in the file are used instead of the file.

Listing 9 - Override DJANGO_SETTINGS_MODULE to load application variables from a file called and not the default

$ export DJANGO_SETTINGS_MODULE=coffeehouse.load_testing
$ python runserver
Validating models...

0 errors found
Django version 1.10, using settings 'coffeehouse.load_testing'
Development server is running at
Quit the server with CONTROL-C.

In listing 9 we use the standard Linux/Unix syntax export variable_name=variable_value to set an environment variable. Once this is done, notice the Django application started with the development server displays the start-up message "using settings 'coffeehouse.testing'".

If you plan to override the DJANGO_SETTINGS_MODULE at the OS level to load different Django application variables, be aware that by default OS variables aren't permanent or inherited. This means you may need to define the DJANGO_SETTINGS_MODULE for every shell from which you start Django and also define it as a local variable for run-time environments (e.g. Apache).