Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

How to Host a Laravel App on AWS (EC2 + RDS + S3)

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.

VPC: 10.0.0.0/16Public Subnet (10.0.1.0/24)EC2 (Laravel + Nginx)PHP 8.4 / PHP-FPMPrivate Subnet (10.0.2.0/24)RDS MySQL 8.4No Public IPPort 3306 OnlyInternet Gateway / NAT
Secure VPC topology isolating RDS from public internet while allowing EC2 Laravel traffic

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.

Laravel App(EC2)Amazon S3Media / DocumentsAmazon RDSMySQL 8.4CloudFrontCDN CacheIAM Role AuthSSL Encrypted
Stateless Laravel data flow separating media (S3) and relational data (RDS)

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.

ComponentDevelopmentProduction (AWS)Why It Matters
StorageLocal diskS3 + CloudFrontEnables horizontal scaling without sticky sessions
DatabaseDocker/Local MySQLRDS Multi-AZAutomated backups, failover, patch management
Cache/SessionsFile/ArrayElastiCache RedisShared state across multiple EC2 instances
DeploymentGit pull / Artisan serveDeployer 7 + SymlinksAtomic releases, instant rollback capability
Secrets.env in repoSSM Parameter StoreNo 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.

Start: On-DemandStable Workload > 3 Months?YesNoReserved InstanceSpot / Auto-ScaleSave 30-40%Pay Per Use
Decision framework for optimizing AWS costs for Laravel workloads

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.

Frequently Asked Questions

A production-ready setup with a t3.small EC2 instance, db.t3.micro RDS, and minimal S3 storage typically costs Rs 4,000–6,000 per month (USD 30–45). Costs scale significantly with traffic, database size, and data transfer out of AWS. Always use the AWS Pricing Calculator for accurate estimates based on your specific workload before committing.

The t3.small or t3.medium instances offer the best price-to-performance ratio for most Laravel apps running PHP 8.3 or 8.4. These burstable instances handle typical web traffic spikes efficiently without the premium cost of dedicated compute. I recommend starting with t3.small and upgrading only when CPU credit exhaustion causes latency, rather than over-provisioning initially.

Use RDS for any business-critical application despite the higher cost. In my experience managing legal-tech portals and eCommerce sites, RDS eliminates database administration overhead like patching, backups, and replication setup. Self-hosted MySQL on EC2 saves roughly Rs 1,500 monthly but risks data loss during failures and requires manual maintenance windows that disrupt service availability.

Install the league/flysystem-aws-s3-v3 package via Composer and set FILESYSTEM_DISK=s3 in your environment variables. Configure AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, and AWS_BUCKET in your .env file. For production applications handling sensitive documents like legal filings, always enable server-side encryption and restrict bucket policies to prevent public access unless explicitly required.

PHP 8.2 minimum.

I use Deployer 7 with GitLab CI to achieve zero-downtime deployments on EC2. The pipeline builds frontend assets locally, uploads them as artifacts, runs composer install on the server, then atomically swaps the release symlink. This approach avoids installing Node.js on production servers and ensures PHP-FPM opcache invalidation happens automatically after each deploy, preventing stale code issues I have encountered repeatedly.

Choose EC2 when you need granular control over networking, compliance requirements, or integration with other AWS services like S3 and RDS. Laravel Forge with DigitalOcean is superior for teams wanting managed server provisioning without DevOps overhead. On client projects where budget matters more than ecosystem integration, I often recommend Forge because it reduces setup time from days to hours while maintaining production reliability.

Never commit .env files to version control. Store secrets in AWS Systems Manager Parameter Store or Secrets Manager and inject them at runtime using IAM roles instead of hardcoded credentials. On EC2, restrict file permissions to 600 for .env and ensure the web server user owns the file. I have seen too many breaches caused by exposed credentials in git history or overly permissive file access on shared hosting environments.

This usually indicates PHP-FPM is not running or the socket path in Nginx configuration does not match the actual FPM socket location. Check systemctl status php8.3-fpm and verify the listen directive matches your Nginx fastcgi_pass setting. After deployments, always reload PHP-FPM to clear opcache. Permission issues on the storage directory also cause silent failures that manifest as 502 errors under load.

Use Certbot with Let's Encrypt for free automated SSL certificates. Install certbot and python3-certbot-apache or nginx plugin, then run certbot --nginx -d yourdomain.com. Configure automatic renewal via systemd timer or cron. For production systems handling payments or legal documents, I strongly recommend enabling HSTS headers and redirecting all HTTP traffic to HTTPS at the Nginx level rather than relying on Laravel middleware alone.

Run queue workers as systemd services with supervisor or native systemd units for automatic restart on failure. Store failed jobs in the database for debugging and retry logic. For high-volume applications, consider moving queues to AWS SQS to decouple processing from web servers. On smaller projects, I keep queues on the same EC2 instance but monitor memory usage closely since queue workers can consume significant RAM during peak processing periods.

Enable OPcache with validate_timestamps=0 in production and preload frequently used classes. Cache routes, config, views, and events using php artisan optimize. Use Redis on ElastiCache or self-hosted for session and cache storage instead of file-based drivers. Configure PHP-FPM pm.max_children based on available RAM divided by average process memory. These optimizations routinely reduce response times by 40-60% on client applications I have maintained.

Only for development or testing.

Configure CORS in config/cors.php to allow your specific frontend domains rather than using wildcards in production. When serving assets from S3 via CloudFront, set CORS headers on the S3 bucket policy and CloudFront behavior separately since they are evaluated independently. Test thoroughly with actual browser requests because misconfigured CORS between Laravel API and S3-hosted frontends is a frequent source of hard-to-diagnose authentication failures in SPAs.

Enable automated RDS snapshots with point-in-time recovery retained for at least seven days. Schedule nightly mysqldump exports to S3 with lifecycle policies for long-term archival. Back up EC2 EBS volumes daily using AWS Backup or snapshot automation. For applications like legal portals where document integrity is critical, I also implement application-level backups of uploaded files to a separate S3 bucket with versioning enabled to protect against accidental deletion or ransomware.

Share this article

Quick Contact Options
Choose how you want to connect me: