Setting up a secure HTTPS environment while developing Python web applications locally can feel like a daunting task, but it’s essential for testing OAuth callbacks, service‑worker behavior, and any feature that depends on a trusted TLS connection. In this guide we’ll walk through everything you need to spin up a fully functional HTTPS server on your workstation, from generating self‑signed certificates with openssl to configuring popular frameworks such as Flask, Django, and FastAPI. By the end, you’ll have a repeatable workflow that keeps your development cycle fast, safe, and SEO‑friendly.
Why Develop Over HTTPS?
Modern browsers enforce strict security policies: mixed content (HTTP pages loading HTTPS resources) is blocked, Service Workers require a secure context, and many third‑party APIs (Google, Stripe, etc.) reject non‑TLS callbacks. Testing these integrations on plain HTTP can hide bugs that only surface in production. Moreover, search engines reward sites that consistently serve content over HTTPS, so early adoption helps you avoid SEO pitfalls later on.
Prerequisites
- Python 3.8+ installed on your machine.
- A terminal with
openssl(comes with macOS/Linux; Windows users can install Git Bash or use WSL). - One of the following frameworks installed via
pip:- Flask (
pip install flask) - Django (
pip install django) - FastAPI with Uvicorn (
pip install fastapi uvicorn)
- Flask (
Step 1: Generate a Self‑Signed Certificate
A self‑signed certificate is sufficient for local development because you control the trust store on your own machine. Follow these commands to create a private key and a certificate that’s valid for 365 days.
# Create a private key
openssl genrsa -out localdev.key 2048
# Generate a certificate signing request (CSR)
openssl req -new -key localdev.key -out localdev.csr \
-subj "/C=US/ST=California/L=SanFrancisco/O=MyLocalDev/OU=Dev/CN=localhost"
# Sign the certificate (self‑signed)
openssl x509 -req -days 365 -in localdev.csr -signkey localdev.key -out localdev.crt
Place localdev.key and localdev.crt in a secure folder within your project (e.g., certs/) and add that folder to .gitignore to avoid committing private keys.
Step 2: Trust the Certificate on Your Local Machine
Browsers will still flag the certificate as “not trusted” unless you add it to your OS’s trust store. Here’s how to do it on the three major platforms:
macOS
- Open Keychain Access.
- Drag
localdev.crtinto the “System” keychain. - Double‑click the certificate, expand “Trust”, and set “When using this certificate” to Always Trust.
Windows
- Run
mmc→ File → Add/Remove Snap‑in → Certificates → “Computer account”. - Navigate to Trusted Root Certification Authorities → Certificates and import
localdev.crt.
Linux (Ubuntu/Debian)
sudo cp localdev.crt /usr/local/share/ca-certificates/localdev.crt
sudo update-ca-certificates
Step 3: Configure Your Python Framework
Each framework has a slightly different way to enable HTTPS. Below are minimal examples that you can copy‑paste into your project.
Flask
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, secure Flask!"
if __name__ == "__main__":
# Provide the path to the certificate and key
app.run(host="127.0.0.1", port=8443, ssl_context=("certs/localdev.crt", "certs/localdev.key"))
Run the app with python app.py and visit https://localhost:8443. Your browser should show a secure lock icon if the certificate is trusted.
Django
Django’s built‑in development server can serve HTTPS when you pass the --cert and --key options (available from Django 4.2 onward). First, create a manage.py command or use the built‑in one:
# From the project root
python manage.py runserver_plus 0.0.0.0:8443 \
--cert-file certs/localdev.crt \
--key-file certs/localdev.key
If you’re on an older Django version, you can wrap the WSGI app with ssl.wrap_socket manually:
import ssl
from django.core.management import execute_from_command_line
if __name__ == "__main__":
execute_from_command_line()
# After the server starts, you can use a reverse proxy like nginx
For most developers, upgrading to the latest Django release is the simplest path.
FastAPI with Uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Secure FastAPI"}
# Save this as main.py and run:
# uvicorn main:app --host 127.0.0.1 --port 8443 --ssl-keyfile certs/localdev.key --ssl-certfile certs/localdev.crt
Step 4: Automate the Workflow with a Makefile or Scripts
Repeating the same commands can be error‑prone. Create a Makefile (or a simple shell script) that handles certificate generation, trust installation, and server launch.
# Makefile
CERT_DIR=certs
KEY=$(CERT_DIR)/localdev.key
CRT=$(CERT_DIR)/localdev.crt
generate:
\topenssl genrsa -out $(KEY) 2048
\topenssl req -new -key $(KEY) -out $(CERT_DIR)/localdev.csr -subj "/CN=localhost"
\topenssl x509 -req -days 365 -in $(CERT_DIR)/localdev.csr -signkey $(KEY) -out $(CRT)
trust-mac:
\topen -a "Keychain Access" $(CRT)
\t# Manual steps required – see guide above
run-flask:
\tpython flask_app.py
run-django:
\tpython manage.py runserver_plus 8443 --cert-file $(CRT) --key-file $(KEY)
run-fastapi:
\tuvicorn main:app --host 127.0.0.1 --port 8443 --ssl-keyfile $(KEY) --ssl-certfile $(CRT)
Now you can simply run make generate run-flask to spin up a secure Flask server in one line.
Step 5: Testing HTTPS Locally
After your server is up, verify the TLS handshake with tools you already trust:
- Browser: Open https://localhost:8443 and check the lock icon.
- cURL:
curl -v https://localhost:8443should showSSL connection using TLSwithout certificate errors. - OpenSSL:
openssl s_client -connect localhost:8443 -servername localhostdisplays the certificate chain.
Common Pitfalls and How to Fix Them
Browser Still Shows “Not Secure”
- Make sure the certificate’s
CN(Common Name) matches the hostname you’re using (localhostor a custom.localdomain). - Double‑check that the certificate is added to the correct trust store (system vs. user).
- Clear cached HSTS entries: in Chrome, go to
chrome://net-internals/#hstsand delete the domain.
Port Conflicts
HTTPS typically runs on 443. On a development machine that port is often occupied by Docker, Kubernetes, or other services. Use an alternative like 8443 or 3000 and configure your client applications accordingly.
“SSL_ERROR_SYSCALL” Errors
These usually indicate the server closed the connection unexpectedly. Verify that the paths to .crt and .key are correct and that the files have proper read permissions.
Advanced Topics
Using mkcert for Hassle‑Free Certificates
If you prefer a tool that automatically creates trusted certificates, mkcert is a great alternative. Install it, then run:
mkcert -install
mkcert localhost 127.0.0.1 ::1
Leave a Reply