Certain modern website features require HTTPS, including Progressive Web Apps (PWA), WebSockets, camera and microphone usage, and geolocation detection. However, the default Django runserver command can only run the web server under HTTP. Is it possible to have HTTPS in your local development environment? Surely, and in this article, I will guide you through how to set it up.
How TLS/SSL certificates work
First of all, let's have a common ground on how the HTTPS work.
HTTPS is what makes websites secure, protecting transferred visitors' data (like passwords or credit card info).
TLS (Transport Layer Security), which replaced SSL (Secure Sockets Layer), is the security tool that works behind the scenes to create the safe connection between a client (like a browser) and a server by encrypting the data exchanged.
They work using a public key (shared via the certificate) and a private key (kept secret by the server) to establish encrypted connections. These keys are often stored in BASE64
-encoded files with extensions like .pem
.
A Certificate Authority (CA) is a trusted organization or entity responsible for issuing and managing digital certificates used in TLS/SSL encryption. Trusted Certificate Authorities verify the identity of website owners and sign their certificates to ensure authenticity.
A trusted Certificate Authority signs the certificate to verify the server’s identity, and clients (like the browsers) validate it against the Certificate Authority’s root certificate to ensure the connection is authentic and secure. The CA root certificates are typically stored in the operating system or the browser and updated regularly with software updates.
Some well-known Certificate Authorities include:
- Let’s Encrypt (free, automated certificates),
- DigiCert,
- GlobalSign,
- Sectigo (formerly Comodo).
For local development, one can use a simple tool mkcert to create locally trusted TLS certificates to secure development environments without needing external Certificate Authorities. Soon, you'll see how to do that.
TLS/SSL certificates are used not only for HTTPS connections but also to secure other communication protocols, including WebSockets (wss://
), FTPS, database connections, email protocols (SMTP, IMAP, POP3), and message brokers (e.g., Redis, RabbitMQ, Kafka).
1. The /etc/hosts file
I usually create local hostnames for my development environments to access them by domains, such as djangotricks.local
instead of localhost
or 127.0.0.1
. This can be done by adding a mapping line to your /etc/hosts
file:
127.0.0.1 djangotricks.local
2. Install mkcert
Let's install mkcert to your development environment. I used Homebrew with the following commands on macOS:
$ brew install mkcert
$ brew install nss # for Firefox
$ mkcert -install
Installation instructions for other operating systems are available at the mkcert GitHub page.
3. Create certificates for your local domain
I made a directory certs
in my Django project, changed the current directory to it, and then ran:
mkcert djangotricks.local
Afterwards, I created .gitignore
file in it with the following content that ignores everything but itself:
*
!.gitignore
Finally, my project's file structure looked like this:
djangotricks/
├── certs/
│ ├── .gitignore
│ ├── djangotricks.local.pem - public certificate
│ ├── djangotricks.local-key.pem - private key
├── manage.py
4. Django settings
I added a few TLS-related settings to my development-environment settings file at djangotricks/settings/dev.py
:
CSRF_TRUSTED_ORIGINS = [
"https://djangotricks.local:8000",
"http://djangotricks.local:8000",
]
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
5.a) Create a custom script for the WSGI server
The next step was to prepare scripts for running the web server under HTTPS. After a few iterations of chatting with Claude AI, I created a file run_https_wsgi_server.py
with the following content:
import os
import ssl
from pathlib import Path
from django.core.servers.basehttp import get_internal_wsgi_application
from wsgiref.simple_server import make_server
from watchfiles import run_process
def run_server():
# Set Django environment
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangotricks.settings.dev")
application = get_internal_wsgi_application()
# Set certificate paths
cert_path = Path(__file__).parent / "certs"
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(
certfile=cert_path / "djangotricks.local.pem",
keyfile=cert_path / "djangotricks.local-key.pem",
)
# Run server with TLS
with make_server("djangotricks.local", 8000, application) as httpd:
httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True)
print("Serving on https://djangotricks.local:8000")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("Server stopped.")
if __name__ == "__main__":
# Watch directories for changes
watched_dirs = [
str(Path(__file__).parent), # Current directory
]
# Start watching and run the server with auto-reload
run_process(
*watched_dirs, # Paths to watch
target=run_server, # Function to run
debounce=1600, # Debounce changes (milliseconds)
recursive=True, # Watch directories recursively
debug=True # Enable debug logging
)
This Python script runs Python's default web server with the Django WSGI under HTTPS, watches the project files for changes, and restarts the server whenever something is updated.
Then I installed the dependency watchfiles
, which is a Rust-powered watcher for file changes:
(venv)$ pip install watchfiles
That allowed me to run the web server script with:
(venv)$ python run_https_wsgi_server.py
5.b) Create a custom script for the ASGI server
Analogically, I created a script for running the web server under HTTPS with ASGI support. It uses uvicorn for the ASGI protocol and watchfiles for automatic reloading:
(venv)$ pip install uvicorn
(venv)$ pip install watchfiles
I named the script run_https_asgi_server.py
and it had the following content:
import os
from pathlib import Path
import uvicorn
if __name__ == "__main__":
# Set Django environment
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangotricks.settings.dev")
# Set certificate paths
project_root = Path(__file__).parent
ssl_keyfile = project_root / "certs" / "djangotricks.local-key.pem"
ssl_certfile = project_root / "certs" / "djangotricks.local.pem"
# Run server with TLS
uvicorn.run(
"djangotricks.asgi:application",
host="djangotricks.local",
port=8000,
ssl_keyfile=str(ssl_keyfile),
ssl_certfile=str(ssl_certfile),
reload=True, # Enable autoreload
reload_dirs=[
str(project_root),
],
reload_includes=["*.py", "*.html", "*.js", "*.css", "*.txt"],
workers=1,
)
Uvicorn's core features include integration of watchfiles
and TLS/SSL support. Developers can define which directories and file types to watch for autoloading.
Lastly, I could run the script with:
(venv)$ python run_https_asgi_server.py
6. Test your server
To test my results, I navigated to https://djangotricks.local:8000
, browsed all the main pages, tried to submit a form, tried an asynchronous view, tried one of the modern JavaScript features, and tried changing some HTML markup to see how the web server automatically restarts for the changes to take effect immediately.
Conclusion
HTTPS is an essential feature of modern web development, and it's absolutely necessary to be able to set it up in development environments.
Tools like mkcert
make it possible to use TLS certificates locally.
Although Django's runserver
doesn't yet support TLS/SSL certificates, you can create custom Python (or Bash) scripts to run the web servers with HTTPS and autoloading. Also, it is quite straightforward to remove the autoloading if you don't need it.
Now, it's time for me to update all my local development environments. For you, too?
Cover picture by Laura Gigch
Top comments (0)