Deploy Laravel to AWS the Production Way
Everything you need to ship a Laravel app on EC2 with Nginx, RDS, ALB, and CloudFront — no fluff, just steps.
Overview
This guide describes a general production-style Laravel deployment on AWS using:
- EC2 as the application server
- Nginx as the web server
- PHP-FPM as the PHP runtime
- RDS as the managed database
- ALB as the load balancer
- CloudFront as the CDN layer
The examples use placeholders such as <app-name>, <repository-url>, and <domain-name>. Do not publish real credentials, private repository URLs, internal hostnames, IP addresses, access keys, or .env values in deployment notes.
Prerequisites
- The server is Ubuntu-based.
- HTTPS terminates at CloudFront or the ALB.
- EC2 receives traffic over HTTP from the load balancer.
- The Laravel app lives under
/var/www/<app-name>. - Database credentials are stored in
.envor a secrets manager, not in the deployment script.
1. Enable Basic User Data Logging
If this deployment is automated through EC2 User Data, start the script with logging and strict failure handling:
exec > /var/log/user-data.log 2>&1
set -euo pipefail
This makes boot-time failures easier to debug and prevents the script from continuing after a failed command.
2. Update the Server and Install Core Packages
sudo apt update
sudo apt upgrade -y
sudo apt install -y git unzip curl nginx jq software-properties-common
Core packages:
| Package | Purpose |
|---|---|
git | Clone application source code |
unzip | Extract downloaded archives |
curl | Download installation scripts and release files |
nginx | Serve HTTP traffic |
jq | Parse JSON when needed |
software-properties-common | Manage package repositories |
3. Install PHP and Laravel Extensions
Choose one PHP version and keep it consistent across PHP-FPM, Composer, and Nginx.
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
PHP_VERSION=8.3
sudo apt install -y \
php${PHP_VERSION} \
php${PHP_VERSION}-fpm \
php${PHP_VERSION}-mysql \
php${PHP_VERSION}-xml \
php${PHP_VERSION}-mbstring \
php${PHP_VERSION}-curl \
php${PHP_VERSION}-zip \
php${PHP_VERSION}-bcmath \
php${PHP_VERSION}-gd
php -v
Common Laravel extensions support database access, string handling, XML parsing, HTTP clients, archives, math operations, and image processing.
4. Install Composer
sudo apt install -y composer
composer --version
For stricter production builds, install dependencies without development packages:
composer install --no-dev --optimize-autoloader
Run this command inside the application directory after the repository has been cloned.
5. Install Node.js for Frontend Builds
Laravel projects that use Vite need Node.js to build frontend assets.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.nvm/nvm.sh
nvm install 22
node -v
npm -v
Use the Node.js version required by the project. If the project has .nvmrc, use:
nvm install
nvm use
6. Prepare the Application Directory
sudo mkdir -p /var/www/<app-name>
sudo chown -R ubuntu:ubuntu /var/www/<app-name>
cd /var/www/<app-name>
Use a non-root user for application commands where possible.
7. Clone the Application Repository
git clone <repository-url> .
git checkout main
Public-safe notes:
- Use a placeholder in documentation instead of a real private repository URL.
- Use SSH deploy keys or a CI/CD pipeline for private repositories.
- Do not store Git credentials in User Data.
8. Create the Environment File
cp .env.example .env
Update .env manually or through a secure deployment process.
The .env file usually contains:
APP_KEYAPP_ENVAPP_DEBUG- Database connection settings
- Cache, queue, session, and mail settings
- Third-party service credentials
Security guidance:
- Never commit
.env. - Never paste real
.envvalues into public notes. - Prefer AWS Systems Manager Parameter Store, Secrets Manager, or CI/CD secrets for production.
- Set
APP_DEBUG=falsein production.
Generate the Laravel app key:
php artisan key:generate
9. Install Backend Dependencies
composer install --no-dev --optimize-autoloader
If this is a first deployment, run migrations only after confirming database connectivity and backup strategy:
php artisan migrate --force
10. Build Frontend Assets
npm ci
npm run build
This compiles production assets into Laravel's public build output.
11. Set Laravel File Permissions
Laravel needs write access to storage and bootstrap/cache.
sudo chown -R ubuntu:www-data /var/www/<app-name>
sudo find /var/www/<app-name> -type f -exec chmod 0644 {} \;
sudo find /var/www/<app-name> -type d -exec chmod 0755 {} \;
sudo chown -R www-data:www-data /var/www/<app-name>/storage
sudo chown -R www-data:www-data /var/www/<app-name>/bootstrap/cache
sudo chmod -R 0775 /var/www/<app-name>/storage
sudo chmod -R 0775 /var/www/<app-name>/bootstrap/cache
sudo chown ubuntu:www-data /var/www/<app-name>/.env
sudo chmod 0640 /var/www/<app-name>/.env
Avoid making the whole application directory world-writable.
12. Optimize Laravel for Production
php artisan config:cache
php artisan route:cache
php artisan view:cache
When configuration changes, clear and rebuild the cache:
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
13. Configure Nginx for Laravel
Create an Nginx site file:
sudo nano /etc/nginx/sites-available/<app-name>
Example configuration:
server {
listen 80;
server_name _;
root /var/www/<app-name>/public;
index index.php;
charset utf-8;
client_max_body_size 50M;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location = /health {
access_log off;
default_type text/plain;
return 200 "OK";
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico {
access_log off;
log_not_found off;
}
location = /robots.txt {
access_log off;
log_not_found off;
}
error_page 404 /index.php;
location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param HTTPS $http_x_forwarded_proto if_not_empty;
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
fastcgi_param HTTP_X_FORWARDED_HOST $http_x_forwarded_host;
fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Make sure the PHP-FPM socket path matches the installed PHP version.
14. Enable the Site and Reload Services
sudo ln -sfn /etc/nginx/sites-available/<app-name> /etc/nginx/sites-enabled/<app-name>
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm
Use nginx -t before reloading so syntax errors do not break the active web server.
15. Connect ALB, CloudFront, and RDS
Typical request flow:
User
-> CloudFront
-> ALB
-> EC2 running Nginx and PHP-FPM
-> RDS
Recommended setup:
- CloudFront uses HTTPS for public traffic.
- ALB terminates HTTPS or receives traffic from CloudFront.
- EC2 security groups allow HTTP only from the ALB security group.
- RDS security groups allow database access only from the application security group.
- EC2 uses an IAM role instead of static AWS access keys.
16. Post-Deployment Checks
curl -I http://localhost/health
sudo nginx -t
sudo systemctl status nginx --no-pager
sudo systemctl status php8.3-fpm --no-pager
php artisan about
Also confirm:
- The Laravel home page loads through the domain.
- Static assets load without mixed-content warnings.
- Application logs write correctly to
storage/logs. - Database migrations have completed successfully.
APP_DEBUG=falseis set in production.
Security Checklist
- Keep
.envout of Git. - Use placeholders in public documentation.
- Do not expose real private repository URLs.
- Do not publish real domains, internal IPs, database endpoints, usernames, or credentials unless they are intentionally public.
- Use IAM roles instead of long-lived AWS access keys.
- Restrict SSH access and prefer Session Manager where possible.
- Restrict EC2 inbound traffic to the ALB security group.
- Restrict RDS inbound traffic to the application security group.
- Enable application, Nginx, ALB, CloudFront, and database logs according to your retention policy.
- Keep OS packages, PHP, Composer dependencies, and Node dependencies patched.