Deploying Laravel on AWS EC2 with RDS

Khimananda Oli 8 min read Cloud
Deploying Laravel on AWS EC2 with RDS

By Khimananda Oli | Last reviewed: August 2026

Deploying Laravel on AWS EC2 with RDS requires more than just launching an instance and pointing it to a managed database; it demands a secure VPC topology, optimized PHP-FPM process management, and strict separation of credentials from code. Many teams skip the networking fundamentals and end up with exposed databases or unscalable monoliths that fail under load. This guide provides the exact architecture and configuration steps I use in production environments to ensure your application is secure, performant, and audit-ready from day one.

What is the secure architecture for deploying Laravel on AWS EC2 with RDS?

A common mistake when deploying Laravel on AWS EC2 with RDS is treating the cloud like a traditional VPS where everything lives on a single public IP. In a production AWS environment, you must isolate your compute and data layers. The standard secure pattern uses a Virtual Private Cloud (VPC) with distinct public and private subnets across at least two Availability Zones (AZs). Your Application Load Balancer (ALB) sits in the public subnet, terminating SSL and forwarding traffic to EC2 instances in private subnets. The RDS instance also resides in a private subnet, accessible only by the EC2 security group.

Production VPC (10.0.0.0/16)Public Subnet (AZ-a)Application Load BalancerPrivate Subnet (AZ-a & AZ-b)EC2 Instance (Laravel)Nginx + PHP-FPMRDS MySQL/AuroraPrivate Endpoint OnlyAWS Secrets ManagerNo direct internet access to EC2 or RDS. All traffic flows through ALB or NAT Gateway.
Figure 1: Secure VPC topology isolates Laravel EC2 and RDS from direct public exposure while maintaining high availability.

This isolation means your EC2 instances have no public IPv4 address. You manage them using AWS Systems Manager Session Manager, which eliminates the need to open port 22 to the internet. For teams managing multiple environments, this architecture scales cleanly because adding capacity simply means provisioning more EC2 instances behind the ALB without reconfiguring database connectivity. If you are migrating from shared hosting, understanding this network boundary is critical before writing any infrastructure code.

How do you configure Nginx and PHP-FPM for Laravel on EC2?

The performance of your Laravel application depends heavily on how you tune the web server stack. On Ubuntu 24.04 LTS, install Nginx and PHP 8.3-FPM along with required extensions. Avoid using Apache unless you have specific .htaccess requirements; Nginx handles concurrent connections far more efficiently for modern PHP applications.

sudo apt update
sudo apt install -y nginx php8.3-fpm php8.3-mysql php8.3-xml php8.3-curl php8.3-zip php8.3-gd php8.3-mbstring php8.3-redis
sudo systemctl enable --now nginx php8.3-fpm

Nginx Server Block Configuration

Create a dedicated server block at /etc/nginx/sites-available/laravel. The key directive here is try_files, which routes all requests through Laravel's front controller while serving static assets directly. Always set fastcgi_pass to the Unix socket rather than TCP for lower latency on the same host.

server {
    listen 80;
    server_name example.com;
    root /var/www/laravel/current/public;
    index index.php;

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

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

PHP-FPM Process Tuning

Default PHP-FPM settings are too conservative for production. Edit /etc/php/8.3/fpm/pool.d/www.conf and switch to dynamic process management. Calculate pm.max_children based on available RAM: reserve 1GB for OS/Nginx, then divide remaining RAM by average PHP process size (typically 40-60MB for Laravel). For a 4GB instance, 50 children is a safe starting point.

  • pm = dynamic: Allows scaling workers up and down based on load.
  • pm.max_children = 50: Hard limit to prevent OOM kills.
  • pm.start_servers = 10: Pre-forked processes at startup.
  • pm.min_spare_servers = 5: Minimum idle workers ready for bursts.
  • pm.max_requests = 1000: Recycle workers to prevent memory leaks.

After updating these values, validate the configuration with nginx -t and php-fpm8.3 -t before reloading. Misconfigured FPM pools are the most frequent cause of 502 Bad Gateway errors during traffic spikes.

How do you connect EC2 to RDS securely without exposing the database?

When deploying Laravel on AWS EC2 with RDS, never whitelist 0.0.0.0/0 or use public endpoints. Instead, create a dedicated security group for your RDS instance that allows inbound traffic on port 3306 only from the EC2 security group ID. This creates a stateful firewall rule that automatically permits return traffic without additional configuration.

Connection MethodSecurity PostureComplexityRecommended For
Public Endpoint + IP WhitelistPoor (exposed to internet)LowNever in production
Private Subnet + Security Group ReferenceHigh (network isolated)MediumStandard production apps
RDS Proxy + IAM AuthenticationHighest (connection pooling + no DB creds)HighHigh-scale / multi-tenant apps

For most Laravel applications, the private subnet approach provides sufficient security. Store the RDS endpoint, username, and password in AWS Secrets Manager rather than in your .env file committed to Git. During deployment, retrieve these values using the AWS CLI or SDK and inject them into the environment. This practice aligns with SOC 2 compliance requirements for secret management and makes credential rotation possible without redeploying application code.

CI/CD RunnerEC2 InstanceSecrets ManagerRDS Database1. Deploy Code (rsync/git)2. GetSecretValue(DB_Creds)3. Return Encrypted Secret4. Connect (TLS + Injected Creds)5. Query ResultCache in Memory
Figure 2: Secrets are fetched at runtime or deploy-time and never stored on disk, reducing credential leakage risk.

If your application experiences connection exhaustion, consider adding RDS Proxy between your EC2 instances and the database. The proxy maintains a pool of persistent connections and multiplexes application requests, allowing Laravel to scale beyond the database's native connection limit without modifying application code. This is particularly valuable for Laravel applications using queue workers that maintain long-lived connections.

Manual deployments via SSH are unsustainable and error-prone. Adopt an atomic deployment strategy using tools like Deployer or Ansible. Atomic deployments symlink the current directory to a new release folder only after all migrations and asset builds succeed, enabling instant rollback if something fails. This approach is essential for achieving zero-downtime releases.

  1. Build artifacts in CI: Compile assets, run tests, and create a tarball in your pipeline. Never build on production servers.
  2. Transfer artifact: Upload the pre-built archive to S3, then pull it onto EC2 instances via user-data or deployment script.
  3. Install dependencies: Run composer install --no-dev --optimize-autoloader in the new release directory.
  4. Migrate database: Execute php artisan migrate --force against the RDS endpoint. Use maintenance mode for breaking changes.
  5. Optimize caches: Run config:cache, route:cache, and view:cache to eliminate runtime parsing overhead.
  6. Activate release: Update the symlink atomically. Restart PHP-FPM to clear OPcache gracefully.

Automate this entire sequence through GitLab CI or GitHub Actions. Store your deployment SSH keys or OIDC trust policies securely, and always test the full pipeline against a staging environment that mirrors production networking. For teams operating in Nepal or regions with higher latency to AWS Singapore/Mumbai, consider using CodeDeploy with S3 artifacts stored in the nearest region to reduce transfer times during release windows.

How do you monitor and optimize Laravel performance on AWS?

Performance tuning doesn't end at deployment. Install the AWS CloudWatch agent to stream PHP-FPM slow logs and system metrics. Configure Laravel's logging channel to write structured JSON to CloudWatch Logs for easier querying. Set up alarms on RDS CPU utilization, free storage space, and database connections — these are leading indicators of impending outages.

Observability Options for Laravel on AWSAWS Native (CloudWatch)✓ Zero external dependencies✓ Integrated with RDS/EC2 metrics✗ Limited visualization & alertingBest for: Small teams, compliance-firstOpen Source (Prometheus/Grafana)✓ Rich dashboards & PromQL✓ Exporters for PHP-FPM/MySQL✗ Self-hosted maintenance burdenBest for: Performance tuning, custom SLOs
Figure 3: Choose AWS CloudWatch for simplicity or Prometheus/Grafana for deep Laravel performance visibility and custom alerting.

Enable Laravel's built-in cache drivers backed by ElastiCache Redis for session and query caching. Redis significantly reduces RDS load for read-heavy workloads. Monitor cache hit rates alongside database query times; a dropping hit rate often indicates a missing index or inefficient Eloquent relationship before users notice slowdowns. Refer to the Laravel performance optimization guide for application-level tuning that complements infrastructure optimizations.

Final Checklist for Production Readiness

Successfully deploying Laravel on AWS EC2 with RDS means verifying every layer before going live. Confirm that your EC2 instances lack public IPs, your RDS security group references only the app tier, secrets are injected dynamically, and your deployment pipeline supports atomic rollbacks. Enable automated backups with point-in-time recovery for RDS, and test restoration procedures quarterly. Set up CloudWatch alarms for CPU, memory, and disk thresholds with SNS notifications to your on-call channel.

If your team needs help designing a compliant, scalable AWS architecture for Laravel or auditing an existing deployment, reach out to discuss your infrastructure requirements. Whether you're building for a Nepal-based audience or global users, getting the foundation right prevents costly rearchitecture later. Start with the VPC design, automate your deployments early, and treat observability as a first-class feature — not an afterthought.

Frequently Asked Questions

The t4g.medium instance offers the best price-to-performance ratio for most Laravel applications running PHP 8.4 or newer. It provides sufficient CPU credits for typical web traffic while keeping monthly costs under twenty dollars before storage and data transfer fees.

Configure your DB_HOST environment variable to use the RDS endpoint and ensure both resources share the same VPC. Never expose RDS publicly; instead, rely on security groups allowing port 3306 traffic only from your EC2 instance ID.

Self-hosting saves licensing fees but increases operational overhead significantly. For production Laravel apps, RDS Multi-AZ deployments justify their cost through automated backups, patching, and failover capabilities that prevent revenue loss during database outages.

Check security group rules first. Ensure inbound TCP 3306 allows traffic from your EC2 private IP. Also verify the RDS subnet group includes at least two availability zones and that route tables permit internal VPC communication correctly.

Yes, absolutely required for production. Use ElastiCache Redis for session storage, queue drivers, and caching to reduce RDS load. Configure Laravel CACHE_DRIVER and SESSION_DRIVER to redis in your environment file for optimal performance.

Use Supervisor to manage multiple php artisan queue:work processes. Configure maxTries and timeout values matching your longest job duration. Monitor failed jobs via Horizon and set up CloudWatch alarms for queue depth exceeding acceptable thresholds.

Expect fifty to eighty dollars monthly minimum. This covers a t4g.medium EC2, db.t4g.micro RDS instance, 20GB gp3 storage, and basic NAT Gateway usage. Costs increase with Multi-AZ, larger instances, or significant outbound data transfer.

Use AWS CodeDeploy with blue-green deployment strategy or simple rolling updates via GitHub Actions. Store artifacts in S3, trigger deployments through webhooks, and run php artisan migrate:fresh only after confirming new code health checks pass successfully.

Yes, it eliminates .env files on disk entirely. Grant EC2 IAM role permissions to read specific parameters, then use the aws-sdk-php package to fetch secrets at bootstrap. Rotate credentials without redeploying application code or restarting services.

Profile database queries first using Telescope or Debugbar. Missing indexes on Eloquent relationships cause N+1 problems that no amount of vertical scaling fixes. Enable RDS Performance Insights to identify expensive queries, then add composite indexes matching your actual access patterns.

Not initially, but Application Load Balancer becomes necessary before adding second instances. It terminates SSL, handles health checks, and enables sticky sessions if not using centralized Redis. Start with CloudFront for static assets to reduce EC2 load.

Set opcache.memory_consumption to 256M and opcache.max_accelerated_files to 20000 minimum. Enable opcache.validate_timestamps=0 in production and deploy via atomic symlink swaps. Restart PHP-FPM after each deployment to clear stale bytecode cache safely.

Enable automated RDS snapshots with seven-day retention plus manual snapshots before major deployments. Export critical data to S3 weekly using AWS Backup. Test restores quarterly in staging environment to verify recovery time objectives match business requirements.

Integrate Sentry or Flare for real-time exception tracking. Forward PHP-FPM and Nginx logs to CloudWatch Logs via unified CloudWatch agent. Create metric filters for 5xx responses and fatal errors, triggering SNS alerts when thresholds exceed baseline rates.

Upgrade when sustained CPU utilization exceeds seventy percent during peak hours or when credit balance consistently depletes. Memory-optimized r7g instances suit apps with large dataset caching needs, while compute-optimized c7g benefits CPU-intensive PDF generation or image processing workloads.