[ Back to log ]

Laravel deployment guide: from scratch to production

Laravel

Deploying Laravel manually isn’t as simple as dragging and dropping files. You have to wire up Nginx, PHP-FPM, a database, and background workers. It’s tedious, but doing it once helps you understand exactly how your stack works.

Here is how to take a Laravel app from a GitHub repo to a live server.

1. Initial server setup

Start by getting the basic server packages in place.

Connect and update

SSH into your server and update your packages:

ssh user@your_server_ip
sudo apt update && sudo apt upgrade -y

Configure the firewall

Lock down your ports so you don’t leave database or Redis ports open to the internet. We’ll also rate-limit SSH to slow down brute-force attempts.

sudo apt install ufw -y
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw logging off
sudo ufw enable
sudo ufw limit ssh
sudo ufw status

Install Nginx and Certbot

Grab Nginx and Certbot (for SSL later):

sudo apt install nginx certbot python3-certbot-nginx -y

2. Application setup

Clone the repository

Create a directory for your site and pull your code.

sudo mkdir -p /var/www/example.com
# Take ownership so you don't need sudo to run git
sudo chown -R $USER:$USER /var/www/example.com
cd /var/www/example.com
git clone <repository-url> .

Configure Nginx

Create a new Nginx config file:

sudo nano /etc/nginx/sites-available/example.com

Paste the following configuration (adapted from the official Laravel documentation). Be sure to update server_name and root to match your domain and path, and ensure the fastcgi_pass matches your PHP version (we will use PHP 8.3 as an example in the next step):

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    root /var/www/example.com/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 ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; # Update 8.3 to your PHP version
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

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

Enable the site:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

Install PHP and Composer

Install PHP 8.x (we’ll use 8.3 as an example) along with the extensions Laravel needs, plus Composer:

sudo apt install php8.3-fpm php8.3-curl php8.3-common php8.3-cli php8.3-mbstring php8.3-xml php8.3-zip php-bcmath php-curl composer -y

(If you use a different PHP version, replace 8.3 with your version).

3. Database setup

You don’t always need a heavy database server. SQLite is actually great for small to medium apps now. But if you need MariaDB, here’s how to set it up.

Option A: SQLite

Install SQLite:

sudo apt install php-sqlite3 sqlite3 -y

In your Laravel .env file, set the connection to sqlite and delete the other DB variables:

DB_CONNECTION=sqlite

Create the empty database file:

touch database/database.sqlite

Option B: MariaDB (MySQL)

Install MariaDB and the PHP MySQL extension (matching your PHP version):

sudo apt install mariadb-server php8.3-mysql -y
sudo mysql_secure_installation

Log into MariaDB:

sudo mysql

Create the database and user:

CREATE DATABASE laravel;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON laravel.* TO 'laravel_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Update your .env file:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel_user
DB_PASSWORD=your_password

4. Laravel configuration

Set directory permissions

Nginx runs as www-data. It needs permission to write to your storage and cache directories:

sudo chown -R www-data:www-data /var/www/example.com/storage
sudo chown -R www-data:www-data /var/www/example.com/bootstrap/cache

(If you chose SQLite, give Nginx access to the database file too):

sudo chown -R :www-data /var/www/example.com/database
sudo chmod -R 775 /var/www/example.com/database

Install dependencies and migrate

Install your PHP packages, generate the app key, and run migrations:

composer install --no-dev --optimize-autoloader
cp .env.example .env
# Edit .env now to add your database credentials and set APP_ENV=production and APP_DEBUG=false
nano .env

php artisan key:generate
php artisan migrate --force

5. Queue setup (Supervisor)

If you dispatch jobs or send emails in the background, you can’t just run queue:work and close your terminal. Supervisor keeps that process alive.

Install Supervisor:

sudo apt install supervisor -y

Create a config file for your worker:

sudo nano /etc/supervisor/conf.d/laravel-worker.conf

Add this configuration:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/example.com/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/nginx/worker.log
stopwaitsecs=3600

Start the worker:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start "laravel-worker:*"

6. Final configuration

Create a deployment script

You don’t want to run all these commands manually every time you push code. Create a quick bash script:

nano /var/www/example.com/update.sh
#!/bin/bash
cd /var/www/example.com

php artisan down
git pull
composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
php artisan migrate --force
php artisan optimize:clear
php artisan optimize
php artisan queue:restart
php artisan up

Make it executable:

chmod +x /var/www/example.com/update.sh

Secure with SSL and reload Nginx

Get your SSL certificate:

sudo certbot --nginx -d example.com -d www.example.com

Test auto-renewal:

sudo certbot renew --dry-run

Reload Nginx:

sudo nginx -t && sudo systemctl reload nginx

Your app is live.