Cómo implementar una app de Django en un VPS con Gunicorn y nginx
Para implementar una app de Django en un VPS, ejecútala con un servidor de producción, coloca un proxy inverso delante, conecta una base de datos y configura HTTPS. Esta guía usa Ubuntu 24.04 LTS, Django 5.2, PostgreSQL, Gunicorn, systemd y nginx. Adapta las rutas y el nombre del módulo a tu proyecto.
Necesitas un VPS con acceso SSH y sudo, un dominio apuntado a él, un repositorio de Django funcional y un bloqueo de dependencias o requirements.txt fijado. El ejemplo usa example.com, /srv/myapp y myproject.wsgi:application como marcadores de posición. Cubre una aplicación WSGI; usa una configuración ASGI si tu app la requiere. Referencias verificadas el 9 de septiembre de 2026.
1. Preparar el servidor y el usuario de la aplicación
Instala los paquetes base en el nuevo servidor Ubuntu:
sudo apt update
sudo apt install -y python3-venv python3-dev build-essential libpq-dev postgresql nginx git
sudo adduser --system --group --home /srv/myapp django
sudo install -d -o django -g django /srv/myappEl usuario django ejecuta la aplicación sin privilegios de root. Mantén el acceso SSH administrativo con tu cuenta normal. Permite tu conexión SSH en el firewall antes de activar las reglas, y permite el tráfico entrante HTTP y HTTPS. Mantén PostgreSQL accesible solo donde la app lo necesite.
2. Crear credenciales de PostgreSQL
Crea un rol de base de datos con una contraseña que elijas cuando se te pida, y luego crea su base de datos:
sudo -u postgres createuser --pwprompt myapp
sudo -u postgres createdb --owner=myapp myappLa aplicación se conectará a 127.0.0.1, que usa autenticación por contraseña bajo la configuración habitual de PostgreSQL en Ubuntu. Verifica tu pg_hba.conf si la conexión falla. No expongas el puerto de la base de datos públicamente para resolver un problema de autenticación local.
3. Instalar la aplicación y las dependencias
Sustituye la URL del repositorio de ejemplo. Para un repositorio privado, organiza el acceso de lectura sin poner un token en la URL ni en archivos confirmados.
sudo -u django git clone https://github.com/your-org/your-app.git /srv/myapp/app
sudo -u django python3 -m venv /srv/myapp/venv
sudo -u django /srv/myapp/venv/bin/pip install -r /srv/myapp/app/requirements.txtTus requisitos deben incluir la versión de Django, Gunicorn y el controlador de PostgreSQL que use tu proyecto. Usa el gestor de paquetes del proyecto si tiene un lockfile diferente. Confirma que el módulo de configuraciones y la ruta de importación WSGI coinciden con tu repositorio.
4. Cargar configuraciones de producción explícitamente
Crea /etc/myapp.env con tu editor. Guarda ahí los valores reales, no en Git. Este ejemplo asume que tus configuraciones leen estos nombres:
DJANGO_SECRET_KEY='replace-with-a-long-random-secret'
DJANGO_ALLOWED_HOSTS='example.com'
DJANGO_CSRF_TRUSTED_ORIGINS='https://example.com'
DB_NAME='myapp'
DB_USER='myapp'
DB_PASSWORD='replace-with-the-password-you-set'
DB_HOST='127.0.0.1'
DB_PORT='5432'Usa valores simples entre comillas compatibles tanto con un shell como con la sintaxis de archivo de entorno de systemd. Protege el archivo:
sudo chown root:django /etc/myapp.env
sudo chmod 640 /etc/myapp.envEn la configuración de producción, configura las lecturas correspondientes. Este fragmento asume que BASE_DIR ya existe:
import os
DEBUG = False
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
ALLOWED_HOSTS = os.environ['DJANGO_ALLOWED_HOSTS'].split(',')
CSRF_TRUSTED_ORIGINS = os.environ['DJANGO_CSRF_TRUSTED_ORIGINS'].split(',')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DB_NAME'],
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASSWORD'],
'HOST': os.environ['DB_HOST'],
'PORT': os.environ['DB_PORT'],
}
}
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'Mantén los archivos multimedia subidos separados de los estáticos recopilados. Elige almacenamiento duradero, política de respaldo y controles de acceso apropiados para el contenido subido. Consulta la lista de verificación de implementación de Django.
5. Verificar configuraciones y preparar la base de datos
Ejecuta los comandos de gestión como el usuario de la app con el mismo entorno que el servicio:
sudo -u django bash -c 'set -a; . /etc/myapp.env; set +a; cd /srv/myapp/app; /srv/myapp/venv/bin/python manage.py check --deploy'
sudo -u django bash -c 'set -a; . /etc/myapp.env; set +a; cd /srv/myapp/app; /srv/myapp/venv/bin/python manage.py migrate --noinput'
sudo -u django bash -c 'set -a; . /etc/myapp.env; set +a; cd /srv/myapp/app; /srv/myapp/venv/bin/python manage.py collectstatic --noinput'Revisa las advertencias de implementación. Los ajustes relacionados con HTTPS vienen después de que el proxy tenga un certificado funcional. Para una base de datos de producción existente, haz y prueba una copia de seguridad antes de aplicar cambios de esquema. Ejecuta las migraciones una vez como parte de la versión, no en cada worker de Gunicorn.
6. Ejecutar Gunicorn bajo systemd
Crea /etc/systemd/system/myapp.service:
[Unit]
Description=Django application
After=network.target postgresql.service
[Service]
User=django
Group=django
WorkingDirectory=/srv/myapp/app
EnvironmentFile=/etc/myapp.env
ExecStart=/srv/myapp/venv/bin/gunicorn myproject.wsgi:application --bind 127.0.0.1:8001 --workers 2 --access-logfile - --error-logfile -
Restart=on-failure
RestartSec=5
PrivateTmp=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.targetEl valor de dos workers es un ejemplo inicial, no una fórmula de dimensionamiento. Mide memoria, conexiones a la base de datos y tiempo de respuesta antes de aumentarlo. Gunicorn escucha en loopback para que los visitantes lleguen a la app a través de nginx.
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp --no-pager
curl -I -H 'Host: example.com' http://127.0.0.1:8001/Usa journalctl -u myapp para errores de proceso. Un fallo de importación suele apuntar a la ruta del módulo, el directorio de trabajo o la instalación de dependencias. Consulta la guía WSGI de Django y Gunicorn.
7. Poner nginx delante
Crea /etc/nginx/sites-available/myapp:
server {
listen 80;
server_name example.com;
location /static/ {
alias /srv/myapp/app/staticfiles/;
}
location / {
proxy_pass http://127.0.0.1:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Habilita el nuevo sitio una vez, prueba la configuración y recarga:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp
sudo nginx -t
sudo systemctl reload nginxConfirma que nginx puede leer los archivos estáticos recopilados y recorrer sus directorios padres. Mantén cualquier configuración de sitio por defecto consistente con tus otros dominios alojados; no elimines sitios no relacionados. La guía de nginx explica la configuración de proxy inverso y archivos estáticos.
8. Añadir HTTPS y re-verificar Django
Instala un certificado usando tu cliente ACME elegido y sus instrucciones actuales. Con Certbot instalado para nginx, la solicitud de certificado para este ejemplo es:
sudo certbot --nginx -d example.com
sudo certbot renew --dry-runUsa las instrucciones de Certbot para la instalación en tu SO. Verifica el DNS del dominio, la accesibilidad HTTP y la renovación antes de considerar TLS completo.
Una vez que HTTPS funcione, establece estas opciones de Django:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = TrueConfía en la cabecera forwarded-protocol solo porque nginx la controla y Gunicorn no está expuesto directamente. Reinicia el servicio, vuelve a ejecutar check --deploy, y prueba el inicio de sesión y un envío de formulario sobre HTTPS. Revisa HSTS por separado tras verificar HTTPS en cada host afectado.
9. Hacer las futuras versiones repetibles
Para cada versión, elige una revisión revisada, instala sus dependencias bloqueadas, aplica cualquier migración compatible, recopila archivos estáticos y reinicia el servicio. Prueba la aplicación en vivo y vigila los errores tras el cambio.
Mantén la revisión anterior disponible, pero recuerda que revertir código puede no deshacer una migración de base de datos. Documenta la ruta de recuperación para cambios de esquema y prueba restaurar datos en una base de datos separada.
Si la app usa Celery, dale una definición de servicio, comando y configuración de broker separados. No ejecutes el worker como comando en segundo plano dentro del servicio de Gunicorn.
Cuándo elegir hosting gestionado en su lugar
Un VPS es razonable cuando quieres control del servidor y puedes mantenerlo. Si quieres delegar más operaciones de host, compara opciones de hosting de apps Python. En Lizard, la aplicación puede usar Managed Postgres y un worker separado, mientras mantienes el control de su código y configuración.
La documentación de implementación cubre esa ruta. Sigues necesitando configuraciones correctas de Django, un proceso de versión y un plan de recuperación de datos verificado.
Preguntas frecuentes
¿Por qué obtengo un error 502? Verifica si Gunicorn está corriendo y escuchando en 127.0.0.1:8001, luego inspecciona sus logs y el log de errores de nginx. Un proxy inverso no puede arreglar un proceso que falla al iniciar.
¿Por qué faltan archivos estáticos? Verifica STATIC_ROOT, la salida de collectstatic, la ruta alias de nginx y los permisos de archivo. Los medios subidos requieren una configuración separada.
¿Puedo usar SQLite en vez de PostgreSQL? Algunas aplicaciones pueden, pero evalúa los requisitos de concurrencia, persistencia y respaldo. No cambies el motor de base de datos sin probar la carga de trabajo de tu aplicación.
¿Basta una comprobación de implementación exitosa? No. Prueba un flujo real de usuario, escrituras en base de datos, acceso a archivos y recuperación. Las comprobaciones de configuración detectan solo parte del comportamiento en producción.
Construye con IA. Publica con Lizard.
No necesitas un equipo de plataforma para salir a producción. Toda tu nube, a un comando de CLI de distancia.
- Espacios de trabajo
- —
- Servicios
- —
- Add-ons
- —
- Despliegues
- —