ES
Inicia sesión Crea tu cuenta

Los errores más comunes al implementar Django en producción y cómo evitarlos

Bad Request (400), el admin sin estilos, fallo en la verificación CSRF? Aquí tienes 10 errores comunes al implementar Django en producción, cómo identificarlos y solucionarlos, más una lista de verificación.

Grzegorz Kalmus ·

La aplicación Django funciona perfectamente en tu portátil, pero al subirla al servidor muestra "Bad Request (400)", el panel de administración sin estilos o una página en blanco con error 500. No es mala suerte, sino algunas trampas comunes en las que casi todos caen en su primer despliegue. El entorno de desarrollo de Django es intencionadamente indulgente: muestra errores, sirve archivos estáticos por sí mismo y no pregunta por el dominio. La producción no perdona ninguno de estos detalles.

En esta guía recopilamos los 10 errores más frecuentes al desplegar Django en producción. Para cada uno explicamos cómo se manifiesta, de dónde viene y cómo solucionarlo. Al final encontrarás una breve lista de verificación para revisar antes de cada despliegue, junto con respuestas a las preguntas más comunes.

Índice

  1. ¿Por qué Django funciona localmente, pero no en el servidor?
  2. Error 1: ¿Es un gran problema tener DEBUG = True en producción?
  3. Error 2: ¿Dónde guardar SECRET_KEY y las contraseñas?
  4. Error 3: ¿De dónde viene el “Bad Request (400)”?
  5. Error 4: ¿Por qué aparece “CSRF verification failed” detrás de un proxy?
  6. Error 5: ¿Por qué el panel de administración no tiene estilos?
  7. Error 6: ¿Se puede dejar runserver activo?
  8. Error 7: ¿Qué pasa si olvidas las migraciones?
  9. Error 8: ¿SQLite es suficiente para producción?
  10. Error 9: ¿Por qué pip install falla el despliegue?
  11. Error 10: ¿Cómo saber si la aplicación ha caído?
  12. Lista de comprobación antes del despliegue
  13. ¿Cómo es en Saplo?
  14. Preguntas frecuentes

¿Por qué Django funciona localmente pero no en el servidor?

El comando python manage.py runserver inicia un servidor de desarrollo que hace muchas cosas automáticamente para el programador. Sirve archivos CSS y JavaScript, muestra una página clara con la traza de errores, acepta peticiones en localhost y no se preocupa por HTTPS.

En producción ese confort desaparece. La aplicación está detrás de un servidor web o proxy inverso, funciona bajo un dominio real, el tráfico es cifrado, y cada elemento que "funcionaba solo" localmente debe configurarse explícitamente. La mayoría de los errores descritos a continuación surgen precisamente de esta diferencia entre ambos entornos.

Antes de continuar, memoriza un comando. Django tiene una auditoría incorporada para la configuración de producción:

python manage.py check --deploy

Muestra advertencias sobre DEBUG, la clave secreta, cookies, HTTPS y cabeceras de seguridad. Ejecutarlo antes del primer despliegue te salvará de la mitad de los problemas listados aquí.

Error 1: ¿Es un gran problema tener DEBUG = True en producción?

Sí, lo es. Con DEBUG activado, cada error 500 muestra al visitante una página diagnóstica completa: fragmentos de código, variables locales, rutas en el servidor, lista de configuraciones y consultas SQL. Django oculta algunas de las variables sensibles, pero no todas, y la estructura misma de la aplicación es un mapa valioso para un atacante. Además, en modo DEBUG se registra cada consulta a la base de datos, por lo que con el tiempo la memoria del proceso crece.

Cómo solucionarlo: configura DEBUG mediante una variable de entorno, con un valor predeterminado seguro.

import os
DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"

Localmente configuras DJANGO_DEBUG=1, y en el servidor no configuras nada. Solo recuerda que al desactivar DEBUG, comienzan a aplicarse ALLOWED_HOSTS y Django deja de servir archivos estáticos. Por eso los errores 3 y 5 suelen aparecer justo después de corregir esto.

Error 2: ¿Dónde guardar SECRET_KEY y las contraseñas?

No en el repositorio. SECRET_KEY firma las sesiones, los tokens de restablecimiento de contraseña y las cookies CSRF. Quien lo conozca puede falsificar la sesión de un usuario autenticado. La clave generada por startproject suele quedar en el primer commit y permanece para siempre en el historial de git, incluso después de eliminarla del archivo.

Lo mismo aplica para la contraseña de la base de datos, las claves API de pagos o correo.

