Skip to main content
Cloud & DevOps (AWS)

Ultimate DevOps Guide: Install and Configure PostgreSQL & Redis Locally on AWS EC2 for Laravel (Fix Common Connection Errors)

Introduction You’ve shipped a great Laravel app to an EC2 instance. Nginx is humming, PHP-FPM is warm, and then—wham—your logs explode with: SQLSTATE...

Engr Mejba Ahmed
Author
Engr Mejba Ahmed
Published
Oct 05, 2025
Reading time
11 min · 2,027 words
Ultimate DevOps Guide: Install and Configure PostgreSQL & Redis Locally on AWS EC2 for Laravel (Fix Common Connection Errors)
Featured image for Ultimate DevOps Guide: Install and Configure PostgreSQL & Redis Locally on AWS EC2 for Laravel (Fix Common Connection Errors)

Introduction

You’ve shipped a great Laravel app to an EC2 instance. Nginx is humming, PHP-FPM is warm, and then—wham—your logs explode with:

  • SQLSTATE[08006] [7] connection to server at "127.0.0.1", port 5432 failed: Connection refused
  • FATAL: role "ec2-user" does not exist
  • SQLSTATE[42501]: Insufficient privilege: permission denied for schema public
  • Class "Redis" not found or Connection refused [tcp://127.0.0.1:6379]

If that looks familiar, this guide is for you.

In this premium, end-to-end tutorial I’ll show you how to install and production-configure PostgreSQL and Redis locally on the same AWS EC2 machine that runs Laravel—and how to troubleshoot and fix the most common errors you’ll face along the way. You’ll get a blueprint you can repeat for every project: lean, secure, and fast.

What you’ll get by the end:

  • A working PostgreSQL 15 and Redis stack on Amazon Linux (or similar) with systemd services and persistence.
  • A hardened Laravel .env that uses Redis for cache/sessions/queues and PostgreSQL for the database.
  • A quick-hit troubleshooting section that resolves connection refused, missing roles, permission errors, and PHP Redis extension issues.
  • Practical performance and reliability tips: small-instance tuning, log checks, and production hygiene.

Let’s ship.


What You’ll Need

  • An AWS EC2 instance (t3.micro or larger). Amazon Linux 2023 is perfect; commands map closely to RHEL/CentOS with dnf.
  • A security group allowing inbound 80/443 (HTTP/HTTPS) from the internet and 22 (SSH) from your IP. Do not open PostgreSQL (5432) or Redis (6379) to the public internet.
  • A deployed Laravel codebase (Nginx + PHP-FPM) with SSH access as ec2-user (or similar).

Install Packages Required by Laravel + DB Stack

The commands below target Amazon Linux 2023. For Ubuntu, replace dnf with apt.

# Always start here
sudo dnf -y update

# PHP & common extensions (adjust versions if needed)
sudo dnf -y install php-cli php-fpm php-common php-opcache php-mbstring php-xml php-curl php-zip php-gd php-intl

# Postgres client + PHP extension
sudo dnf -y install postgresql postgresql15 postgresql15-server php-pgsql

# Redis server + PHP Redis extension
# On Amazon Linux 2023, redis is typically provided as redis6 in 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 & tools
sudo dnf -y install git unzip

Why both postgresql and postgresql15*? On AL2023, postgresql15 is the server you want. The generic postgresql package provides client utilities. If postgresql15 isn’t found, enable the right repo or use the OS-provided PostgreSQL major version.

Restart PHP-FPM after PHP extension installs:

sudo systemctl restart php-fpm

Initialize and Start PostgreSQL 15

Initialize the data directory as the postgres system user:

# Initialize cluster
sudo -u postgres /usr/bin/initdb -D /var/lib/pgsql/15/data

# (If initdb isn't found, locate it)
# sudo find / -type f -name initdb 2>/dev/null

# Create a systemd service for PG15 if your image lacks the unit:
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

If the status shows active (running), you’re good.


Create Database, User, and Secure Local Auth

Enter the Postgres shell:

sudo -u postgres psql

Create a dedicated database/user and grant ownership (this avoids the dreaded “permission denied for schema public”):

-- inside 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;

-- optional: make sure future objects default to the app user
ALTER DATABASE writeflow OWNER TO writeflow_user;

Harden local authentication to use MD5 for local TCP connections (or peer/ident if you prefer sockets):

sudo nano /var/lib/pgsql/15/data/pg_hba.conf

Add or ensure these lines are present near the top:

# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   all             all                                     peer
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5

Reload PostgreSQL:

sudo systemctl restart postgresql-15

Quick tests:

PGPASSWORD='StrongPassword123!' psql -U writeflow_user -d writeflow -h 127.0.0.1 -c "select now();"

If that returns a timestamp, your DB auth is correct.


Install & Start Redis (and Make It Persistent)

# Install (already done above); start and enable
sudo systemctl enable redis
sudo systemctl start redis
sudo systemctl status redis --no-pager

# Smoke test
redis-cli ping
# Expect: PONG

If your image ships Redis 6 as a different unit name (e.g., redis6), simply adapt:

sudo systemctl enable redis6
sudo systemctl start redis6
sudo systemctl status redis6 --no-pager

Bind + Security: For local-only use, bind to 127.0.0.1 and keep protected mode on.

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

Configure Laravel’s .env

In your app root:

nano /var/www/your-app/.env

Use this clean baseline for DB and Redis:

APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com

# --- Database (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

Important: ensure PHP’s Redis extension is loaded. After php-pecl-redis install, PHP-FPM restart usually picks it up:

php -m | grep -i redis     # should print: redis
sudo systemctl restart php-fpm

Migrate & Optimize the Laravel App

From your app directory:

php artisan key:generate
php artisan config:clear
php artisan cache:clear
php artisan view:clear

# Run fresh or standard migrate as needed
php artisan migrate --force

If you hit permission denied for schema public, re-run the Postgres grants (see Section 3). Also, ensure the database owner is your app user and not postgres.


Fix Every Common Error (Troubleshooting Vault)

A) SQLSTATE[08006] [7] connection to server at "127.0.0.1", port 5432 failed: Connection refused

