Install and Configure Laravel 13 with Nginx on Ubuntu 24.04: A Step-by-Step Guide Without Production Surprises

Half of the problems people run into with Laravel on a live server have nothing to do with application code — they come from the PHP-FPM / Nginx / file permissions stack. A blank white screen instead of your site, a 502 Bad Gateway right after deployment, permission denied errors in storage — these are classic issues almost everyone hits when moving Laravel from a local Sail environment to a real VPS. Over the past several years I’ve deployed Laravel on dozens of servers, and this article walks through a working sequence of steps for Laravel 13 on Ubuntu 24.04 with Nginx, including the exact spots where people usually lose hours debugging.

Laravel 13 was released in March 2026 and requires PHP 8.3 as a minimum. The good news: Ubuntu 24.04 LTS ships with PHP 8.3 out of the box in its repositories, so in most cases you won’t need to pull in a third-party PPA just to get the right version.

What You’ll Need Before Starting

  • A server running Ubuntu 24.04 (a fresh VPS or a clean install) with SSH access and sudo privileges.
  • A domain or test subdomain, if you plan to configure HTTPS.
  • A database — the examples here use MySQL 8, though PostgreSQL works just as well.
  • 15–20 minutes of free time and some patience for file permissions — they’re usually what ruins the mood at the very last step.

Step 1: Update the System and Install Base Packages

Always start with a system update — it eliminates a good chunk of odd package-version conflicts down the line:

bash

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl unzip git software-properties-common

Don’t skip apt upgrade. Fresh images from VPS providers often ship with outdated ca-certificates packages, which can later cause curl or Composer to fail with SSL errors when fetching dependencies.

Step 2: Install PHP 8.3 and the Required Extensions

Laravel 13 needs PHP 8.3 or higher. On Ubuntu 24.04 it’s already in the main repository, so the ondrej/php PPA isn’t strictly required — but if you want guaranteed access to future PHP 8.4 minor releases, it’s still worth adding:

bash

sudo add-apt-repository ppa:ondrej/php -y
sudo apt update

Install PHP-FPM along with the extensions Laravel needs to either run at all or avoid misbehaving on specific features:

bash

sudo apt install -y php8.3-fpm php8.3-cli php8.3-common \
  php8.3-mysql php8.3-pgsql php8.3-mbstring php8.3-xml \
  php8.3-curl php8.3-zip php8.3-bcmath php8.3-gd php8.3-intl php8.3-redis

Pitfall #1: a missing php8.3-mbstring or php8.3-xml won’t show up as an installation error — it’ll show up during composer install, with a vague message about a missing extension. It’s simpler to install the full set up front than to guess later what’s missing.

Verify the version:

bash

php -v

Make sure the output shows 8.3.x specifically, not an older system php-cli version — on servers with a history of upgrades, multiple PHP versions sometimes coexist, and update-alternatives can point to the wrong one.

Step 3: Install Composer

Composer is PHP’s dependency manager, and there’s no building Laravel without it:

bash

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
composer --version

Step 4: Install Laravel 13

There are two workable approaches: the global Laravel installer or Composer directly. The second is more reliable in CI/CD scenarios, so it’s the one I typically use on servers:

bash

composer create-project laravel/laravel example-app "13.*"
cd example-app

If you’re cloning an existing project repository instead, run composer install --no-dev --optimize-autoloader in place of create-project. The --no-dev flag is critical in production — it excludes development-only packages like Pest or Laravel Telescope, which have no business being in a production build.

Set up the environment:

bash

cp .env.example .env
php artisan key:generate

Edit .env to set your database parameters, APP_URL, and — most importantly for production — APP_ENV=production and APP_DEBUG=false. Leaving APP_DEBUG enabled on a live server isn’t a minor oversight: any unhandled error will show visitors a full stack trace, including server paths, package versions, and sometimes the contents of .env.

Step 5: File Permissions — The Source of Most Problems

Nginx and PHP-FPM typically run as the www-data user. The storage and bootstrap/cache directories need write access, or Laravel won’t be able to write logs, cache files, and compiled Blade templates:

bash