Cómo solucionarlo: lee todos los secretos desde variables de entorno, dejando solo los nombres en el repositorio.

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]

La ausencia de la variable provocará un error al iniciar, y eso está bien: es mejor que la aplicación no se inicie a que lo haga con una clave predeterminada. Si la clave ya se ha filtrado en un repositorio público, genera una nueva. Todos los usuarios serán desconectados, pero la clave antigua dejará de ser un riesgo.

Error 3: ¿De dónde viene el "Bad Request (400)"?

Casi siempre proviene de un ALLOWED_HOSTS vacío o incompleto. Con DEBUG desactivado, Django rechaza cualquier solicitud cuyo encabezado Host no esté en esa lista. Los descuidos típicos incluyen la falta de versión con www, la ausencia del subdominio temporal bajo el que la aplicación funcionaba antes de vincular el dominio, o un error tipográfico.

Cómo solucionarlo: escribe todos los dominios desde los que la aplicación debe responder.

ALLOWED_HOSTS = ["tuempresa.es", "www.tuempresa.es"]

No recurras a poner ["*"]. Esto desactiva la protección contra ataques al encabezado Host, como por ejemplo la sustitución de un dominio falso en los enlaces para restablecer la contraseña.

Error 4: ¿Por qué aparece "CSRF verification failed" detrás de un proxy?

En producción, Django rara vez recibe el tráfico directamente. Delante de él hay un reverse proxy que acepta HTTPS y pasa a la aplicación HTTP sin cifrar. Por eso Django ve la petición como no cifrada, aunque el navegador envíe el formulario desde la dirección https://. Desde la versión 4.0 Django compara el encabezado Origin con una lista de orígenes confiables y, si no está en ella, rechaza los formularios, incluido el inicio de sesión en el panel de administración.

Otra variante del mismo problema: activas SECURE_SSL_REDIRECT = True y la página entra en un bucle infinito de redirecciones porque Django sigue pensando que la petición llegó por HTTP.

Cómo solucionarlo: indícale a Django qué encabezado usa el proxy para informar de HTTPS, y añade los orígenes confiables.

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
CSRF_TRUSTED_ORIGINS = ["https://tuempresa.es", "https://www.tuempresa.es"]
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

La configuración SECURE_PROXY_SSL_HEADER es segura solo si el proxy siempre establece ese encabezado por sí mismo y no deja pasar valores desde el cliente.

Error 5: ¿Por qué el panel de administración no tiene estilos?

Cuando se desactiva DEBUG, Django no sirve los archivos estáticos. El panel /admin/ funciona, pero se ve como una página de los años noventa, y los CSS personalizados y las imágenes devuelven error 404. Además, está el problema olvidado de collectstatic: los archivos están en los directorios de la aplicación, pero nadie los ha reunido en un solo lugar.

Cómo arreglarlo: configura STATIC_ROOT, recoge los archivos en cada despliegue y sírvelos mediante el servidor web o la biblioteca WhiteNoise.

STATIC_ROOT = BASE_DIR / "staticfiles"
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # reszta middleware
]
python manage.py collectstatic --noinput

WhiteNoise solo maneja archivos estáticos desde el código. Los archivos subidos por los usuarios (MEDIA_ROOT, por ejemplo, fotos de productos) deben servir por separado, a través del servidor web o un almacenamiento externo. Este suele ser el segundo problema común justo después del primero.

Error 6: ¿Se puede dejar runserver?

No. La documentación de Django indica claramente que el servidor de desarrollo no ha pasado auditorías de seguridad ni pruebas de rendimiento. Atiende las solicitudes de una forma no adecuada para el tráfico, no tiene una gestión sensata de los procesos y si se cae, nadie lo reinicia.

Cómo solucionarlo: ejecuta la aplicación mediante un servidor WSGI, normalmente Gunicorn, bajo el control de un gestor de procesos que lo reanude tras fallos o reinicios del sistema.

gunicorn yourproject.wsgi:application --bind 127.0.0.1:8000 --workers 2

El número de procesos de trabajo depende de la memoria. Cada proceso de Gunicorn es una copia independiente de la aplicación en RAM, por lo que en un servidor pequeño con 1 GB es mejor empezar con dos y monitorizar el consumo, que poner ocho y arriesgarse a que el sistema mate procesos.

Error 7: ¿Qué pasa si olvidas las migraciones?

La nueva versión del código hace referencia a una columna que todavía no existe en la base de datos. La página que usa este modelo devuelve un error 500, y en los registros aparece no such column o relation does not exist. Una variante de este error es un archivo de migración que no llegó al repositorio porque estaba en .gitignore o nadie lo añadió.

