Tutorials  /  Security

How To Harden the Security of Your Production Django Project

Ccentron Redaktion · December 2024 ·12 min read ·Security, Tutorial

Developing a Django application can be a convenient experience because it’s structured to be flexible and scalable. This premise extends to Django’s security-oriented settings that can help you prepare your project for production. But there are several ways to further secure your project.

Breaking Up Your Settings

Breaking up your settings will allow you to set up different configurations based on the environment. Leveraging .env for setting environment variables or hiding confidential settings will ensure you don’t release any details that may compromise your project. And changing default URLs and other settings will help you avoid common security vulnerabilities.

While implementing these strategies might seem time-consuming at first, developing a practical workflow will allow you to deploy releases of your project without compromising on security or your productivity.

SEC

Matching infrastructure at centron

Hardening does not stop at the server: cloud firewalls filter traffic before it reaches the VM – centrally managed, no per-rule surcharge. Explore cloud firewalls →

Security-Oriented Workflow for Django

In this tutorial, you will leverage a security-oriented workflow for your Django project by implementing and configuring environment-based settings, .env, and Django’s built-in security settings. These features all complement each other and will result in a version of your Django project that is ready for different approaches you may take to deployment.

Prerequisites

  • A pre-existing Django project. If you don’t already have one set up, you can use our How To Install Django and Set Up a Development Environment tutorial for setup. In that tutorial, you’ll use the testsite project from this tutorial as an example.
  • For your Django project, you’ll also need Python 3 installed. You can install it by following Step 1 of our tutorial, How To Install Python 3 and Set Up a Programming Environment on an Ubuntu 20.04 Server.
  • A Let’s Encrypt certificate. If you don’t already have one set up, you can use our How To Secure Nginx with Let’s Encrypt on Ubuntu 20.04 tutorial for setup.
  • To use the Let’s Encrypt certificate, you’ll need Nginx installed. You can install it by following our tutorial How To Install Nginx on Ubuntu 20.04.
  • This Django Development tutorial series is a great way to get familiar with Django’s file structure and its core settings.

Note: If you’re using an existing Django project, you may have different requirements. This tutorial suggests a particular project structure; however, you can also use each of the sections of this tutorial individually as needed.

Step 1 — Restructuring Django’s Settings

Before you get into the nit and grit of securing your Django project, you’ll need to go into your project’s directory and activate your virtual environment:

Console
cd django-apps
. env/bin/activate

In this first step, you’ll start by rearranging your settings.py file into environment-specific configurations...

This is a good practice when you need to move a project between different environments, for example, development and production. This arrangement will mean less reconfiguration for different environments; instead, you’ll use an environment variable to switch between configurations, which will be discussed later in the tutorial.

Create a new directory called settings in your project’s subdirectory:

Console
mkdir testsite/testsite/settings

(As per the prerequisites, this tutorial uses testsite, but you can substitute your project’s name in here.)

This directory will replace your current settings.py configuration file; all of your environment-based settings will be in separate files contained in this folder.

Creating Separate Settings Files

In your new settings folder, create three Python files:

Console
cd testsite/testsite/settings
touch base.py development.py production.py

The development.py file will contain settings you’ll normally use during development. And production.py will contain settings for use on a production server. You should keep these separate because the production configuration will use settings that will not work in a development environment; for example, forcing the use of HTTPS, adding headers, and using a production database.

The base.py settings file will contain settings that development.py and production.py will inherit from. This is to reduce redundancy and to help keep your code cleaner. These Python files will be replacing settings.py, so you’ll now remove settings.py to avoid confusing Django.

Renaming Settings

While still in your settings directory, rename settings.py to base.py with the following command:

Code
mv ../settings.py base.py

You’ve just completed the outline of your new environment-based settings directory. Your project won’t understand your new configuration yet, so next, you’ll fix this.

Step 2 — Using django-environ

Currently, Django will not recognize your new settings directory or its internal files. So, before you continue working with your environment-based settings, you need to make Django work with django-environ. This is a dependency that loads environment variables from a .env file. This means that Django will look inside a .env file in your project’s root directory to determine which settings configuration it will use.

Project Root Directory

Go to your project’s root directory and then use the ls command to list the directory’s content:

Console
cd ../../
ls

The files in your project’s root directory should look like this:

db.sqlite3  manage.py  testsite

Installing django-environ

Install django-environ:

Console
pip install django-environ

Now you need to configure Django to use .env. You’ll edit two files to do this: manage.py, for development, and wsgi.py, for production.

Editing manage.py

Start by opening manage.py for editing using nano or your preferred text editor:

Code
nano manage.py

Add the following highlighted code:

Python
import os
import sys
import environ

environ.Env.read_env()

def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings')

    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)

if __name__ == '__main__':
    main()

Save and close manage.py by pressing CTRL+X, pressing Y to save, and then pressing ENTER.

Next open wsgi.py for editing:

Editing wsgi.py

Next, open wsgi.py for editing:

Code
nano testsite/wsgi.py

Add the following highlighted lines:

Python
import os
import environ

environ.Env.read_env()

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings')

application = get_wsgi_application

Save and close the file by pressing CTRL+X, pressing Y to save, and then pressing ENTER.

The code you’ve added to both of these files does two things. First, whenever Django runs—manage.py for running development, wsgi.py for production—you’re telling it to look for your .env file. If the file exists, you instruct Django to use the settings file that .env recommends; otherwise, you use the development configuration by default.

Creating a .env File

Finally, you’ll create a .env file in the current directory:

Code
nano .env

Now add in the following line to set the environment to development:

Code
DJANGO_SETTINGS_MODULE="testsite.settings.development"

Save and close the file by pressing CTRL+X, pressing Y to save, and then pressing ENTER.

Note: Add .env to your .gitignore file, so it is never included in your commits; you’ll use this file to contain data such as passwords and API keys that you do not want visible publicly. Every environment your project is running on will have its own .env with settings for that specific environment.

It is recommended to create a .env.example to include in your project, so you can easily create a new .env wherever you need one.

Step 3 — Creating Development and Production Settings

Next, you’ll open your base.py and add the configuration you want to modify for each environment in the separate development.py and production.py files. The production.py will need to use your production database credentials, so ensure you have those available.

Note: It is up to you to determine which settings you need to configure, based on environment. This tutorial will only cover a common example for production and development settings (that is, security settings and separate database configurations).

Configuring Development Settings

Begin by opening development.py:

Code
nano testsite/settings/development.py

Then add the following code:

Python
import os
from .base import *

DEBUG = True

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

Save and close the file by pressing CTRL+X, pressing Y to save, and then pressing ENTER.

First, you will import from base.py—this file inherits settings from base.py. Then you’ll transfer the settings you want to modify for the development environment. In this case, the settings specific to development are as follows: DEBUG, which you need to be True in development, but not in production; and DATABASES, a local database instead of a production database. You’re using an SQLite database here for development.

Configuring Production Settings

Next, you’ll add to production.py. Open the file with the following command:

Code
nano testsite/settings/production.py

Then add the following code. production.py will be similar to development.py, but with a different database configuration and DEBUG set to False:

Python
import os
from .base import *
import environ

env = environ.Env()
environ.Env.read_env()

DEBUG = False

ALLOWED_HOSTS = []

DATABASES = {
    'default': {
        'ENGINE': env('SQL_ENGINE', default='django.db.backends.sqlite3'),
        'NAME': env('SQL_DATABASE', default=os.path.join(BASE_DIR, 'db.sqlite3')),
        'USER': env('SQL_USER', default='user'),
        'PASSWORD': env('SQL_PASSWORD', default='password'),
        'HOST': env('SQL_HOST', default='localhost'),
        'PORT': env('SQL_PORT', default=''),
    }
}

Save and close the file by pressing CTRL+X, pressing Y to save, and then pressing ENTER.

The setup continues with implementing security settings and leveraging django-environ for secrets. Let me know if you'd like to include the remaining content!

Step 4 — Working with Django’s Security Settings

Django includes security settings ready for you to add to your project. In this step, you’ll add security settings to your project that are considered essential for any production project. These settings are intended for use when your project is available to the public. It’s not recommended to use any of these settings in your development environment; hence, in this step, you’re limiting these settings to the production.py configuration.

Adding Security Settings

First, open production.py:

Code
nano testsite/settings/production.py

In your file, add the highlighted settings that work for your project:

Python
import os
from .base import *
import environ

env = environ.Env()
environ.Env.read_env()

DEBUG = False

ALLOWED_HOSTS = ['your_domain', 'www.your_domain']