sudo chown -R $USER:www-data /var/www/example-app
sudo find /var/www/example-app -type f -exec chmod 664 {} \;
sudo find /var/www/example-app -type d -exec chmod 775 {} \;
sudo chmod -R ug+rwx storage bootstrap/cache

Pitfall #2: the quick-and-dirty fix of running chmod -R 777 storage does technically work, but it’s a security hole — any process on the server gains write access to those directories. The correct approach is group ownership by www-data with 775 permissions, as shown above.

Step 6: Install and Configure Nginx

bash

sudo apt install -y nginx

Create a configuration file for the site:

bash

sudo nano /etc/nginx/sites-available/example-app

Here’s a base Nginx configuration for Laravel, tested across a number of projects:

nginx

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example-app/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    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 ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

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

Three details people most often overlook:

  1. root points to the public folder, not the project root. This is the single most common reason a visitor sees a file listing instead of the site — or worse, gets direct access to the .env source file.
  2. The PHP-FPM socket path must match the actual PHP version in use. If you have multiple versions installed, php8.3-fpm.sock might not match the version active in the CLI — check with ls /run/php/.
  3. The location ~ /\.(?!well-known).* block blocks access to dotfiles, including .env and .git. Without it, .env can theoretically be downloaded directly via URL if the configuration is misconfigured elsewhere — this is a real vulnerability that automated scanners actively look for.

Enable the configuration:

bash

sudo ln -s /etc/nginx/sites-available/example-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Running nginx -t before reload is not optional — it checks the syntax and saves you from taking down a working site over a typo in the config.

Step 7: Configure the Database and Run Migrations

bash

sudo mysql -e "CREATE DATABASE laravel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -e "CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'strong_password';"
sudo mysql -e "GRANT ALL PRIVILEGES ON laravel_db.* TO 'laravel_user'@'localhost';"

Enter these credentials in .env, then run the migrations:

bash

php artisan migrate --force

The --force flag is necessary because in a production environment Laravel asks for confirmation before running migrations by default — without the flag, the command will simply hang inside an interactive prompt during your deploy script.

Step 8: Cache Configuration and Optimize for Production

bash

php artisan config:cache
php artisan route:cache
php artisan view:cache
composer install --optimize-autoloader --no-dev

Pitfall #3, the most annoying one: if you’ve run config:cache and then change a variable in .env and simply reload the site — the change won’t take effect. Laravel will read the cached config, not the .env file. After any edit to .env in production, always run php artisan config:clear and re-run config:cache afterward. This detail breaks deploy scripts more often than you’d expect.

Step 9: HTTPS via Let’s Encrypt

bash

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Certbot will automatically add the port-443 block and set up the redirect from HTTP. Verify certificate auto-renewal:

bash

sudo certbot renew --dry-run

Verifying the Result

Open the site in your browser. If you see a 502 Bad Gateway error, it’s almost always an incorrect PHP-FPM socket path in the Nginx config, or the php8.3-fpm service isn’t running (sudo systemctl status php8.3-fpm). If you see a blank white page with no visible error, temporarily set APP_DEBUG=true, check the actual error in your browser or in storage/logs/laravel.log, and then make sure to set APP_DEBUG=false again afterward.

FAQ

What PHP version does Laravel 13 require? Laravel 13 requires PHP 8.3 at minimum. Ubuntu 24.04 includes PHP 8.3 in its standard repositories, so an additional PPA isn’t required for a basic install.

Can I use Apache instead of Nginx for Laravel 13? Yes, Laravel isn’t tied to a specific web server. That said, Nginx as a reverse proxy in front of PHP-FPM generally performs better under high load thanks to its asynchronous connection-handling model.

Why does the site return a 502 Bad Gateway after deployment? The most common cause is an incorrect path to the PHP-FPM unix socket in the Nginx configuration, or the php8.3-fpm service being stopped. Check systemctl status php8.3-fpm and the socket path under /run/php/.

Should I use chmod 777 for the storage folder? No, that’s an unsafe practice. The correct approach is to make www-data the group owner and set permissions to 775, as described in Step 5.

What if changes to .env don’t take effect after editing it? If config:cache was run previously, Laravel reads the cached configuration instead of .env directly. Run php artisan config:clear, then config:cache again if needed.

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *