
August 17, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Deploying a production application requires more than just launching a virtual machine; understanding how to host a Laravel app on AWS (EC2 + RDS + S3) is essential for building scalable, secure systems that separate compute, database, and storage concerns. While managed platforms like Vapor or Forge simplify this process, many Nepal-based businesses and international clients require direct infrastructure ownership for compliance, cost control, or specific architectural needs. This guide provides the exact configuration patterns I use when setting up this stack manually for legal-tech portals and eCommerce platforms.
How do you architect the VPC for Laravel on AWS?
Before touching the Laravel installer, you must establish a secure network foundation. A common mistake I see in audits for Laravel development projects is placing RDS instances in public subnets or opening port 3306 to 0.0.0.0/0. For a production Laravel environment, your VPC architecture must enforce isolation by default.
Your VPC should contain at least two public subnets across different Availability Zones (AZs) for high availability of your load balancer or NAT gateway, and two private subnets for your RDS instances. The EC2 instance running Laravel typically sits in a private subnet behind a NAT Gateway if it doesn't need direct inbound internet access, or in a public subnet if you are managing a single-server setup directly. For most small-to-medium business applications I build, a single public-facing EC2 with strict security groups is the pragmatic starting point before scaling to auto-scaling groups.
Security groups act as your primary firewall. Your EC2 security group should only allow inbound traffic on ports 80, 443, and 22 (restricted to your specific IP address). Your RDS security group must never allow 0.0.0.0/0. Instead, configure it to accept connections exclusively from the EC2 security group ID. This ensures that even if your database has a misconfigured route table, no external actor can attempt authentication.
How do you configure EC2 and PHP-FPM for Laravel 12?
For Laravel 12.x in 2026, PHP 8.4 is the recommended stable release, though PHP 8.3 remains fully supported. When provisioning your EC2 instance, choose Ubuntu 24.04 LTS for long-term stability. Avoid using the default Amazon Linux unless your team has specific expertise with it; Ubuntu's PPA ecosystem makes managing multiple PHP versions significantly easier for Laravel developers.
Installing PHP 8.4 and Required Extensions
Laravel requires several PHP extensions that aren't installed by default. On a fresh Ubuntu 24.04 server, add the Ondřej Surý PPA to get current PHP builds:
sudo apt update && sudo apt upgrade -y
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y php8.4-fpm php8.4-cli php8.4-mysql php8.4-pgsql \
php8.4-sqlite3 php8.4-gd php8.4-curl php8.4-mbstring \
php8.4-xml php8.4-zip php8.4-bcmath php8.4-intl \
php8.4-readline php8.4-redis nginx mysql-client unzip git After installation, tune PHP-FPM for your instance size. The default pool configuration at /etc/php/8.4/fpm/pool.d/www.conf is conservative. For a t3.medium or similar, adjust these values based on available RAM (each PHP worker consumes roughly 30-50MB):
- pm = dynamic: Allows workers to scale up and down based on load.
- pm.max_children = 30: Adjust based on RAM. Formula: (Total RAM - System Reserve) / Average Process Size.
- pm.start_servers = 10: Roughly 30% of max_children.
- pm.min_spare_servers = 5: Keeps warm workers ready for traffic spikes.
- pm.max_spare_servers = 15: Prevents excessive idle memory usage.
Nginx Configuration for Laravel
Create a dedicated site configuration at /etc/nginx/sites-available/laravel. Ensure you set fastcgi_buffer_size and fastcgi_buffers appropriately to handle large headers from Laravel sessions or JWT tokens:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/html/current/public;
index index.php;
charset utf-8;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
fastcgi_busy_buffers_size 24k;
}
location ~ /\.(?!well-known).* {
deny all;
}
} Always validate configuration with sudo nginx -t before reloading. Enable the site and disable the default:
sudo ln -s /etc/nginx/sites-available/laravel /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo systemctl reload nginx
sudo systemctl restart php8.4-fpm How do you connect Laravel to RDS and S3 securely?
The core advantage of learning how to host a Laravel app on AWS (EC2 + RDS + S3) lies in offloading state. Your EC2 instance should be treated as ephemeral; persistent data belongs in managed services.
RDS Connection Without Exposing Credentials
When creating your RDS instance, select "Do not create an automated backup" initially if testing, but always enable Multi-AZ and automated backups for production. Choose MySQL 8.4 LTS or PostgreSQL 16/17 depending on your application's requirements. For most Laravel apps I maintain, MySQL 8.4 offers the best balance of Eloquent compatibility and performance.
In your Laravel .env file, reference the RDS endpoint directly. Never use the root master user for your application. Create a dedicated database user with limited privileges:
DB_HOST=your-rds-instance.xxxxx.ap-south-1.rds.amazonaws.com
DB_PORT=3306
DB_DATABASE=laravel_production
DB_USERNAME=laravel_app_user
DB_PASSWORD=${SSM_PARAMETER_OR_SECRETS_MANAGER}
DB_SSLMODE=require Enforce SSL connections in your RDS parameter group and verify connectivity using the AWS-provided certificate bundle. This prevents credential sniffing within the VPC.
S3 Integration for Stateless Media Storage
Local filesystem storage breaks horizontal scaling. Configure Laravel's filesystem to use S3 for all user uploads, especially critical for legal-tech portals handling document evidence or eCommerce product imagery. Install the AWS SDK:
composer require league/flysystem-aws-s3-v3 "^3.0" Instead of hardcoding AWS keys in .env, assign an IAM Role to your EC2 instance with a policy granting s3:PutObject, s3:GetObject, and s3:DeleteObject only to your specific bucket. Laravel will automatically retrieve temporary credentials from the instance metadata service.
FILESYSTEM_DISK=s3
AWS_BUCKET=your-laravel-media-bucket
AWS_REGION=ap-south-1
AWS_USE_PATH_STYLE_ENDPOINT=false For public files, configure CloudFront as a CDN in front of S3. Direct S3 buckets should generally remain private, with access mediated through signed URLs or the CDN. This pattern is essential for protecting sensitive documents in law firm portals I've built.
What is the best deployment strategy for Laravel on EC2?
Manual git pull deployments cause downtime and inconsistency. In my experience maintaining sister sites like notarykathmandu.com and translationnepal.com on shared EC2 infrastructure, Deployer 7 with zero-downtime symlinked releases is the industry standard for self-managed Laravel hosting.
Zero-Downtime Deployment Structure
Deployer creates atomic releases in /var/www/html/releases/{timestamp} and symlinks /var/www/html/current to the latest successful build. Shared resources like .env, storage/, and vendor directories persist across deployments. This means a failed deploy simply leaves the old symlink intact—zero user impact.
Install Deployer globally on your local machine or CI runner:
composer global require deployer/deployer:^7.0 A minimal deploy.php for Laravel on AWS:
<?php
namespace Deployer;
require 'recipe/laravel.php';
set('application', 'laravel-app');
set('repository', 'git@github.com:org/repo.git');
set('php_version', '8.4');
host('production')
->set('hostname', 'ec2-x-x-x-x.compute.amazonaws.com')
->set('remote_user', 'ubuntu')
->set('deploy_path', '/var/www/html')
->set('labels', ['stage' => 'prod']);
task('deploy:opcache_reset', function () {
run('{{bin/php}} -r "opcache_reset();"');
});
after('deploy:symlink', 'deploy:opcache_reset');
after('deploy:failed', 'deploy:unlock'); Handling Queues and Scheduled Tasks
AWS EC2 doesn't manage Laravel queues automatically. You must configure Supervisor to keep queue workers alive. Create /etc/supervisor/conf.d/laravel-worker.conf:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/current/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/html/current/storage/logs/worker.log For scheduled tasks, add a single cron entry that runs every minute. Laravel's scheduler handles the rest:
* * * * * cd /var/www/html/current && php artisan schedule:run >> /dev/null 2>&1 If you're comparing hosting options for a client project, review AWS cloud hosting versus shared hosting to understand when this complexity is justified over simpler alternatives.
| Component | Development | Production (AWS) | Why It Matters |
|---|---|---|---|
| Storage | Local disk | S3 + CloudFront | Enables horizontal scaling without sticky sessions |
| Database | Docker/Local MySQL | RDS Multi-AZ | Automated backups, failover, patch management |
| Cache/Sessions | File/Array | ElastiCache Redis | Shared state across multiple EC2 instances |
| Deployment | Git pull / Artisan serve | Deployer 7 + Symlinks | Atomic releases, instant rollback capability |
| Secrets | .env in repo | SSM Parameter Store | No credentials on disk, audit trail access |
How do you monitor and optimize costs for Laravel on AWS?
AWS bills can spiral quickly without guardrails. For Nepal-based clients paying in NPR, even small inefficiencies compound significantly. Implement these controls from day one.
Right-sizing: Start with a t3.medium (2 vCPU, 4GB RAM) for most Laravel applications. Monitor CPU utilization and memory pressure via CloudWatch for two weeks before upsizing. Over-provisioning is the most common cost leak I encounter in inherited projects.
Reserved Instances: Once your workload stabilizes, purchase Compute Savings Plans or Reserved Instances for 1-year terms. This typically reduces EC2 and RDS costs by 30-40% compared to on-demand pricing. For a typical Laravel setup, this can save Rs 15,000–25,000/month (~USD 110–185).
S3 Lifecycle Policies: Configure Intelligent-Tiering for media buckets. Legal documents and eCommerce images often have declining access patterns after 30 days. Automatic tiering moves infrequently accessed objects to cheaper storage without application changes.
Monitoring Stack: Install the CloudWatch agent on your EC2 instance to capture memory and disk metrics (not available by default). Set alarms for CPU > 80%, memory > 90%, and disk > 85%. Integrate with Laravel's logging via CloudWatch Logs Insights to query errors without SSH access. For deeper application performance monitoring, consider Laravel Telescope in staging environments only—never enable it in production without IP restriction.
Final Steps for Production Readiness
Understanding how to host a Laravel app on AWS (EC2 + RDS + S3) is only the beginning. Before going live, verify SSL termination via ACM or Let's Encrypt, confirm backup restoration procedures, test queue failure handling, and audit IAM permissions against least-privilege principles. Document your infrastructure as code using Terraform or AWS CDK to prevent manual drift.
If you're evaluating whether to manage this stack internally or need expert guidance on hiring a Laravel developer in Nepal for ongoing maintenance, proper planning now prevents costly rearchitecture later. For teams needing hands-on implementation support or a technical audit of your existing AWS setup, reach out to discuss your project requirements.

