Saltar al contenido
Programming 2026-09-05 15 min de lectura

VPS para Laravel: requisitos y arquitectura recomendada

Laravel en producción no es solo PHP detrás de Nginx. Incluye workers de cola, scheduler, caché, sesiones, base de datos, almacenamiento y un proceso de despliegue que no deje código a medias. Un VPS para Laravel se dimensiona por concurrencia, duración de las solicitudes y trabajo asíncrono, no únicamente por visitas mensuales.

VPS para Laravel: requisitos y arquitectura recomendada
#Laravel#VPS#PHP#Redis
T
Equipo Terranode
Editorial

Respuesta rápida: qué VPS elegir para Laravel

Una aplicación pequeña puede comenzar con 1 o 2 vCPU y 2 GB de RAM si la base de datos está en el mismo servidor y hay pocos workers. Para un proyecto comercial con base, Redis, colas y despliegues frecuentes, 2 vCPU y 4 GB dan un margen más saludable. Procesamiento de imágenes, PDFs, importaciones o muchos workers justifican 8 GB o separar cargas.

EscenarioPunto de partidaQué cabe en el VPS
Pruebas o proyecto de poco tráfico1 vCPU, 2 GB RAMNginx, PHP-FPM, base pequeña y uno o dos workers
Aplicación comercial pequeña2 vCPU, 4 GB RAMStack completo, Redis y varios workers controlados
Importaciones, PDFs o colas activas4 vCPU, 8 GB RAMMás concurrencia y margen para procesos pesados
Alta disponibilidadDos o más instanciasAplicación sin estado, balanceador y servicios compartidos

Estas cifras no son garantías. Dos aplicaciones con el mismo número de usuarios pueden consumir recursos opuestos: una sirve respuestas cacheadas y la otra genera un reporte de cien páginas en cada petición. Mide memoria por proceso y tiempo de CPU durante una carga representativa antes de escalar.

Las piezas de una arquitectura Laravel en una sola máquina

En la primera etapa, mantener el stack junto reduce costo y complejidad. Nginx recibe HTTPS y sirve estáticos; PHP-FPM ejecuta las solicitudes; MySQL o PostgreSQL persiste datos; Redis puede manejar caché, sesiones y colas; Supervisor o systemd mantiene workers; cron activa el scheduler.

text
Internet
   |
Nginx :443
   |
PHP-FPM ---> Laravel
   |          |-- Base de datos
   |          |-- Redis
   |          |-- Archivos privados
   |
Workers y scheduler

Los puertos de base y Redis no necesitan acceso público. Si todo vive en el VPS, usa sockets Unix o loopback. Solo Nginx debería escuchar en 80 y 443, además de SSH restringido para administración.

Esta arquitectura tiene un dominio de falla: si el VPS se detiene, cae todo. Aun así es una elección razonable cuando el negocio tolera ese riesgo y una recuperación rápida desde backups. Separar servicios desde el primer día multiplica redes, secretos, observabilidad y costo sin aportar valor a una aplicación pequeña.

Calcular la memoria de PHP-FPM

PHP-FPM atiende solicitudes mediante procesos worker. Cada proceso mantiene el framework, dependencias y datos de la petición en memoria. pm.max_children limita cuántas solicitudes PHP pueden ejecutarse al mismo tiempo; poner un número enorme no crea capacidad, solo permite que los procesos compitan hasta agotar la RAM.

Mide el consumo de procesos bajo tráfico real:

bash
ps --no-headers -o rss,cmd -C php-fpm8.3 | awk '{sum+=$1; n++} END {if(n) print "Promedio MB:", sum/n/1024, "Procesos:", n}'
free -h

Reserva memoria para Ubuntu, Nginx, base, Redis, workers y caché del sistema. Divide únicamente el presupuesto restante de PHP entre el consumo medio por proceso. Por ejemplo, si quedan 1.200 MB y cada worker usa 75 MB, un límite inicial de 16 es más seguro que 50.

Un pool dynamic puede verse así:

ini
pm = dynamic
pm.max_children = 16
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
request_terminate_timeout = 60s

