Django incluye un servidor de desarrollo que no está pensado para producción. Para publicar un proyecto se usa un servidor WSGI como Gunicorn, que ejecuta el código Python, y Nginx delante para atender las conexiones, servir los archivos estáticos y gestionar TLS. En este tutorial desplegarás un proyecto Django en Ubuntu 24.04 con PostgreSQL, Gunicorn activado por socket de systemd, Nginx y un certificado de Let's Encrypt.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 1 GB de RAM.
- Un usuario no root con privilegios
sudo. En los ejemplos se llamayour_user. - Un dominio con un registro DNS
Aapuntando a la IP del servidor. En los ejemplos se usayour_domain.
El proyecto de ejemplo se llama myproject y vive en /var/www/myproject. Si despliegas tu propio código, sustituye el nombre del paquete de configuración (el directorio que contiene settings.py y wsgi.py) donde aparezca myproject.
Paso 1: Instalar los paquetes del sistema
Ubuntu 24.04 trae Python 3.12, compatible con las versiones actuales de Django. Instala el módulo de entornos virtuales, PostgreSQL y Nginx:
sudo apt update
sudo apt install python3-venv python3-dev postgresql nginx git
Comprueba que PostgreSQL está en marcha:
systemctl status postgresql --no-pager
● postgresql.service - PostgreSQL RDBMS
Active: active (exited) since ...
El estado active (exited) es normal: ese servicio solo agrupa los clústeres, que se ejecutan en postgresql@16-main.
Abre en el cortafuegos SSH, HTTP y HTTPS:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Paso 2: Crear la base de datos PostgreSQL
Crea un usuario de PostgreSQL y una base de datos que le pertenezca. Desde PostgreSQL 15 solo el propietario de la base de datos puede crear tablas en el esquema public, así que usar OWNER evita errores de permisos al migrar. Sustituye your_strong_password por una contraseña larga y aleatoria:
sudo -u postgres psql -c "CREATE USER myproject WITH PASSWORD 'your_strong_password';"
sudo -u postgres psql -c "CREATE DATABASE myproject OWNER myproject;"
Comprueba que puedes conectarte con el nuevo usuario:
psql -h localhost -U myproject -d myproject -c "SELECT current_user;"
current_user
--------------
myproject
(1 row)
Paso 3: Preparar el proyecto y el entorno virtual
Crea el directorio de la aplicación con tu usuario como propietario. Se usa /var/www y no tu directorio personal porque en Ubuntu 24.04 los directorios de /home tienen permisos 750 y Nginx no podría leer los archivos estáticos:
sudo mkdir -p /var/www/myproject
sudo chown your_user:your_user /var/www/myproject
cd /var/www/myproject
Si tienes el proyecto en un repositorio, clónalo en ese directorio con git clone https://github.com/your_account/myproject.git ..
Crea un entorno virtual para aislar las dependencias del Python del sistema y actívalo:
python3 -m venv venv
source venv/bin/activate
Instala Django, Gunicorn y el controlador de PostgreSQL. psycopg[binary] incluye la librería de cliente, así que no necesitas compilar nada:
pip install --upgrade pip
pip install django gunicorn "psycopg[binary]"
Si clonaste tu proyecto, instala además sus dependencias con pip install -r requirements.txt. Si solo quieres probar el despliegue, crea un proyecto nuevo:
django-admin startproject myproject .
Comprueba la versión instalada:
python -m django --version
5.2.x
Paso 4: Guardar los secretos en un archivo de entorno
La clave secreta y la contraseña de la base de datos no deben estar en el código. Genera una clave aleatoria:
python3 -c "import secrets; print(secrets.token_urlsafe(50))"
Crea el archivo de entorno que leerán Gunicorn y tus comandos de gestión:
nano /var/www/myproject/.env
DJANGO_SECRET_KEY=la_clave_generada
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=your_domain,www.your_domain
DB_NAME=myproject
DB_USER=myproject
DB_PASSWORD=your_strong_password
Restringe los permisos para que solo tu usuario pueda leerlo:
chmod 600 /var/www/myproject/.env
Paso 5: Configurar Django para producción
Abre el archivo de ajustes:
nano /var/www/myproject/myproject/settings.py
Añade import os al principio del archivo y sustituye las líneas de SECRET_KEY, DEBUG, ALLOWED_HOSTS y el bloque DATABASES por estas:
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG", "False") == "True"
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": "localhost",
"PORT": "5432",
}
}
Al final del archivo, define dónde se reunirán los archivos estáticos e indica a Django que confíe en la cabecera con la que Nginx le dirá si la petición original llegó por HTTPS:
STATIC_ROOT = BASE_DIR / "staticfiles"
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
Carga las variables de entorno en tu sesión para ejecutar los comandos de gestión:
cd /var/www/myproject
set -a; source .env; set +a
Aplica las migraciones, crea un superusuario para el panel de administración y copia los archivos estáticos a STATIC_ROOT:
python manage.py migrate
python manage.py createsuperuser
python manage.py collectstatic --noinput
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
...
127 static files copied to '/var/www/myproject/staticfiles'.
Por último, ejecuta la comprobación de despliegue de Django, que avisa de ajustes inseguros:
python manage.py check --deploy
Es normal que aparezcan avisos sobre SECURE_HSTS_SECONDS y SECURE_SSL_REDIRECT: la redirección a HTTPS la hará Nginx, y HSTS conviene activarlo solo cuando HTTPS funcione de forma estable.
Sal del entorno virtual con deactivate.
Paso 6: Ejecutar Gunicorn con systemd
Usarás dos unidades: un socket que escucha en /run/gunicorn.sock y un servicio que arranca Gunicorn cuando llega la primera conexión. Crea el socket:
sudo nano /etc/systemd/system/gunicorn.socket
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target
Crea el servicio. EnvironmentFile carga el archivo .env y --workers 3 es un buen punto de partida para un servidor de 1 o 2 vCPU (la regla habitual es 2 x núcleos + 1):
sudo nano /etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon for myproject
Requires=gunicorn.socket
After=network.target
[Service]
User=your_user
Group=www-data
WorkingDirectory=/var/www/myproject
EnvironmentFile=/var/www/myproject/.env
ExecStart=/var/www/myproject/venv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
myproject.wsgi:application
Restart=on-failure
[Install]
WantedBy=multi-user.target
Activa e inicia el socket:
sudo systemctl daemon-reload
sudo systemctl enable --now gunicorn.socket
Envía una petición al socket para que systemd arranque el servicio. La cabecera Host debe estar en ALLOWED_HOSTS:
curl -s -o /dev/null -w "%{http_code}\n" --unix-socket /run/gunicorn.sock -H "Host: your_domain" http://localhost/admin/login/
200
Comprueba que el servicio se ha iniciado:
systemctl status gunicorn --no-pager
● gunicorn.service - gunicorn daemon for myproject
Active: active (running) since ...
Si algo falla, el error de Python aparece en sudo journalctl -u gunicorn.
Paso 7: Configurar Nginx como proxy inverso
Crea un bloque de servidor para el dominio:
sudo nano /etc/nginx/sites-available/myproject
Nginx servirá directamente /static/ desde disco y enviará el resto de peticiones a Gunicorn. El archivo proxy_params de Ubuntu añade las cabeceras Host, X-Real-IP, X-Forwarded-For y X-Forwarded-Proto:
server {
listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
client_max_body_size 20M;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
alias /var/www/myproject/staticfiles/;
expires 30d;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
Activa el sitio, desactiva el sitio por defecto y comprueba la sintaxis:
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
Paso 8: Activar HTTPS con Let's Encrypt
Instala Certbot con su plugin para Nginx y solicita el certificado. Certbot añade la configuración TLS y la redirección de HTTP a HTTPS:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain -d www.your_domain
Comprueba que la renovación automática funciona:
sudo certbot renew --dry-run
Abre https://your_domain/admin/ en el navegador. Deberías ver el formulario de acceso del panel de administración con sus estilos cargados, lo que confirma que Nginx sirve los estáticos. Entra con el superusuario que creaste.
Notacomo las cookies de sesión son
Secure, el acceso al panel solo funciona por HTTPS. Si intentas iniciar sesión por HTTP antes de este paso, el formulario fallará con un error CSRF.
Actualizar la aplicación
Para desplegar cambios, descarga el código, instala dependencias, migra, reúne los estáticos y reinicia Gunicorn:
cd /var/www/myproject
git pull
source venv/bin/activate
set -a; source .env; set +a
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput
deactivate
sudo systemctl restart gunicorn
Solución de problemas
- 502 Bad Gateway: Gunicorn no arranca. Revisa
sudo journalctl -u gunicorn -n 50; los fallos típicos son una variable de.envque falta (KeyError) o un módulo WSGI mal escrito enExecStart. - 400 Bad Request: el dominio no está en
DJANGO_ALLOWED_HOSTS. Corrige.envy ejecutasudo systemctl restart gunicorn. - El panel se ve sin estilos: no has ejecutado
collectstatico la ruta dealiasno coincide conSTATIC_ROOT. Comprueba que existe/var/www/myproject/staticfiles/admin/. - Cambios en las unidades de systemd sin efecto: tras editar los archivos ejecuta
sudo systemctl daemon-reloadysudo systemctl restart gunicorn.socket gunicorn.service.
Conclusión
Tu proyecto Django funciona en producción con PostgreSQL, Gunicorn gestionado por systemd y Nginx sirviendo los estáticos y el tráfico HTTPS. Como siguientes pasos puedes:
- Activar
SECURE_HSTS_SECONDScuando HTTPS lleve un tiempo funcionando sin problemas. - Configurar almacenamiento para los archivos subidos (
MEDIA_ROOT) y servirlos con otro bloquelocationen Nginx. - Añadir Celery con Redis si la aplicación necesita tareas en segundo plano.
