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')),
]

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-Controlcorrectas 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
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.