Django Best Practices: Security
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 the production version of your website before deployment. As well as copious notes on security in the official documentation.
This tutorial covers Django security best practices starting with the most important and working our way down the list.
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.
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.
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
The SECRET_KEY is a randomly generated string used for cryptographic signing created whenever the
startproject command is run. It is very important that
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))'
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.
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
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.
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']
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
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 =
- SECURE_HSTS_INCLUDE_SUBDOMAINS =
- SECURE_HSTS_PRELOAD =
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
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 made.
In general, you can't trust your users. Especially when they have the ability to 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.
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
# 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.
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.
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.