Django Best Practices: Security
Updated
Table of Contents
Django is a mature, battle-tested web framework with a well-deserved reputation for security over the past 15+ years. However, the internet remains a dangerous place, and web security is an evolving field. Like most web frameworks, Django defaults to local development settings when a new project is created. The onus is on the developer to effectively manage both local defaults and customized production settings.
Django comes with an excellent deployment checklist that can be run on your website's production version before deployment. The official documentation also contains copious notes on security.
This tutorial covers Django security best practices, starting with the most important and working our way down the list.
Django Version
The number one security recommendation is to always be on the latest version of Django. Django has a new major release every 9 months or so (2.2, 3.0, 3.1, etc.) and a minor release with security/bug fixes almost monthly (3.1.1, 3.1.2, etc.). Take the time to update regularly to the latest version--there is an official guide in the documentation on upgrading to a newer version--run your test suite, and carry on with your Django project.
Note that writing comprehensive tests and implementing CI so that the tests are run on each commit is a topic for another tutorial or full-length book.
Environment Variables
There is a fundamental tension between settings intended for local development and production. Local development prizes speed, robust bug reports, and access to all features. Production requires locking down the website and minimizing access as much as possible. By default, new Django projects created with the startproject
command have local development settings. The developer is responsible for updating them to production settings.
These days, the standard way to switch between local and production settings is with environment variables. Rather than having multiple settings.py
files, a single settings.py
file can be used with variables loaded in depending upon the environment: local or production.
There are multiple ways to implement environment variables and various third-party packages that can help. Personally, I like using environs, which has a Django-specific option that installs a number of additional packages that help with configuration.
DEBUG
Near the top of any settings.py
file is the configuration for DEBUG. By default, it is set to True
, which generates, among other things, a rich and detailed error page that includes a traceback and lots of metadata, including all currently defined Django settings. That is a roadmap to debugging locally but also a guide for any hacker who wants to compromise your website if released in production. Therefore, make sure that in production DEBUG
is set to False
!
SECRET_KEY
The SECRET_KEY is a randomly generated string used for cryptographic signing, which is created whenever the startproject
command is run. It is very important that the SECRET_KEY
actually be kept, well, secret.
All it takes is one Git commit and your SECRET_KEY
is visible to anyone with access to your source code. In practice, most developers develop a working prototype of a website before adding environment variables and production configurations. Therefore, before the first deployment, take the time to generate a new SECRET_KEY
. One approach is to use Python's built-in secrets module.
$ python -c 'import secrets; print(secrets.token_urlsafe(38))'
The parameter token_urlsafe
returns the number of bytes in a URL-safe text string. With Base64 encoding, on average, each byte has 1.3 characters. So, using 38
results in 51 characters in this case. The important thing is that your SECRET_KEY
has at least 50 characters. Each time you run the command, a new value is outputted.
ALLOWED_HOSTS
The ALLOWED_HOSTS setting lists which hosts/domain names your Django site can serve. By default, it is set to the empty list, []
, aka any host or domain has access to your site. This needs to be changed in production to avoid HTTP Header attacks.
For local development localhost:8000
and 127.0.0.1:8000
are commonly used in Django, therefore both should be explicitly added to the setting. And once you have a custom URL for your production location--for example, if you're using Heroku it will contain custom subdomain like a-random-1234.herokuapp.com
. This should be added as well.
A common ALLOWED_HOSTS
setting for a Heroku app is, therefore the following:
# settings.py
ALLOWED_HOSTS = ['a-random-1234.herokuapp.com', 'localhost', '127.0.0.1']
HTTPS/SSL
These days there is no reason not to deploy your entire website behind HTTPS. Otherwise a hacker could sniff authentication credentials, API keys, or really any information transferred between the client and server. There are a number of settings to enable full SSL support in a Django project.
SECURE_SSL_REDIRECT automatically redirects requests over HTTP to HTTPS. It should be set to True
.
HTTP Strict Transport Security (HSTS) is a security policy that lets our server enforce that web browsers should only interact via HTTPS by adding a Strict-Transport-Security header. There are three implicit HSTS configurations in a settings.py
file that all need to be updated for production:
- SECURE_HSTS_SECONDS =
2,592,000
- SECURE_HSTS_INCLUDE_SUBDOMAINS =
True
- SECURE_HSTS_PRELOAD =
True
The SECURE_HSTS_SECONDS
setting is set to 0
by default but the greater the better for security purposes. A good default is to set it to one month, 2,592,000 seconds, instead. SECURE_HSTS_INCLUDE_SUBDOMAINS
forces subdomains to also exclusively use SSL so it should be set to True
in production. And SECURE_HSTS_PRELOAD
only has an effect when there is a non-zero value for SECURE_HSTS_SECONDS
, but since we just set one, it will need to be set to True
.
Media Files
Most Django websites contain static and unchanging files such as CSS, JavaScript, images, and so on. These files are broadly referred to as Static
files. For certain websites, user-uploaded content is a key feature, such as an image-sharing website similar to Instagram. Django refers to user-uploaded content as Media,
and additional precautions must be taken.
In general, you can't trust your users, especially when they can upload something to your website. Therefore, for websites with media files, hosting all static content on a cloud service or CDN, like Amazon's S3, is necessary. The third-party package django-storages is a popular way to configure this for your website and alleviate media security concerns.
Admin
The Django admin app is a powerful built-in feature. However, since every Django project has it located at /admin
by default, it is easy for a hacker to try to force their way into any Django website at this URL. An easy way to secure your admin is to simply change the URL for the admin. In the example below it has been changed to anything-but-admin
# config/urls.py
urlpatterns = [
path('anything-but-admin/', admin.site.urls),
For additional tips on hardening your admin, see the article 10 Tips for Making the Django Admin More Secure.
DJ Checkup
You can run an automated security check on your Django site by visiting the website DJ Checkup. This is based on previous work by Sasha Romijn, who ran the Pony Checkup website for many years.
Conclusion
There are many more security improvements that can be added to a Django project but this hits the highlights. Make sure to run the Django deployment checklist on your production settings to be sure you have made all the requisite changes.
$ python manage.py check --deploy
For further help on security, the book Django for Professionals walks through building out a production-ready website with proper security features, performance tips, Docker usage, and more.