Tutorial · Laravel

Install Laravel 13 on a Debian 13 server with nginx

5 chapters · 1 hr 15 · Intermediate · Composer, Debian · verified on 7 September 2026

Quick answer

On Debian 13, PHP 8.4 comes from the official repositories: no third-party repository is needed. Install PHP and its extensions, then Composer (checking its signature), create the project with composer create-project laravel/laravel, give write access to storage/ and bootstrap/cache/, then point the nginx root at public/. Check the result: the home page must answer 200 and /.env must answer 403.

This guide runs through the full installation of a Laravel 13 application on a Debian 13 server, from a bare system to a page served by nginx. Every step was executed on a clean Debian 13 “trixie” on 2 September 2026, and the end result checked: HTTP 200 on the home page, and the .env file unreachable from the web.

Part of the Web development learning path. Five of the steps have a detailed chapter of their own, listed in the sidebar. What follows is the complete sequence, with just enough of each step to reach the end.

What you need before you start

  • A Debian 13 “trixie” server reachable over SSH. Debian 12 works too, with PHP 8.2.
  • A non-root user with sudo. Never install or deploy as root.
  • A development machine on your side. On macOS, the usual blocker is the laravel command not being found.
  • A domain name pointing at the server, if you plan on HTTPS.

Create the user that will own the application, if you have not already:

bash
sudo adduser deploy
sudo usermod -aG sudo deploy

1. PHP and its extensions

Debian 13 ships PHP 8.4, which saves you the third-party Sury repository:

bash
sudo apt update
sudo apt install -y php-cli php-fpm \
  php-mbstring php-xml php-curl php-zip \
  php-bcmath php-intl php-mysql php-sqlite3 \
  unzip curl ca-certificates

php -v   # PHP 8.4.24

The full list of required extensions, the php.ini settings and the steps for installing another PHP version are in the chapter devoted to PHP.

2. Composer

Install it while checking the hash of the installer, as the official documentation requires:

bash
EXPECTED="$(curl -sS https://composer.github.io/installer.sig)"
curl -sS https://getcomposer.org/installer -o composer-setup.php
ACTUAL="$(php -r 'echo hash_file("sha384", "composer-setup.php");')"

if [ "$EXPECTED" = "$ACTUAL" ]; then
    sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer
fi
rm composer-setup.php

composer --version   # Composer version 2.10.3

3. Create the Laravel project

As the deploy user, never as root:

bash
su - deploy
composer create-project laravel/laravel /home/deploy/app --no-interaction
cd /home/deploy/app
php artisan --version   # Laravel Framework 13.30.1

create-project writes the .env file, generates the application key and runs the migrations against the default SQLite database. For MySQL or MariaDB, whose installation has a chapter of its own, fill in the connection then run the migrations again:

.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=un_mot_de_passe_solide
bash
php artisan migrate --force

4. File permissions

This is the step people skip, and it produces the blank page, or a 500 error. The folders concerned are storage_path() and bootstrap/cache, described in the application paths, plus database/ as long as the application uses the SQLite database created at install time: the session driver writes to it on every visit. The web server must be able to write to these three folders, and only these:

bash
sudo chown -R deploy:www-data /home/deploy/app/storage /home/deploy/app/bootstrap/cache /home/deploy/app/database
sudo chmod -R 775 /home/deploy/app/storage /home/deploy/app/bootstrap/cache /home/deploy/app/database

If the project lives in a user’s home directory, nginx has to be able to traverse it:

bash
sudo chmod o+x /home/deploy
Never chmod -R 777

It is the most widespread piece of advice and the most dangerous: it makes the application writable by every account on the server. 775 with the right group is enough, and that is what was used for the verification.

5. nginx and PHP-FPM

This section installs nginx. If you would rather use Apache, more common on shared hosting, follow the chapter devoted to Apache instead: the rest of the guide still holds.

bash
sudo apt install -y nginx php-fpm

Note the exact socket path, it carries the PHP version number:

bash
grep -h '^listen' /etc/php/8.4/fpm/pool.d/www.conf
# listen = /run/php/php8.4-fpm.sock

Create the site file. The root points at public/, never at the project root:

/etc/nginx/sites-available/laravel
server {
    listen 80;
    server_name exemple.com;
    root /home/deploy/app/public;
    index index.php;

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

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
    }

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

Enable the site, disable the default one, check the syntax:

bash
sudo ln -s /etc/nginx/sites-available/laravel /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
code
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

6. Check the installation

Two checks are enough to tell whether the installation holds up:

bash
curl -s -o /dev/null -w "page d'accueil : %{http_code}\n" http://127.0.0.1/
curl -s -o /dev/null -w ".env          : %{http_code}\n" http://127.0.0.1/.env
code
page d'accueil : 200
.env          : 403

The second one matters most. A 200 on /.env would mean your database credentials and your APP_KEY are publicly readable. The location ~ /\.(?!well-known).* block is what prevents it, and it is why the site root has to be public/.

7. Move to production

The development configuration leaks information. Three lines in the .env:

.env
APP_ENV=production
APP_DEBUG=false
APP_URL=https://exemple.com

Then the caches, to be regenerated after every deployment:

bash
composer install --no-dev --optimize-autoloader
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan about --only=environment   # Debug Mode must show OFF

That setting is covered in detail in the article on debug mode.

Add HTTPS with Certbot, which edits the nginx configuration itself:

If the application exposes public forms, plan for protection against automated submissions as well.

bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d exemple.com

Finally, the task scheduler, if the application uses one:

bash
sudo crontab -u deploy -e
# * * * * * cd /home/deploy/app && php artisan schedule:run >> /dev/null 2>&1

For cache, sessions and queues on Redis, carry on with the chapter devoted to Redis.

Start the tutorial

Common errors

nginx root on the project instead of public/ The whole source tree, including the .env file, becomes downloadable. Always check that /.env returns 403.
chmod -R 777 on storage Makes the application writable by every account on the server. 775 with the www-data group is enough.
Composer run with sudo Dependency scripts execute as root, and the files they create are no longer writable by the web server.
Hard-coded PHP-FPM socket The path contains the version number: /run/php/php8.4-fpm.sock. Read it from the pool rather than copying it across.
Home directory that cannot be traversed A project in /home/deploy requires chmod o+x /home/deploy, otherwise nginx returns a permission error.
Forgetting to regenerate the caches After a deployment, config:cache, route:cache and view:cache must be run again, otherwise the old configuration stays active.
Newsletter

New tests, tutorials and projects, by e-mail.

Reproducible tests, versioned code, dated results. Never any spam.