Cómo arreglarlo: considera las migraciones como un paso constante en cada implementación, no como una acción manual.

python manage.py makemigrations --check --dry-run
python manage.py migrate --noinput

El primer comando, ejecutado antes del commit, falla si hay cambios en los modelos sin un archivo de migración. El segundo se ejecuta en el servidor antes de iniciar la nueva versión. Antes de una migración que elimine columnas o tablas, haz una copia de la base de datos.

Error 8: ¿Es suficiente SQLite para producción?

Para una aplicación pequeña con pocos usuarios y pocas escrituras, SQLite puede funcionar durante años. Los problemas comienzan con muchas escrituras simultáneas (database is locked) y durante las copias de seguridad: el archivo de la base de datos copiado durante una escritura puede quedar dañado.

El verdadero error no es elegir SQLite, sino mantener la base de datos donde se pierde tras el despliegue. Un archivo db.sqlite3 en un directorio sobrescrito en cada deploy o en un contenedor sin volumen persistente significa que cada nuevo despliegue comienza con una base de datos vacía.

Cómo solucionarlo: si la aplicación va a crecer, cambia a PostgreSQL y especifica la dirección de la base de datos en la variable DATABASE_URL. Independientemente del motor de base de datos, asegúrate de que los datos estén en un lugar permanente y que formen parte de una copia de seguridad.

Error 9: ¿Por qué pip install rompe el despliegue?

Porque el entorno local es diferente al del servidor. Escenarios típicos: el archivo requirements.txt sin versión, por lo que el servidor descarga una biblioteca más reciente y no compatible; paquete instalado manualmente localmente, que no aparece en el archivo (No module named ...); o una versión diferente de Python. Ejemplo real: Django 6.0 requiere Python 3.12 o superior, así que en un servidor con Python 3.11, el comando pip install django sin restricción de versión terminará en fallo o en una instalación que no funcionará.

Cómo arreglarlo: fija versiones y verifica qué Python está funcionando en el servidor.

pip freeze > requirements.txt
python --version

Si el servidor tiene Python 3.11, en el archivo de dependencias pon Django>=5.2,<6. La rama 5.2 tiene soporte prolongado, así que es una opción sensata para producción.

Error 10: ¿Cómo saber si la aplicación ha fallado?

El peor escenario es cuando el cliente es quien informa sobre la avería. Con DEBUG desactivado, los errores no se muestran en la página, así que si no se registran en los logs ni se envían por correo, simplemente desaparecen.

Cómo solucionarlo:

  • configura LOGGING para que los errores se envíen a la salida estándar o a un archivo que puedas revisar tras la avería,
  • establece ADMINS y la configuración correcta del correo para que Django envíe un mail con cada error 500,
  • añade una ruta de control sencilla, por ejemplo /health/, que devuelva {"status": "ok"}, y conéctala a un sistema de monitorización de disponibilidad.

Esta ruta de control también es útil durante los despliegues: el sistema de implementación puede verificar si la nueva versión está activa antes de redirigir el tráfico hacia ella.

Lista de verificación antes de la implementación

Antes de lanzar una nueva versión, repasa estos puntos:

Punto Cómo verificar
DEBUG desactivado DJANGO_DEBUG no está configurado en el servidor
Secretos fuera del repositorio git grep SECRET_KEY no muestra valores
Dominios en ALLOWED_HOSTS versión con www y sin, más subdominio temporal
HTTPS detrás de proxy SECURE_PROXY_SSL_HEADER y CSRF_TRUSTED_ORIGINS configurados
Archivos estáticos collectstatic en el proceso de implementación, el admin tiene estilos
Servidor de aplicaciones Gunicorn en vez de runserver
Migraciones makemigrations --check pasa, migrate en proceso de implementación
Base de datos y copias datos en ubicación persistente, copia de seguridad funcional
Dependencias versiones fijadas, compatibles con Python en el servidor
Logs y monitoreo errores llegan a los logs, /health/ responde
Auditoría Django python manage.py check --deploy sin advertencias

¿Cómo es en Saplo?

En Saplo, la mayor parte de esta lista ya está configurada antes de que escribas la primera línea de código. Cuando creas una aplicación Django desde el catálogo, la receta instala Gunicorn y WhiteNoise, configura STATIC_ROOT, registra el dominio en ALLOWED_HOSTS y CSRF_TRUSTED_ORIGINS, añade SECURE_PROXY_SSL_HEADER, realiza las migraciones y collectstatic, y el proceso de la aplicación se ejecuta bajo la supervisión del gestor de procesos, que lo reinicia si falla. El certificado SSL para el dominio está incluido en el precio del plan.

