Configuración de producción en Django

Avanzado
Django
Django
Actualizado: 18/04/2026

Settings de producción

# settings/produccion.py
import os
from .base import *

# === SEGURIDAD BÁSICA ===
DEBUG = False
SECRET_KEY = os.environ['SECRET_KEY']  # Obligatorio en producción
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')

# === HTTPS Y COOKIES SEGURAS ===
SECURE_SSL_REDIRECT = True                    # Redirige HTTP → HTTPS
SECURE_HSTS_SECONDS = 31536000               # HSTS: 1 año
SECURE_HSTS_INCLUDE_SUBDOMAINS = True        # Incluir subdominios
SECURE_HSTS_PRELOAD = True                   # Habilitar preload de HSTS
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')  # Para proxies
SESSION_COOKIE_SECURE = True                 # Cookie de sesión solo por HTTPS
CSRF_COOKIE_SECURE = True                    # Cookie CSRF solo por HTTPS
SESSION_COOKIE_HTTPONLY = True               # Cookie no accesible por JS
SESSION_COOKIE_SAMESITE = 'Lax'             # Protección CSRF adicional

# === CABECERAS DE SEGURIDAD ===
SECURE_CONTENT_TYPE_NOSNIFF = True          # X-Content-Type-Options: nosniff
X_FRAME_OPTIONS = 'DENY'                    # X-Frame-Options: DENY
SECURE_BROWSER_XSS_FILTER = True            # Compatibilidad: activa XSS filter

# === BASE DE DATOS ===
import dj_database_url
DATABASES = {
    'default': dj_database_url.config(
        default=os.environ['DATABASE_URL'],
        conn_max_age=600,
        ssl_require=True,
    )
}

# === CACHÉ CON REDIS ===
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': os.environ.get('REDIS_URL', 'redis://localhost:6379/1'),
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        },
        'TIMEOUT': 300,
    }
}

# === ARCHIVOS ESTÁTICOS ===
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

# === EMAIL ===
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.gmail.com')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 587))
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ['EMAIL_HOST_USER']
EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD']
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'noreply@ejemplo.com')
SERVER_EMAIL = os.environ.get('SERVER_EMAIL', 'errors@ejemplo.com')

# === LOGGING ===
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
        'simple': {
            'format': '{levelname} {asctime} {message}',
            'style': '{',
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse',
        },
    },
    'handlers': {
        'file_errores': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': BASE_DIR / 'logs/errores.log',
            'maxBytes': 1024 * 1024 * 10,  # 10 MB
            'backupCount': 5,
            'formatter': 'verbose',
        },
        'file_info': {
            'level': 'INFO',
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': BASE_DIR / 'logs/info.log',
            'when': 'midnight',
            'backupCount': 30,
            'formatter': 'simple',
        },
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
            'filters': ['require_debug_false'],
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file_errores', 'mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
        'django.request': {
            'handlers': ['file_errores'],
            'level': 'WARNING',
            'propagate': False,
        },
        'mi_app': {
            'handlers': ['file_info', 'file_errores'],
            'level': 'INFO',
            'propagate': False,
        },
    },
    'root': {
        'handlers': ['file_errores'],
        'level': 'WARNING',
    },
}

ADMINS = [
    ('Admin', os.environ.get('ADMIN_EMAIL', 'admin@ejemplo.com')),
]

Diagrama conceptual de Configuración de producción en Django

python-decouple para variables de entorno

pip install python-decouple dj-database-url
# settings/produccion.py con python-decouple
from decouple import config, Csv

SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())

DATABASES = {
    'default': dj_database_url.config(default=config('DATABASE_URL'))
}

Verificar la configuración de seguridad

Django incluye un comando para verificar la configuración de seguridad:

python manage.py check --deploy

# Output esperado en producción:
# System check identified no issues (0 silenced).

WhiteNoise para archivos estáticos

pip install whitenoise
# settings/base.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',  # Justo después de SecurityMiddleware
    # ...
]

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

WhiteNoise sirve los archivos estáticos con:

  • Compresión automática (gzip y brotli).
  • Cabeceras Cache-Control correctas con fingerprinting.
  • Sin necesidad de Nginx solo para estáticos.

Verificación final del despliegue

# Verificar configuración
python manage.py check --deploy

# Migraciones pendientes
python manage.py showmigrations | grep '\[ \]'

# Recopilar estáticos
python manage.py collectstatic --dry-run

# Test de carga con gunicorn
gunicorn mi_proyecto.wsgi:application --bind 0.0.0.0:8000 --check-config
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Django

Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Configurar DEBUG=False y ALLOWED_HOSTS correctamente en producción. Activar HTTPS con SECURE_SSL_REDIRECT y HSTS. Configurar archivos estáticos en producción con WhiteNoise. Gestionar SECRET_KEY y variables sensibles con variables de entorno. Configurar logging para producción con rotación de archivos.