Meaning: The PG server isn’t listening, crashed, or blocked.

Fix checklist:

sudo systemctl status postgresql-15 --no-pager
journalctl -xeu postgresql-15 --no-pager
ss -ltnp | grep 5432
  • If the unit is inactive/failed, check serverlog in /var/lib/pgsql/15/data/.
  • Verify pg_hba.conf and postgresql.conf (listen_addresses). For local TCP, ensure listen_addresses = 'localhost' or '*' (local only is fine).
  • Ensure your .env uses the correct DB name, user, and password.

B) FATAL: role "ec2-user" does not exist

Meaning: Laravel tried to authenticate with a Linux username (often happens if .env isn’t loaded or env caching is stale).

Fix:

  • Verify .env values and php artisan config:clear.
  • Confirm DB credentials in config/database.php use env() and not hardcoded fallbacks that point to ec2-user.

C) SQLSTATE[42501]: Insufficient privilege: permission denied for schema public

Meaning: Your app user doesn’t own schema or lack permissions.

Fix in 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

Meaning: PHP Redis extension not loaded.

Fix:

sudo dnf -y install php-pecl-redis || sudo dnf -y install php-redis
php -m | grep -i redis
sudo systemctl restart php-fpm

Ensure .env uses REDIS_CLIENT=phpredis.

E) Redis connection refused [tcp://127.0.0.1:6379]

Meaning: Redis service isn’t running, or bound to a different IP.

Fix:

sudo systemctl status redis --no-pager || sudo systemctl status redis6 --no-pager
redis-cli ping
ss -ltnp | grep 6379
  • If the unit is down, sudo systemctl start redis and sudo systemctl enable redis.
  • Ensure bind 127.0.0.1 in /etc/redis/redis.conf, then restart.

F) migrations table does not exist (after you know it does)

Meaning: Config cache pointing at wrong DB, or .env not read.

Fix:

php artisan config:clear
php artisan cache:clear
php artisan migrate --force

Small-Instance Performance Wins

You don’t need a DB admin to avoid out-of-memory errors.