Al desplegar un repositorio propio, el proceso está controlado por el archivo saplo.yaml. Las migraciones se agregan como un hook antes del build, y la recopilación de archivos estáticos como comando de build:

build:
  install: "pip install -r requirements.txt"
  command: "python manage.py collectstatic --noinput"
hooks:
  pre_build: "python manage.py migrate --noinput"

Algunas cosas que vale la pena saber:

  • Python 3.11. El builder usa Python 3.11 del sistema, así que en requirements.txt mantén Django en la rama 5.x (error 9).
  • Secretos. SECRET_KEY o la contraseña de la base de datos se guardan usando el comando saplo env set --secret o en el panel, en la pestaña Variables y secretos. El panel, la API y el asistente AI solo mostrarán asteriscos. Los cambios se aplican desde el siguiente despliegue o reinicio. Más detalles en la documentación de variables y secretos.
  • Un despliegue fallido no tumba la página. Si el build o el chequeo de salud fallan, la versión nueva recibe estatus de error y la versión anterior sigue activa. El chequeo de salud espera respuesta hasta 60 segundos.
  • PostgreSQL se ejecuta como contenedor en el archivo docker-compose.yml de la aplicación, con el puerto expuesto solo en 127.0.0.1 y la dirección en la variable DATABASE_URL.
  • Copias de seguridad. Cada Box tiene una copia diaria cifrada que se almacena en otro centro de datos (7 días atrás en Box S, M y L). A partir de Box L se suman snapshots automáticos para restaurar con un clic, y en todos los planes se puede hacer un snapshot manual antes de una migración arriesgada.
  • Logs se consultan con el comando saplo logs --follow o en el panel, en detalles del despliegue.

El despliegue desde GitHub tras cada git push se explica en la instrucción Git deploy, y los mensajes de error comunes están recogidos en solución de problemas.

¿Qué plan elegir? Para una aplicación Django pequeña basta con Box S (1 GB RAM, 27 € al año). Si necesitas acceso SSH al servidor o la base PostgreSQL en contenedor aparte, un buen punto de partida es Box M (2 GB RAM, SSH, 63 € al año). Box L (6 GB RAM, 136 € al año) añade snapshots automáticos de 7 días. El precio del primer año es igual que en la renovación, y la migración de una aplicación existente desde otro hosting la hacemos gratis. La comparación completa de planes está en el precio.

Si estás empezando con Django, lee primero qué es Django y para qué sirve, y la guía para poner Django en Saplo está en cómo poner Django en Saplo con tu propia aplicación Python.

Preguntas frecuentes

¿Cómo comprobar si mi aplicación Django está lista para producción?

Ejecuta python manage.py check --deploy con la configuración de producción. El comando mostrará alertas relacionadas con DEBUG, la clave, HTTPS, las cookies y los encabezados de seguridad. Luego, repasa la lista de verificación de este artículo, porque la auditoría no revisa las migraciones, las copias de seguridad ni los archivos estáticos.

¿Por qué al desactivar DEBUG la página muestra Bad Request (400)?

Porque a partir de ese momento Django verifica ALLOWED_HOSTS, y el dominio desde el que accedes no está en la lista. Añade todas las variantes de la dirección, también con www y el subdominio temporal, y reinicia la aplicación.

¿Es WhiteNoise suficiente para archivos estáticos en producción?

Para la mayoría de las aplicaciones pequeñas y medianas, sí. WhiteNoise entrega los archivos con compresión y un largo tiempo de almacenamiento en la memoria del navegador. Sin embargo, no gestiona los archivos subidos por los usuarios, que deben ser servidos por separado.

¿Cuánta memoria RAM necesita Django en producción?

Una aplicación sencilla con dos procesos Gunicorn cabe en unos pocos cientos de megabytes, por lo que un servidor con 1 GB de RAM es suficiente para empezar. Se necesita más memoria cuando la base de datos PostgreSQL está en el mismo servidor, hay una cola de tareas en segundo plano o un mayor número de procesos trabajadores.

¿Puedo trasladar una aplicación Django en funcionamiento a Saplo?

Sí. La aplicación se despliega desde un repositorio Git o a través del CLI, y la migración desde otro hosting la realizamos sin coste en cualquier plan. Antes de la migración, es recomendable revisar la lista de control de este artículo, ya que ajustes dependientes del antiguo servidor, como dominios o rutas de archivos, deben adaptarse al nuevo.

  • #Seguridad
  • #Django
  • #Guía
  • #Python
  • #Despliegue