pm.max_requests recicla procesos después de varias solicitudes y limita el impacto de fugas en extensiones. request_terminate_timeout evita procesos eternos, pero debe superar el tiempo legítimo de la operación más lenta. Las tareas largas pertenecen a la cola, no a una petición HTTP retenida.

Activa el slowlog de PHP-FPM para capturar scripts bloqueados:

ini
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm/laravel-slow.log

Después de cambiar el pool, valida y recarga:

bash
sudo php-fpm8.3 -t
sudo systemctl reload php8.3-fpm

CPU: solicitudes, Composer y trabajos pesados

La RAM decide cuántos procesos caben; la CPU determina cuánto tardan. Una API que espera principalmente a la base puede atender varias peticiones con un solo núcleo. Generar PDFs, comprimir imágenes, cifrar archivos o ejecutar cálculos mantiene el núcleo ocupado y necesita más vCPU o workers limitados.

No ejecutes diez workers solo porque hay diez trabajos pendientes. Si cada uno consume un núcleo, en un VPS de dos vCPU aumentarán el tiempo de respuesta web. Empieza con uno o dos procesos, observa la profundidad y antigüedad de la cola, y aumenta hasta el punto donde CPU y latencia siguen dentro del objetivo.

Composer y el build de frontend crean picos. En un VPS pequeño, compilar durante horas de tráfico puede activar swap o el OOM killer. Construye artefactos en CI cuando sea posible o programa el despliegue en una ventana controlada. La capacidad se dimensiona para servir la aplicación; no necesariamente para compilar cada dependencia junto a los usuarios.

Nginx y PHP-FPM: el límite de confianza

Nginx debe apuntar a la carpeta public, nunca a la raíz del repositorio. La raíz contiene .env, código, archivos de Composer y potencialmente información sensible. Un bloque mínimo dirige rutas a index.php y niega archivos ocultos:

nginx
server {
    listen 80;
    server_name app.ejemplo.com;
    root /var/www/miapp/current/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    location ~ /\. {
        deny all;
    }
}

Valida la configuración antes de recargar:

bash
sudo nginx -t
sudo systemctl reload nginx
curl -I http://127.0.0.1 -H 'Host: app.ejemplo.com'

El certificado HTTPS se instala después de que el dominio apunte al VPS. Mantén TLS en Nginx y deja PHP-FPM en un socket local; no hay razón para exponer FastCGI a la red pública.

Elegir base de datos y pool de conexiones

Laravel funciona con MySQL, MariaDB y PostgreSQL, entre otros motores. La elección depende de tu equipo, extensiones y modelo de datos más que de una diferencia abstracta de velocidad. Usa una versión soportada, un usuario exclusivo de la aplicación y backups restaurables.

Cada proceso PHP puede abrir una conexión. Si tienes 16 workers web, 8 de cola y tareas programadas, el máximo teórico ya supera 24 conexiones sin contar administración. Ajusta el pool y la base como sistema; aumentar max_connections sin considerar memoria solo mueve el fallo.

Las migraciones deben correr con credenciales capaces de cambiar el esquema, pero el proceso web puede usar un rol más limitado. En despliegues delicados, evita migraciones que bloqueen tablas grandes dentro de la misma ventana que el cambio de código. Añade columnas compatibles, despliega código que soporte ambos estados y elimina lo antiguo en una versión posterior.

La guía de PostgreSQL en Ubuntu explica roles, reglas de red y recuperación. Si eliges MySQL, aplica la misma idea: base privada, credenciales mínimas y ninguna cuenta root en .env.

Redis: cuándo aporta y cuándo sobra

Redis reduce consultas repetidas, centraliza sesiones y sirve como backend de colas. No es obligatorio para una aplicación pequeña: el driver de base puede bastar al inicio. Agregar Redis sin medir crea otro proceso que ocupa RAM, otra credencial y otra política de persistencia.

Para varias instancias web, las sesiones no pueden vivir en archivos locales porque una siguiente solicitud puede llegar a otro servidor. Redis o cookies seguras resuelven ese estado compartido. En colas, decide si perder un trabajo es aceptable y configura Redis en consecuencia; una política de expulsión apropiada para caché puede borrar tareas.