PostgreSQL (on t3.micro/t3.small):

  • Keep max_connections modest (e.g., 100 or less).
  • Use sensible shared buffers: shared_buffers = 128MB.
  • Let effective_cache_size = 1GB on small boxes (if memory allows).

PHP-FPM:

  • Reduce pm.max_children to match RAM. On small boxes, 5–10 is often plenty.

Redis:

  • Keep it local-only with bind 127.0.0.1 and default persistence for sessions/queues.

Security & Reliability Hygiene

  • Security Groups: Never expose 5432 or 6379 publicly. Keep them bound to 127.0.0.1 only.

  • Backups: Nightly pg_dump to 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.gz
    

    Then cron it:

    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

End-to-End Smoke Test Checklist

  1. sudo systemctl status postgresql-15active (running)
  2. PGPASSWORD='StrongPassword123!' psql -U writeflow_user -d writeflow -h 127.0.0.1 -c "select version();" → returns version
  3. sudo systemctl status redis (or redis6) → active (running)
  4. redis-cli pingPONG
  5. php -m | grep -i redisredis
  6. php artisan migrate --forceDONE
  7. Login/register flows work; sessions persist; caches warm.

Bullet Points / Quick Takeaways

  • Never expose DB/Redis to the internet. Bind to 127.0.0.1, secure via security groups.
  • Fix “role does not exist” by ensuring .env is loaded and the correct DB user is created.
  • Fix “permission denied for schema public” by transferring schema/database ownership to your app user.
  • Fix Redis “Class not found” by installing php-pecl-redis and restarting PHP-FPM.
  • Fix Redis “Connection refused” by enabling and starting the Redis service, then confirming it listens on 127.0.0.1:6379.
  • Lock in reliability: health-check with systemctl and journalctl, and back up nightly to S3.

Call to Action (CTA)

Want a battle-tested deployment checklist (Nginx, PHP-FPM, SSL, Postgres, Redis) you can paste into your terminal? Need this done right—fast? 👉 Purchase my Fiverr gig : https://www.fiverr.com/s/DBjDz4a


FAQ (High-Intent Questions)

Q1) Should I use local PostgreSQL/Redis or managed services (RDS/ElastiCache)? If you’re early-stage or cost-conscious, local is lean and fast to ship. As your traffic grows or you need multi-AZ failover, move to RDS + ElastiCache for managed reliability and scaling.

Q2) Do I need to open port 5432 or 6379 externally? No. Your Laravel app connects locally. Keep both closed to the internet and bound to 127.0.0.1.

Q3) How do I fix “SQLSTATE[08006] connection refused” even though PostgreSQL is running? Verify it’s listening on 127.0.0.1:5432 (ss -ltnp | grep 5432), confirm .env credentials, and check pg_hba.conf for local md5 rules. Finally, php artisan config:clear to flush stale env cache.

Q4) Redis is running but sessions still fail—why? Confirm php -m | grep -i redis shows the extension. Ensure .env has REDIS_CLIENT=phpredis and restart php-fpm. Also verify storage/framework/sessions is not being used by mistake (check SESSION_DRIVER).

Q5) How do I avoid “permission denied for schema public” on new tables? After creating the DB, set the owner to your app user and run:

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;

This fixes both current and future objects.

Q6) What about SSL/TLS to the database? For same-host connections (127.0.0.1), SSL isn’t necessary. If you later move DB to a separate instance or RDS, enable SSL and restrict connections via security groups.


Final Word

You don’t need a massive DevOps budget to run fast, stable Laravel apps on AWS. With this guide you’ve:

  • Set up PostgreSQL 15 + Redis locally,
  • Wired Laravel for performance (Redis cache/sessions/queues),
  • Solved the high-friction errors that block most teams on day one.

Pin this guide. Reuse it next launch. And when you’re ready to scale, you’ll already be production-ready.

Engr Mejba Ahmed

About the author

Engr Mejba Ahmed

I'm Engr. Mejba Ahmed, a Software Engineer, Cybersecurity Engineer, and Cloud DevOps Engineer specializing in Laravel, Python, WordPress, cybersecurity, and cloud infrastructure. Passionate about innovation, AI, and automation.

Tags

Keep reading

Browse the blog →