Saltar a contenido

06 — Configuración de nginx

Este SaaS necesita un único vhost nginx que sirve tres tipos de tráfico:

Path Destino Comentario
/static/* Filesystem (alias) Widget JS/CSS, uploads, assets del Django admin
/media/* Filesystem (alias) Logos y avatares subidos por usuarios
/api/*, /v3/*, /admin/*, /health proxy_pass http://127.0.0.1:8000 Django
/ (resto) Filesystem (root + fallback /index.html) SPA del dashboard

Vhost completo (copy-paste)

El template real está en infra/nginx-superbot.conf.example. Resumen del bloque de overrides (lo que va en un Plesk vhost) o del server { } completo (si es nginx puro):

server {
    listen 443 ssl http2;
    server_name app.tudominio.com widget.tudominio.com;

    # SSL — ajusta paths de tus certificados
    ssl_certificate     /etc/letsencrypt/live/app.tudominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.tudominio.com/privkey.pem;

    client_max_body_size 64M;

    # ── Estáticos del widget + uploads ──
    # Importante: NO añadir try_files con alias (genera double-path 404).
    location /static/ {
        alias /home/blimxapp/saas/chatbot-v3-workspace/static/;
        autoindex off;
        expires 1h;
        add_header Cache-Control "public, max-age=3600" always;
        add_header Access-Control-Allow-Origin "*" always;
        disable_symlinks off;
    }

    # ── Uploads de usuarios (logos, avatares) ──
    location /media/ {
        alias /home/blimxapp/saas/chatbot-v3-workspace/backend_django/media/;
        autoindex off;
        expires 7d;
        add_header Cache-Control "public, max-age=604800" always;
    }

    # ── Endpoints públicos del widget ──
    location /v3/ {
        proxy_pass http://127.0.0.1:8000;
        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;
        proxy_read_timeout 60s;
        proxy_connect_timeout 30s;
        gzip on;
        gzip_types application/json text/plain text/css application/javascript;
    }

    # ── API del SaaS ──
    location /api/ {
        proxy_pass http://127.0.0.1:8000;
        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;
        proxy_read_timeout 600s;
    }

    # ── Stripe webhook (sin rate limit, sin compresión) ──
    location = /api/billing/stripe/webhook/ {
        proxy_pass http://127.0.0.1:8000;
        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;
        proxy_read_timeout 600s;
    }

    # ── Django admin ──
    location /admin/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # ── Health check ──
    location = /health {
        proxy_pass http://127.0.0.1:8000;
        access_log off;
    }

    # ── Dashboard SPA (React) ──
    location / {
        root /home/blimxapp/saas/chatbot-v3-workspace/apps/dashboard/dist;
        try_files $uri $uri/ /index.html;
        index index.html;
        expires 5m;
    }
}

# ── HTTP → HTTPS redirect ──
server {
    listen 80;
    server_name app.tudominio.com widget.tudominio.com;
    return 301 https://$host$request_uri;
}

Por qué cada bloque importa

/static/ con alias y sin try_files

location /static/ {
    alias /home/blimxapp/saas/chatbot-v3-workspace/static/;
    disable_symlinks off;
}
  • alias (no root) porque queremos que /static/v3/foo.js busque static/v3/foo.js (sin duplicar el prefijo).
  • NO try_files $uri =404 — combinado con alias produce un bug donde nginx busca un path doblado y devuelve 404 en TODO. Si necesitas 404 explícito, omite try_files (nginx ya lo hace).
  • disable_symlinks off porque el path origen está fuera del docroot.

/v3/ y /api/ apuntan al mismo upstream

Ambos paths van a Django :8000. El routing interno lo hace blimx_admin/urls.py:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),
    path('api/auth/', include('authentication.urls')),
    path('v3/', include('api.widget.urls')),
]

Mantenerlos separados en nginx permite aplicar timeouts distintos:

  • /v3/* → 60s (queries rápidas, FAQs)
  • /api/* → 600s (Stripe, generación de PDF de facturas)

El endpoint /v3/faq devuelve únicamente categories[].faqs y no duplica una lista plana faqs[]. Esto reduce el payload móvil sin afectar al widget Help Center, que renderiza las preguntas desde sus categorías.

Webhook Stripe en location = (exact match)

location = /api/billing/stripe/webhook/ {
    ...
}

Stripe espera respuesta en menos de 10s. El = (exact match) hace que nginx no evalúe otros location bloques y no pierde tiempo. También tener un bloque dedicado evita que un futuro rate-limit global bloquee a Stripe.

/media/ separado de /static/

  • /static/ es inmutable (output de builds, mismo en todos los servidores).
  • /media/ es mutable (uploads de usuarios, distinto entre servidores).

Si haces backup, basta con respaldar /media/ y la BD; /static/ se regenera con un build.

Permisos de filesystem

Para que nginx pueda leer /static/ y /media/:

  1. Identifica como qué usuario corre nginx:

    ps -eo user,comm | grep nginx | grep -v grep | head -1
    # output esperado: nginx (Plesk/RHEL) o www-data (Debian/Ubuntu)
    

  2. Añade ese usuario al grupo de la app:

    sudo usermod -a -G blimxapp nginx       # ajusta el nombre del usuario
    sudo systemctl restart nginx            # restart, NO reload (los workers heredan grupos al fork)
    

  3. Verifica permisos del path raíz:

    ls -la /home/blimxapp        # debe ser drwxr-x--- y group=blimxapp
    

Si está en drwx------ (700), nginx no podrá entrar. Cambia a 750:

sudo chmod 750 /home/blimxapp

  1. Smoke test:
    sudo -u nginx ls /home/blimxapp/saas/chatbot-v3-workspace/static/v3/  # debe listar archivos
    

Despliegue en Plesk

Plesk genera el server { } automáticamente. No edites /etc/nginx/plesk.conf.d/vhosts/<dominio>.conf (Plesk lo regenera). En su lugar pon los bloques location en:

/var/www/vhosts/system/<dominio>/conf/vhost_nginx.conf

Plesk hace include de ese archivo dentro del server { }, así sobrescribe los defaults de Plesk con los nuestros.

Tras editar:

sudo nginx -t
sudo systemctl reload nginx

Verificación final

# 1) Frame del widget se sirve
curl -sI https://widget.tudominio.com/static/v3/dist/blimx-v3-chatbot-frame.html | head -3

# 2) Bundle JS se sirve
curl -sI https://widget.tudominio.com/static/v3/dist/blimx-chatbot.min.js | head -3

# 3) /v3/health responde JSON
curl -s https://widget.tudominio.com/v3/health

# 4) /api/health responde JSON
curl -s https://app.tudominio.com/api/health/

# 5) Dashboard SPA carga
curl -sI https://app.tudominio.com/ | head -3

Todos deben devolver 200 OK. Si alguno da 502, revisa que blimx-django.service esté active. Si da 403, problema de permisos (ver sección anterior).