Laravel deployment guide: from scratch to production
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.