Separa prefijos y, cuando sea posible, instancias para cargas con políticas distintas. Monitoriza memoria, claves expulsadas y latencia. La guía Redis en un VPS para producción desarrolla ACL, límites y persistencia.

Workers de cola que sobreviven a SSH

php artisan queue:work no debe depender de una terminal abierta. Supervisor puede mantener varios procesos y reiniciarlos si fallan:

ini
[program:miapp-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/miapp/current/artisan queue:work redis --sleep=2 --tries=3 --timeout=90 --max-time=3600
directory=/var/www/miapp/current
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=deploy
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/supervisor/miapp-worker.log
stopwaitsecs=120

Aplica la configuración y revisa los procesos:

bash
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status

El timeout del worker debe ser menor que retry_after del backend de cola; de lo contrario un trabajo lento puede volver a estar disponible mientras la primera copia sigue ejecutándose. Haz que los trabajos sean idempotentes: un reintento no debe cobrar dos veces ni enviar dos órdenes incompatibles.

Durante un despliegue, ejecuta php artisan queue:restart. Los workers de larga vida cargan el código una vez y seguirán ejecutando la versión anterior hasta terminar. El reinicio elegante les permite finalizar el trabajo actual antes de arrancar con el release nuevo.

Scheduler sin tareas duplicadas

Laravel espera que cron invoque el scheduler cada minuto:

bash
* * * * * cd /var/www/miapp/current && php artisan schedule:run >> /dev/null 2>&1

Verifica las tareas registradas con:

bash
cd /var/www/miapp/current
php artisan schedule:list
php artisan schedule:run -v

En una sola máquina el cron es directo. Cuando replicas la aplicación, usa onOneServer() y un almacenamiento de caché compartido para trabajos exclusivos. withoutOverlapping() evita iniciar otra copia si la anterior sigue activa. Estas defensas son necesarias para facturación, correos masivos y sincronizaciones que no pueden duplicarse.

Almacenamiento local y archivos de usuarios

El directorio storage necesita escritura por PHP y workers; el resto del código no. Ajusta propietarios y permisos sin recurrir a chmod 777:

bash
sudo chown -R deploy:www-data /var/www/miapp
sudo find /var/www/miapp -type f -exec chmod 644 {} \;
sudo find /var/www/miapp -type d -exec chmod 755 {} \;
sudo chmod -R 775 /var/www/miapp/shared/storage
sudo chmod 640 /var/www/miapp/shared/.env

Si habrá varias instancias, los archivos subidos no pueden quedar en el disco local de una sola. Usa almacenamiento de objetos o un servicio compartido. El enlace public/storage debe apuntar al directorio persistente, no quedar atrapado dentro de un release que se elimina.

Despliegues versionados y rollback

Editar el único directorio activo con git pull puede mezclar código viejo, dependencias nuevas y cachés antiguas. Un esquema de releases construye una carpeta nueva, enlaza recursos persistentes, valida y cambia el symlink current de forma atómica:

text
/var/www/miapp/
├── current -> releases/20260905-1430
├── releases/
│   ├── 20260905-1200/
│   └── 20260905-1430/
└── shared/
    ├── .env
    └── storage/

Dentro del release ejecuta dependencias bloqueadas y cachés de producción:

bash
composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force
php artisan queue:restart

No uses composer update en producción; cambia el lockfile en desarrollo y despliega exactamente lo probado. APP_DEBUG debe permanecer en false. Cuando cambies .env, recuerda que config:cache congela la configuración hasta volver a generarla.

El rollback del código consiste en devolver current al release anterior, pero una migración destructiva no se revierte automáticamente. Diseña cambios de base compatibles hacia adelante y atrás, y respalda antes de una operación que transforme datos.

Healthchecks que comprueban lo necesario

Un endpoint /health debería responder rápido y confirmar que el proceso acepta solicitudes. Para un chequeo de disponibilidad puedes añadir una consulta ligera a la base y, si es crítico, a Redis. No ejecutes migraciones, trabajos pesados ni llamadas a terceros en cada sondeo.

Separa salud del proceso y disposición para recibir tráfico. Durante un despliegue, el proceso puede estar vivo mientras aún calienta cachés. En una arquitectura balanceada, el chequeo de readiness evita enviar usuarios antes de tiempo.

Prueba desde el propio VPS y desde el exterior:

bash
curl -fsS http://127.0.0.1/health -H 'Host: app.ejemplo.com'
curl -fsS https://app.ejemplo.com/health

Registra tiempo de respuesta, código HTTP y release activo. Un 200 sin validar dependencias puede ocultar una base caída; un healthcheck demasiado profundo puede derribar el sistema que pretende vigilar.

Observabilidad: mirar el recorrido completo

Vigila latencia por percentiles, tasa de errores, solicitudes por segundo, workers ocupados, antigüedad de la cola, conexiones de base y memoria disponible. El promedio no muestra los peores casos: un p95 creciente indica que una fracción relevante de usuarios ya espera aunque la media parezca estable.

Los logs útiles se correlacionan con un identificador de solicitud. Nginx registra la entrada; Laravel registra el mismo ID junto con usuario o tarea; la base aporta consultas lentas. Evita guardar contraseñas, tokens o cuerpos sensibles.

bash
sudo tail -f /var/log/nginx/miapp_error.log
sudo journalctl -u php8.3-fpm -f
tail -f /var/www/miapp/shared/storage/logs/laravel.log
sudo supervisorctl tail -f miapp-worker:miapp-worker_00

Configura rotación antes de que los logs llenen el disco. Una alerta de espacio libre y otra por OOM protegen más que revisar manualmente cuando el sitio ya cayó.

Cuándo separar servicios o escalar horizontalmente

Separa la base cuando compite por I/O o RAM, necesita un objetivo de recuperación distinto o la caída de la aplicación no debe afectarla. Separa workers cuando procesos de imágenes o importaciones consumen CPU y degradan el tráfico web. La motivación debe aparecer en métricas o requisitos de disponibilidad.

Para varias instancias web, elimina estado local: sesiones compartidas, archivos en objetos, caché central y secretos iguales. Coloca Nginx o un balanceador delante y despliega la misma versión. Si una aplicación depende de archivos temporales locales entre solicitudes, el balanceo revelará errores intermitentes.

Escalar verticalmente sigue siendo válido: pasar de 2 a 4 vCPU reduce complejidad y puede resolver la carga durante mucho tiempo. Escalar horizontalmente aporta tolerancia a fallos, pero exige automatización, coordinación del scheduler y pruebas de despliegue por etapas.

Seguridad y recuperación propias de Laravel

Mantén .env fuera del repositorio, con permisos restrictivos, y rota credenciales si alguna vez fue publicada. La APP_KEY cifra datos y cookies: perderla puede inutilizar información cifrada; cambiarla sin un plan cierra sesiones y hace ilegibles valores existentes. Resguárdala como parte de la recuperación.

Actualiza PHP, framework y dependencias tras probarlos en staging. composer audit ayuda a detectar avisos conocidos, pero no reemplaza revisar código, permisos y exposición. Limita UFW a SSH, HTTP y HTTPS; la base, Redis y PHP-FPM permanecen internos.

Respaldar solo el repositorio no protege datos. Copia la base y archivos cargados fuera del VPS, registra el release desplegado y ensaya una restauración. El objetivo no es guardar muchos archivos, sino recuperar la aplicación dentro de un tiempo aceptable.

Lista de aceptación antes de abrir tráfico

Comprueba que Nginx apunta a public, HTTPS redirige correctamente, .env no responde por web y PHP-FPM tiene un límite acorde a la RAM. Ejecuta una solicitud, un trabajo de cola y una tarea programada; valida logs, correos y escritura en base con usuarios reales de prueba.

Reinicia el VPS en una ventana controlada y confirma que Nginx, PHP-FPM, base, Redis y Supervisor vuelven solos. Simula un worker fallido y observa el reintento. Restaura una copia en otro entorno. Una arquitectura no está terminada mientras dependa de una sesión SSH o de pasos que solo recuerda una persona.

La guía cómo desplegar Laravel en un VPS contiene una instalación paso a paso. Usa ese procedimiento después de elegir capacidad y responsabilidades con esta arquitectura.

OPcache y cachés de Laravel en cada despliegue

PHP recompila archivos si OPcache no conserva el bytecode, así que una aplicación de producción debe comprobar que la extensión está activa y dimensionada para el código desplegado. No copies valores de otra máquina: observa memoria usada, desperdiciada y reinicios de la caché después de cargar las rutas principales. Si el espacio es insuficiente, las expulsiones frecuentes eliminan buena parte del beneficio.

bash
php -i | grep -E 'opcache.enable|opcache.memory_consumption|opcache.validate_timestamps'
php -m | grep -i opcache

Las cachés de Laravel resuelven otro nivel. config:cache combina la configuración y evita leer .env durante cada solicitud; route:cache acelera el registro de rutas cuando todas son compatibles; view:cache precompila plantillas. Deben generarse dentro del release nuevo, con sus variables definitivas, antes de recibir tráfico:

bash
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan about

No ignores un fallo de route:cache: normalmente revela una ruta no serializable o un nombre duplicado que debes corregir. Después de cambiar .env, vuelve a construir la caché; editar el archivo sin hacerlo deja a la aplicación usando valores anteriores. En un rollback, cada release debe conservar cachés generadas para su propio código y entorno, no compartirlas accidentalmente mediante un enlace persistente.

Si despliegas sin reiniciar PHP-FPM, los workers pueden mantener bytecode o procesos antiguos durante una ventana. Recarga de forma controlada después de cambiar el enlace current y valida el identificador del release a través del healthcheck. Un despliegue correcto no se demuestra porque Artisan terminó, sino porque una solicitud externa ejecuta el código y la configuración esperados.

Reintentos de cola sin duplicar cobros ni correos

Los workers pueden morir después de ejecutar un efecto externo y antes de marcar el trabajo como completado. Al reintentarlo, Laravel vuelve a invocar el código. Por eso un job de producción debe ser idempotente: repetirlo produce el mismo estado final en lugar de cobrar dos veces, crear dos facturas o enviar correos duplicados.

Usa una clave de negocio estable —por ejemplo, el ID del pedido más el tipo de operación— y registra el resultado en una tabla con restricción única. Para APIs que aceptan claves de idempotencia, envía la misma en todos los intentos. Encierra en transacción únicamente las escrituras locales relacionadas; no mantengas una transacción SQL abierta mientras esperas a un proveedor externo.

Configura tries, timeout, backoff y retry_after como un conjunto. El timeout del worker debe ser menor que el tiempo tras el cual la cola considera perdido el trabajo; de lo contrario, dos workers pueden procesar simultáneamente la misma tarea. El backoff debe dar tiempo a recuperarse a la dependencia, no bombardearla con reintentos inmediatos.

Vigila antigüedad del trabajo más viejo, trabajos fallidos y número de intentos. Una cola de cien tareas puede ser normal si se vacía en segundos; una sola tarea bloqueada durante una hora puede señalar un problema grave. Prueba el diseño apagando un worker a mitad de una operación controlada y confirma que el trabajo vuelve, no duplica efectos y deja evidencia suficiente para diagnosticarlo.

Empieza con una arquitectura de una máquina cuando el tamaño y la disponibilidad lo permiten: Nginx, PHP-FPM, base, Redis opcional, workers supervisados y scheduler coordinado. Dos vCPU y 4 GB son un punto equilibrado para una aplicación comercial pequeña, pero el número real depende de memoria por worker, consultas y tareas pesadas.

La calidad del VPS para Laravel se demuestra en operación: despliegues atómicos, colas que sobreviven a reinicios, archivos persistentes, backups restaurados y métricas que explican la saturación. Separa servicios o añade réplicas cuando haya una razón medible, no para imitar una arquitectura grande antes de necesitarla.

¿Listo para llevar tu sitio al siguiente nivel?

Hosting con LiteSpeed, NVMe y soporte 24/7 desde $3/mes.

Ver planes de hosting

Preguntas frecuentes