Introducción
Has desplegado una gran app Laravel en una instancia EC2. Nginx está funcionando, PHP-FPM está caliente, y entonces — ¡bam! — tus logs explotan con:
SQLSTATE[08006] [7] connection to server at "127.0.0.1", port 5432 failed: Connection refusedFATAL: role "ec2-user" does not existSQLSTATE[42501]: Insufficient privilege: permission denied for schema publicClass "Redis" not foundoConnection refused [tcp://127.0.0.1:6379]
Si eso te resulta familiar, esta guía es para ti.
En este tutorial premium de principio a fin te mostraré cómo instalar y configurar PostgreSQL y Redis localmente en la misma máquina AWS EC2 que ejecuta Laravel — y cómo diagnosticar y corregir los errores más comunes que enfrentarás en el camino. Obtendrás un plano que puedes repetir para cada proyecto: limpio, seguro y rápido.
Lo que tendrás al final:
- Un stack PostgreSQL 15 y Redis funcionando en Amazon Linux (o similar) con servicios systemd y persistencia.
- Un Laravel .env reforzado que usa Redis para caché/sesiones/colas y PostgreSQL para la base de datos.
- Una sección rápida de troubleshooting que resuelve connection refused, roles faltantes, errores de permisos y problemas con la extensión PHP Redis.
- Consejos prácticos de rendimiento y confiabilidad: tuning de instancias pequeñas, verificación de logs e higiene de producción.
Empecemos.
Lo Que Necesitarás
- Una instancia AWS EC2 (t3.micro o mayor). Amazon Linux 2023 es perfecto; los comandos coinciden estrechamente con RHEL/CentOS usando
dnf. - Un security group que permita tráfico entrante en 80/443 (HTTP/HTTPS) desde internet y 22 (SSH) desde tu IP. No abras PostgreSQL (5432) ni Redis (6379) al internet público.
- Una codebase Laravel desplegada (Nginx + PHP-FPM) con acceso SSH como
ec2-user(o similar).
Instalar Paquetes Requeridos por Laravel + Stack de DB
Los comandos a continuación son para Amazon Linux 2023. Para Ubuntu, reemplaza
dnfporapt.
# Siempre empezar aquí
sudo dnf -y update
# PHP & extensiones comunes (ajustar versiones si es necesario)
sudo dnf -y install php-cli php-fpm php-common php-opcache php-mbstring php-xml php-curl php-zip php-gd php-intl
# Cliente Postgres + extensión PHP
sudo dnf -y install postgresql postgresql15 postgresql15-server php-pgsql
# Servidor Redis + extensión PHP Redis
# En Amazon Linux 2023, redis se proporciona típicamente como redis6 en amazon-linux-extras.
sudo dnf -y install redis || true
sudo amazon-linux-extras enable redis6 || true
sudo dnf -y install redis
sudo dnf -y install php-pecl-redis || sudo dnf -y install php-redis
# Git & herramientas
sudo dnf -y install git unzip
¿Por qué tanto postgresql como postgresql15*?
En AL2023, postgresql15 es el servidor que quieres. El paquete genérico postgresql proporciona utilidades de cliente. Si postgresql15 no se encuentra, habilita el repositorio correcto o usa la versión principal de PostgreSQL proporcionada por el SO.
Reinicia PHP-FPM después de instalar extensiones PHP:
sudo systemctl restart php-fpm
Inicializar e Iniciar PostgreSQL 15
Inicializa el directorio de datos como el usuario de sistema postgres:
# Inicializar cluster
sudo -u postgres /usr/bin/initdb -D /var/lib/pgsql/15/data
# (Si initdb no se encuentra, localízalo)
# sudo find / -type f -name initdb 2>/dev/null
# Crea un servicio systemd para PG15 si tu imagen carece de la unidad:
cat <<'EOF' | sudo tee /etc/systemd/system/postgresql-15.service
[Unit]
Description=PostgreSQL 15 database server
After=network.target
[Service]
Type=notify
User=postgres
ExecStart=/usr/bin/pg_ctl -D /var/lib/pgsql/15/data -l /var/lib/pgsql/15/data/serverlog start
ExecStop=/usr/bin/pg_ctl -D /var/lib/pgsql/15/data stop
ExecReload=/usr/bin/pg_ctl -D /var/lib/pgsql/15/data reload
KillMode=mixed
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable postgresql-15
sudo systemctl start postgresql-15
sudo systemctl status postgresql-15 --no-pager
Si el estado muestra active (running), estás listo.
Crear Base de Datos, Usuario y Asegurar Auth Local
Entra a la shell de Postgres:
sudo -u postgres psql
Crea una base de datos/usuario dedicado y otorga propiedad (esto evita el temido "permission denied for schema public"):
-- dentro de psql
CREATE DATABASE writeflow;
CREATE USER writeflow_user WITH ENCRYPTED PASSWORD 'StrongPassword123!';
GRANT ALL PRIVILEGES ON DATABASE writeflow TO writeflow_user;
\c writeflow
ALTER SCHEMA public OWNER TO writeflow_user;
-- opcional: asegura que los objetos futuros pertenezcan al usuario de la app
ALTER DATABASE writeflow OWNER TO writeflow_user;
Refuerza la autenticación local para usar MD5 para conexiones TCP locales (o peer/ident si prefieres sockets):
sudo nano /var/lib/pgsql/15/data/pg_hba.conf
Agrega o asegura que estas líneas estén cerca del principio:
# TYPE DATABASE USER ADDRESS METHOD
local all all peer
host all all 127.0.0.1/32 md5
host all all ::1/128 md5
Recarga PostgreSQL:
sudo systemctl restart postgresql-15
Pruebas rápidas:
PGPASSWORD='StrongPassword123!' psql -U writeflow_user -d writeflow -h 127.0.0.1 -c "select now();"
Si eso devuelve una marca de tiempo, tu autenticación de DB es correcta.
Instalar e Iniciar Redis (y Hacerlo Persistente)
# Instalar (ya hecho arriba); iniciar y habilitar
sudo systemctl enable redis
sudo systemctl start redis
sudo systemctl status redis --no-pager
# Prueba de humo
redis-cli ping
# Esperado: PONG
Si tu imagen trae Redis 6 con un nombre de unidad diferente (ej. redis6), simplemente adapta:
sudo systemctl enable redis6
sudo systemctl start redis6
sudo systemctl status redis6 --no-pager
Bind + Seguridad: Para uso solo local, enlaza a 127.0.0.1 y mantén el modo protegido activado.
sudo sed -i 's/^#\?bind .*/bind 127.0.0.1/' /etc/redis/redis.conf || true
sudo sed -i 's/^protected-mode .*/protected-mode yes/' /etc/redis/redis.conf || true
sudo systemctl restart redis || sudo systemctl restart redis6
Configurar el .env de Laravel
En la raíz de tu app:
nano /var/www/your-app/.env
Usa esta línea base limpia para DB y Redis:
APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com
# --- Base de Datos (PostgreSQL)
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=writeflow
DB_USERNAME=writeflow_user
DB_PASSWORD=
# --- Cache/Session/Queue via Redis
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
Importante: asegúrate de que la extensión Redis de PHP esté cargada. Después de instalar php-pecl-redis, un reinicio de PHP-FPM generalmente la detecta:
php -m | grep -i redis # debería mostrar: redis
sudo systemctl restart php-fpm
Migrar y Optimizar la App Laravel
Desde tu directorio de app:
php artisan key:generate
php artisan config:clear
php artisan cache:clear
php artisan view:clear
# Ejecuta fresh o standard migrate según sea necesario
php artisan migrate --force
Si obtienes permission denied for schema public, vuelve a ejecutar los grants de Postgres (ver Sección 3). También asegúrate de que el propietario de la base de datos sea tu usuario de app y no postgres.
Corregir Cada Error Común (Bóveda de Troubleshooting)
A) SQLSTATE[08006] [7] connection to server at "127.0.0.1", port 5432 failed: Connection refused
Significado: El servidor PG no está escuchando, se cayó o está bloqueado.
Lista de verificación:
sudo systemctl status postgresql-15 --no-pager
journalctl -xeu postgresql-15 --no-pager
ss -ltnp | grep 5432
- Si la unidad está inactiva/fallida, revisa
serverlogen/var/lib/pgsql/15/data/. - Verifica
pg_hba.confypostgresql.conf(listen_addresses). Para TCP local, aseguralisten_addresses = 'localhost'o'*'(solo local está bien). - Asegúrate de que tu
.envuse el nombre de DB, usuario y contraseña correctos.
B) FATAL: role "ec2-user" does not exist
Significado: Laravel intentó autenticarse con un nombre de usuario Linux (ocurre a menudo si .env no está cargado o el cache de env está obsoleto).
Fix:
- Verifica los valores de
.envyphp artisan config:clear. - Confirma que las credenciales de DB en
config/database.phpusenenv()y no fallbacks hardcodeados que apunten aec2-user.
C) SQLSTATE[42501]: Insufficient privilege: permission denied for schema public
Significado: Tu usuario de app no es propietario del schema o carece de permisos.
Fix en psql:
\c writeflow
ALTER SCHEMA public OWNER TO writeflow_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO writeflow_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO writeflow_user;
ALTER DATABASE writeflow OWNER TO writeflow_user;
D) Class "Redis" not found
Significado: Extensión PHP Redis no cargada.
Fix:
sudo dnf -y install php-pecl-redis || sudo dnf -y install php-redis
php -m | grep -i redis
sudo systemctl restart php-fpm
Asegúrate de que .env use REDIS_CLIENT=phpredis.
E) Redis connection refused [tcp://127.0.0.1:6379]
Significado: El servicio Redis no está corriendo o está enlazado a otra IP.
Fix:
sudo systemctl status redis --no-pager || sudo systemctl status redis6 --no-pager
redis-cli ping
ss -ltnp | grep 6379
- Si la unidad está caída,
sudo systemctl start redisysudo systemctl enable redis. - Asegura
bind 127.0.0.1en/etc/redis/redis.conf, luego reinicia.
F) migrations table does not exist (cuando sabes que existe)
Significado: El cache de config apunta a la DB incorrecta, o .env no se lee.
Fix:
php artisan config:clear
php artisan cache:clear
php artisan migrate --force
Ganancias de Rendimiento para Instancias Pequeñas
No necesitas un administrador de DB para evitar errores de falta de memoria.
PostgreSQL (en t3.micro/t3.small):
- Mantén
max_connectionsmodesto (ej. 100 o menos). - Usa shared buffers razonables:
shared_buffers = 128MB. - Configura
effective_cache_size = 1GBen máquinas pequeñas (si la memoria lo permite).
PHP-FPM:
- Reduce
pm.max_childrenpara coincidir con la RAM. En máquinas pequeñas, 5–10 suele ser suficiente.
Redis:
- Mantenlo solo local con
bind 127.0.0.1y persistencia predeterminada para sesiones/colas.
Higiene de Seguridad y Confiabilidad
-
Security Groups: Nunca expongas 5432 o 6379 públicamente. Mantén ambos enlazados solo a 127.0.0.1.
-
Backups:
pg_dumpnocturno a S3:# /usr/local/bin/pg-backup.sh #!/usr/bin/env bash set -e TS=$(date +%F-%H%M) PGPASSWORD='StrongPassword123!' pg_dump -U writeflow_user -h 127.0.0.1 writeflow \ | gzip > /tmp/writeflow-$TS.sql.gz aws s3 cp /tmp/writeflow-$TS.sql.gz s3://your-db-backups/ rm -f /tmp/writeflow-$TS.sql.gzLuego prográmalo con cron:
sudo crontab -e 0 3 * * * /usr/local/bin/pg-backup.sh >> /var/log/pg-backup.log 2>&1 -
Logs:
- PostgreSQL:
/var/lib/pgsql/15/data/serverlog - Redis:
journalctl -xeu redis - Nginx/PHP:
/var/log/nginx/access.log,/var/log/nginx/error.log,journalctl -xeu php-fpm
- PostgreSQL:
Checklist de Prueba de Humo End-to-End
sudo systemctl status postgresql-15→ active (running)PGPASSWORD='StrongPassword123!' psql -U writeflow_user -d writeflow -h 127.0.0.1 -c "select version();"→ devuelve versiónsudo systemctl status redis(oredis6) → active (running)redis-cli ping→ PONGphp -m | grep -i redis→ redisphp artisan migrate --force→ DONE- Los flujos de login/registro funcionan; las sesiones persisten; los cachés se calientan.
Puntos Clave / Resumen Rápido
- Nunca expongas DB/Redis a internet. Enlaza a
127.0.0.1, asegura vía security groups. - Corrige "role does not exist" asegurándote de que
.envesté cargado y el usuario de DB correcto esté creado. - Corrige "permission denied for schema public" transfiriendo la propiedad del schema/base de datos a tu usuario de app.
- Corrige Redis "Class not found" instalando
php-pecl-redisy reiniciando PHP-FPM. - Corrige Redis "Connection refused" habilitando e iniciando el servicio Redis, luego confirmando que escucha en 127.0.0.1:6379.
- Asegura la confiabilidad: verifica con
systemctlyjournalctl, y haz backup nocturno a S3.
Llamada a la Acción (CTA)
¿Quieres una checklist de deployment probada en batalla (Nginx, PHP-FPM, SSL, Postgres, Redis) que puedas pegar en tu terminal? ¿Necesitas esto hecho bien — rápido? 👉 Compra mi gig en Fiverr: https://www.fiverr.com/s/DBjDz4a
FAQ (Preguntas Frecuentes)
P1) ¿Debería usar PostgreSQL/Redis local o servicios administrados (RDS/ElastiCache)? Si estás en etapa temprana o eres consciente de los costos, local es ligero y rápido de configurar. A medida que tu tráfico crece o necesitas failover multi-AZ, migra a RDS + ElastiCache para confiabilidad y escalado administrados.
P2) ¿Necesito abrir el puerto 5432 o 6379 externamente? No. Tu app Laravel se conecta localmente. Mantén ambos cerrados a internet y enlazados a 127.0.0.1.
P3) ¿Cómo corrijo "SQLSTATE[08006] connection refused" aunque PostgreSQL está corriendo?
Verifica que esté escuchando en 127.0.0.1:5432 (ss -ltnp | grep 5432), confirma credenciales de .env, y revisa pg_hba.conf para reglas md5 locales. Finalmente, php artisan config:clear para limpiar el caché de env obsoleto.
P4) Redis está corriendo pero las sesiones siguen fallando — ¿por qué?
Confirma que php -m | grep -i redis muestre la extensión. Asegura que .env tenga REDIS_CLIENT=phpredis y reinicia php-fpm. También verifica que storage/framework/sessions no se esté usando por error (revisa SESSION_DRIVER).
P5) ¿Cómo evito "permission denied for schema public" en tablas nuevas? Después de crear la DB, establece el propietario como tu usuario de app y ejecuta:
ALTER SCHEMA public OWNER TO writeflow_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO writeflow_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO writeflow_user;
Esto corrige tanto objetos actuales como futuros.
P6) ¿Qué hay del SSL/TLS hacia la base de datos? Para conexiones en el mismo host (127.0.0.1), SSL no es necesario. Si luego mueves la DB a una instancia separada o RDS, habilita SSL y restringe conexiones vía security groups.
Palabra Final
No necesitas un presupuesto DevOps enorme para ejecutar apps Laravel rápidas y estables en AWS. Con esta guía has:
- Configurado PostgreSQL 15 + Redis localmente,
- Conectado Laravel para rendimiento (Redis caché/sesiones/colas),
- Resuelto los errores frustrantes que bloquean a la mayoría de los equipos en el día uno.
Guarda esta guía. Reúsala en tu próximo lanzamiento. Y cuando estés listo para escalar, ya serás production-ready.