DATABASES = {
    'default': {
        'ENGINE': env('SQL_ENGINE', default='django.db.backends.sqlite3'),
        'NAME': env('SQL_DATABASE', default=os.path.join(BASE_DIR, 'db.sqlite3')),
        'USER': env('SQL_USER', default='user'),
        'PASSWORD': env('SQL_PASSWORD', default='password'),
        'HOST': env('SQL_HOST', default='localhost'),
        'PORT': env('SQL_PORT', default=''),
    }
}

SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True

ALLOWED_HOSTS is a list of strings that represent the host/domain names that your project can serve. This is a security measure to prevent an attacker from poisoning caches and DNS.

SECURE_SSL_REDIRECT redirects all HTTP requests to HTTPS (unless exempt). This means your project will always try to use an encrypted connection. You will need to have SSL configured on your server for this to work.

SESSION_COOKIE_SECURE tells the browser that cookies can only be handled over HTTPS. This means cookies your project produces for activities, such as logins, will only work over an encrypted connection.

CSRF_COOKIE_SECURE is the same as SESSION_COOKIE_SECURE but applies to your CSRF token. CSRF tokens protect against cross-site request forgery.

SECURE_BROWSER_XSS_FILTER sets the X-XSS-Protection header on all responses, preventing injected scripts from executing.

Adding HSTS Support

The following settings support HTTP Strict Transport Security (HSTS):

Code
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True

SECURE_HSTS_SECONDS specifies how long HSTS is enforced. SECURE_HSTS_PRELOAD allows your site to be preloaded in browsers, and SECURE_HSTS_INCLUDE_SUBDOMAINS applies HSTS to all subdomains.

Warning: Improperly configuring HSTS can break your site. Consult Django’s documentation before implementing these settings.

Step 5 — Using django-environ for Secrets

Finally, you’ll leverage django-environ to hide sensitive information, such as your project’s SECRET_KEY or admin login URL. This is especially useful if you’re publishing your code on platforms like GitHub.

Hiding the SECRET_KEY

Open your .env file in the root directory of your project:

Code
nano .env

Add the following line:

Code
SECRET_KEY="your_secret_key"

Updating base.py

Update base.py to use the hidden SECRET_KEY:

Python
import environ

env = environ.Env()
environ.Env.read_env()

SECRET_KEY = env('SECRET_KEY')

Hiding the Admin URL

Add a secret admin URL to your .env file:

Code
SECRET_ADMIN_URL="very_secret_url"

Then update urls.py to use this hidden URL:

Python
from django.urls import path
import environ

env = environ.Env()
environ.Env.read_env()

urlpatterns = [
    path(env('SECRET_ADMIN_URL') + '/admin/', admin.site.urls),
]

Conclusion od How To Harden the Security of Your Production Django Project

In this tutorial, you configured your Django project for secure production use by leveraging environment-based settings, django-environ, and Django’s built-in security features. You now have:

  • SSL/HTTPS enforced for all communications
  • Protection against XSS and CSRF attacks
  • Concealed project secrets
  • A hidden admin URL
  • Separate settings for development and production

For more information, explore Django’s official settings documentation and related tutorials.

Jetzt 200 € Guthaben sichern

Testen Sie Ihr Setup auf ccloud³

Registrieren Sie sich in der ccloud³ und erhalten Sie 200 € Startguthaben für Ihr Projekt – z. B. für eine PostgreSQL-VM mit automatischen Backups.

centron Redaktion Technische Redaktion

Das Redaktionsteam von centron schreibt Anleitungen aus dem Betriebsalltag: getestet auf unserer eigenen Plattform, betrieben im Rechenzentrum in Hallstadt bei Bamberg.

Kategorie Security
Teilen
Noch offene Fragen?

Our team will help you with your specific setup - in German or English, by people who run the platform themselves.

War dieses Tutorial hilfreich?

Your answer is stored anonymously and helps us improve our tutorials.

Kommentare

No comments yet - be the first to ask a question about this tutorial.

Sign in to comment

Comments are open to centron customers. Sign in to your account to ask a question about this tutorial.

Weiterlesen

Das könnte Sie auch interessieren

Jetzt kostenlos anfangen

Melden Sie sich an und erhalten Sie in den ersten 60 Tagen ein Guthaben von 200 € bei centron.

Dieses Werbeangebot gilt nur für neue Konten. Angebot ausschließlich für Gewerbetreibende.

Jetzt loslegen Sales kontaktieren