Dev Portfolio
Back to Writings

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.

Jun 7, 2026

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 .env or 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:

PackagePurpose
gitClone application source code
unzipExtract downloaded archives
curlDownload installation scripts and release files
nginxServe HTTP traffic
jqParse JSON when needed
software-properties-commonManage 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_KEY
  • APP_ENV
  • APP_DEBUG
  • Database connection settings
  • Cache, queue, session, and mail settings
  • Third-party service credentials

Security guidance:

  • Never commit .env.
  • Never paste real .env values into public notes.
  • Prefer AWS Systems Manager Parameter Store, Secrets Manager, or CI/CD secrets for production.
  • Set APP_DEBUG=false in 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=false is set in production.

Security Checklist

  • Keep .env out 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.