Introduction

The previous part covered testing and static files — foundations for reliable development. This part addresses security configuration and database considerations: the decisions that determine whether your application survives contact with the real world.

Security mistakes lead to breaches. Database misconfigurations cause outages. Understanding these topics before deployment prevents painful lessons learned in production.

Security Configuration

Django includes security features, but many require configuration. Default settings prioritize development convenience over production security — you can see detailed error pages, you don’t need HTTPS, cookies work over any connection. Before deploying, you must change these defaults.

The Deployment Checklist

Django provides a built-in security checker:

python manage.py check --deploy

This command reports common security misconfigurations. Run it before every deployment and address every warning. The checker catches issues like DEBUG = True in production, missing HTTPS settings, and insecure cookie configurations.

Don’t ignore warnings. Each represents a real vulnerability class that attackers know to exploit.

Essential Settings

Your production settings.py should include:

# Never expose in production
DEBUG = False

# Only your domain(s)
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']

# Protect session cookies
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# Prevent clickjacking
X_FRAME_OPTIONS = 'DENY'

# HTTPS enforcement
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000  # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

Each setting addresses a specific vulnerability. Understanding them helps you make informed decisions.

DEBUG = False prevents detailed error pages from exposing your code structure to attackers. In development, seeing variable values and code snippets helps debugging. In production, this information helps attackers. With DEBUG = False, users see a generic error page while details go to your logs.

ALLOWED_HOSTS prevents HTTP Host header attacks. Without this restriction, attackers could manipulate links in password reset emails by sending requests with malicious Host headers. The setting specifies which domains your application legitimately serves.

SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE ensure cookies transmit only over HTTPS. Without these settings, someone on the same network (a coffee shop, an airport) could intercept session tokens and impersonate users.

SECURE_SSL_REDIRECT automatically redirects HTTP requests to HTTPS. Users who type http:// get redirected to https:// before any sensitive data transmits.

HSTS (HTTP Strict Transport Security) tells browsers to always use HTTPS, even if users type http:// or click an http:// link. After the first HTTPS visit, browsers remember and upgrade future requests automatically. The SECURE_HSTS_SECONDS value determines how long browsers remember — 31536000 seconds is one year.

Secret Key Management

The SECRET_KEY setting is critical. Django uses it for cryptographic signing — sessions, password reset tokens, CSRF protection, and more. If an attacker obtains your secret key, they can forge session cookies and impersonate any user, including administrators.

Never commit the secret key to version control. If it’s in your Git history, it’s compromised — even if you delete it later, anyone with repository access can find it.

Use environment variables instead:

import os
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')

Generate a strong key for production:

python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

Each deployment environment should have its own unique key. Your development key, staging key, and production key should all differ. If one leaks, the others remain secure.

For development, you can keep a default in settings:

SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'dev-key-not-for-production')

But ensure production always sets the environment variable — the default should never run in production.

Database Credentials

Similarly, database credentials don’t belong in code:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'HOST': os.environ.get('DB_HOST'),
        'PORT': os.environ.get('DB_PORT', '5432'),
    }
}

Tools like python-decouple or django-environ provide cleaner syntax for environment variables, including type conversion and default values. The principle remains: secrets stay out of your repository.

Separating Settings

Managing different settings for development and production can become messy. A common pattern uses multiple settings files:

myproject/
├── settings/
│   ├── __init__.py
│   ├── base.py
│   ├── development.py
│   └── production.py

base.py contains settings common to all environments. development.py and production.py import from base and override what differs:

# production.py
from .base import *

DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com']
# ... other production settings

Select the settings module via environment variable:

export DJANGO_SETTINGS_MODULE=myproject.settings.production

This keeps production secrets out of your repository while maintaining a single source of truth for shared configuration.

Database Considerations

SQLite works fine for development and small deployments — it requires no setup and stores everything in a single file. But production applications typically need PostgreSQL or MySQL. Understanding when and how to switch prevents problems.

Why PostgreSQL

PostgreSQL is the recommended database for Django. The reasons are practical:

Full feature support. Some Django features — ArrayField, JSONField, full-text search — work only with PostgreSQL. If you start with SQLite and later need these features, you’ll migrate anyway.

Concurrency handling. SQLite locks the entire database for writes. With multiple users, this creates bottlenecks. PostgreSQL handles concurrent connections efficiently.

Data integrity. PostgreSQL enforces constraints strictly. If you define a field as NOT NULL, PostgreSQL enforces it. SQLite historically was more permissive, potentially allowing invalid data.

Ecosystem. Backup tools, monitoring solutions, replication options — PostgreSQL has mature tooling for production operations.

MySQL works too and some organizations prefer it for existing expertise or infrastructure. But if you’re starting fresh, PostgreSQL is the safer choice.

Switching Databases

Install the database adapter:

pip install psycopg2-binary  # for PostgreSQL

The -binary package includes compiled dependencies; psycopg2 (without -binary) requires a C compiler. For development, -binary is simpler. For production, some prefer compiling from source.

Update settings:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydatabase',
        'USER': 'myuser',
        'PASSWORD': 'mypassword',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

Run migrations to create tables in the new database:

python manage.py migrate

Migrations create the schema. They don’t transfer data. If you have existing data in SQLite that needs to transfer, use Django’s dumpdata and loaddata:

# Before switching
python manage.py dumpdata > backup.json

# After switching and migrating
python manage.py loaddata backup.json

Complex datasets may need manual handling — particularly if you’re changing field types or relationships during the switch.

Connection Pooling

Database connections are expensive to create. Each request that needs the database opens a connection, uses it, and closes it. Under load, this connection churn becomes a bottleneck.

Connection pooling reuses connections across requests. Instead of creating and destroying connections, the pool maintains a set of open connections that requests borrow and return.

Django doesn’t include pooling by default. Options include:

django-db-connection-pool: Adds pooling directly to Django’s database backend.

PgBouncer: A separate service that sits between Django and PostgreSQL, pooling connections externally.

For small to medium applications, Django’s default connection handling works fine. When you see connection-related slowdowns or errors under load, pooling becomes necessary.

What Comes Next

This part covered security and database configuration — decisions that protect your application and its data. With these foundations in place, you’re ready for deployment.

The next and final part will address deployment options (PaaS, VPS, containers), WSGI and ASGI servers, logging, monitoring, and performance optimization.

Conclusion - Staff Note

Security and database configuration aren’t glamorous topics. They don’t produce visible features. But they determine whether your application survives contact with reality.

Take security settings seriously. Run check --deploy before every deployment. Keep secrets out of version control. Use HTTPS everywhere.

Choose your database based on real needs, not hypothetical scale. SQLite handles more than people assume. PostgreSQL handles most of everything else. Switch when you have evidence you need to, not before.


The Frontek.dev Team