Cover Image
#laravel #linux #nginx

The Ultimate Laravel Deployment Guide

22nd Jan 2022
Mohammed Omer By Mohammed Omer

What is this?

This is a detailed guide to deploying a laravel app to a VPS or a dedicated server. There are awesome paid tools like laravel forge to manage your server and laravel envoyer for zero down-time deployments, but if you are just starting out and don’t have the budget for these tools ,you will have to do everything yourself, this guide will help you do exactly that.


  • A VPS or a dedicated server with ssh access and root access.


  1. Installing Nginx
  2. Installing php and some of its extensions
  3. Installing composer
  4. Installing MySQL and running the secure installation command
  5. Create a new database and a database user to have access to that database
  6. Setting up zero-downtime deployments and making the required permission changes
  7. Configuring Nginx
  8. Adding free, auto-renewable SSL certificates using certbot
  9. Installing and setting up supervisor to run our laravel queue workers
  10. Adding a cron-job for scheduled tasks
  11. Increasing maximum upload file size in Nginx and php level
Before we start, make sure you have ssh access to the server that you want to host your website in, and then ssh into the server and execute the following steps. If you haven’t added your ssh key to your server yet, you can follow this guide

Step 1, Installing Nginx:

To install Nginx and then check if it is running execute the following commands

sudo apt install nginx
sudo systemctl nginx status

you should see an output that contains Active: active (running)

Step 2, Installing php and some of its extensions:

We first need to import ondrej/php PPA to be able to install the latest version of php:

sudo apt install software-properties-common && sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt upgrade -y

now we can install php along with the extensions that you will probably need (here we use php8.1, you can change this to the version your application requires)

sudo apt install php8.1 php8.1-gd php8.1-fpm php8.1-curl php8.1-intl php8.1-mysql php8.1-redis php8.1-sqlite3 php8.1-zip php8.1-xml php8.1-mbstring

Step 3, Installing composer:

Composer is the package manager that makes it easy to install our project php dependencies.

To install the latest version of composer you will need to go to the official download page and follow the instructions.

Step 4, Installing MySQL

sudo apt install mysql-server

The second command will walk you through some questions to secure MySQL, for any question you can press y or Y to answer with YES, or any other key to answer with NO.

for the question VALIDATE PASSWORD COMPONENT I press n as I don’t like MySQL to force me to write a secure password (not that I’ll choose a week password though)😶.

Next, you will have to enter a new password for the root MySQL user (you won’t be able to see star characters or anything on the terminal while you are typing, but it is working). Next, it will ask you to confirm your password by entering it again.

Next question is: Remove anonymous users? to which you may want to type y so that no one without an account can log into MySQL.

you will also need to respond with y for the Disallow root login remotely question. This will ensure that no one will be able to access MySQL from a host other than localhost (the same machine)

if you want to access the database from your local machine you can create an ssh tunnel to inspect your database and maybe run some queries locally.

Next, you will be asked to remove the test database and access to it, press y

you will then be asked to reload privilege tables so that all changes made so far will take effect immediately so answer with y

and now we have a secure MySQL installation!

Step 5, Updating MySQL root user and allow password login using MySQL root user:

To be able to access the database from within our laravel app we have to allow logging into MySQL with a username and password. It is always a good idea to create a new user for your database instead of using the root user.

sudo mysql -u root

After logging into MySQL as a root user you will have to create a new database and a new user for that database (make sure you save the database name, the username and password because we will need them later when setting environment variables).

CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON project.* TO 'username'@'localhost';

make sure to change username and password to the username and password you want for your database.

Step 6, Setting up zero-downtime deployments and making the required permission changes:

Zero downtime deployment allows you to deploy new changes without getting your site down. The approach we are going to take here is creating a new bare repository on our server so that we can push changes from our local machine directly to the server. I recommend this for small websites and when you are the only developer working on the project, but for larger projects and with more team members you may consider creating a CI/CD pipeline to automatically deploy changes when pushing to your github repo master branch for example. For this tutorial, we will make it simple and stick to the first approach.

We will be serving our project from /var/www/project folder, this is where our laravel application will reside. We need to change the ownership of this folder to our currently logged in user and create our project folder here, to do so we will run:

cd /var/www/ && sudo chown ${USER} . && mkdir project

Next we will be setting up our bare repo at /var/projects/project to accept pushes from our local machine and deploy those changes to our laravel app at /var/www/project.

cd /var/projects/
sudo chown ${USER} .
mkdir project
cd project
git init --bare

Then, we will create a post-receive hook to apply changes to our project when we push to this bare repo from our local machine.

cd hooks
touch post-receive
chmod +x post-receive
here you must have to be able to run sudo without being asked for a password. To do this run `sudo visudo` and edit the file and add the following line at the end of the file `username ALL=(ALL) NOPASSWD:ALL` then reload the terminal session or open a new one

Then run the command nano post-receive to edit it and paste the following code:


cd /var/www/project

echo "## Copying files"
git --work-tree=/var/www/project --git-dir=/var/projects/project checkout -f

echo "## Running composer install"
composer install --no-interaction --prefer-dist --optimize-autoloader
echo "## Changing permission"
sudo chown -R :www-data .

echo "## Reloading php-fpm"
sudo service php8.1-fpm reload
echo "## Restarting queue workers via supervisor"
sudo supervisorctl restart all
echo "## Running migrations"
php artisan migrate --force
echo "## Running cache commands"
php artisan config:cache
php artisan route:cache
php artisan view:cache

This will handle copying all incoming changes when we push to the server and then run some commands to ensure that we install new packages if added and reload our php service and laravel queue workers (which we will create using supervisor). It will also run the migrations and clear the cache.

Setting up our local machine to push changes to our server:

Now we will switch from the server to our local machine where we are developing our laravel app. You should have git initialized in your laravel app. Navigate to your app folder and add a new remote repo called production:

git remote add production ssh://

Change user to your username on the server, and to your domain name if you set it up, or you can set to the IP address of the server if you haven’t yet pointed your domain name to the server.

Now you can run the command git push production master to push your project from your local machine to the server. It will not be accessible yet, we have to configure Nginx for the site to work which we will do in the next step.

Now back to the server, run the following commands to create your .env file and generate an application key:

cd /var/www/project
cp .exmaple.env .env
php artisan key:generate

Now add all necessary configuration to your .env file, including your database credentials:


Now we will make sure that files and folder permissions are right:

cd /var/www/project && sudo find . -type f -exec chmod 644 {} \;
cd /var/www/project && sudo find /project -type d -exec chmod 755 {} \;
sudo chown -R $USER:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap

Step 7, Configuring Nginx:

We will need to configure Nginx to proxy web incoming request to php-fpm and set the root folder to our project public folder /var/www/project/public.

To edit Nginx configuration run:

sudo nano /etc/nginx/sites-available/default

and then paste this code:

server {

    root /var/www/project/public;

    index index.php index.html index.htm index.nginx-debian.html;


    add_header X-Frame-Option "SAMEORIGIN";

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

    location ~ \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
            fastcgi_index index.php;
            include fastcgi_params;
              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

make sure to change /var/www/project/public to your laravel application public folder and set []( to your domain name or IP address, also if you are using a different php version you should edit fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; to reflect your php version.

Now run sudo systemctl restart nginx and if everything went fine you will be able to access your website!

Step 8, Adding free, auto-renewable SSL certificates using certbot (requires having a domain name):

certbot will install a letsencrypt free SSL certificate on your site to enable https connections.

To install and add a certificate to your site run the following commands(snap package manager is required):

sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx

It will ask you to choose which site you want to add SSL certificate to, choose your website and it will generate the certificate and will auto-renew it so you don’t have to think about manually renewing your certificate.

To test automatic auto-renewal run the command sudo certbot renew --dry-run

Step 9, Installing and setting up supervisor to run our laravel queue workers:

supervisor is a process control system that allows its users to monitor and control a number of processes on UNIX-like operating systems. We will be using it to manage our laravel queue workers (if your application doesn’t make use of laravel queues you can skip this step). To install supervisor run:

sudo apt install supervisor

Then we will create a configuration file for supervisor to handle our queue workers:

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

paste the following inside the file:

command=php /var/www/project/artisan queue:work --tries=3

This will instruct supervisor to create 5 processes to handle our laravel queue jobs. Make sure to replace the path /var/www/project to match your project path.

The run sudo supervisorctl reread to read new changes.

Run sudo supervisorctl update to run the new command and keep the queue running even after a system reboot.

To see running processes in supervisor run sudo supervisorctl

Step 10, Adding a cron-job for scheduled tasks:

To make sure that tasks defined on app/Console/Kernel.php run as expected, you have to add a crontab entry by executing:

crontab -e

At the end of the file that is open by this command, add the following line:

* * * * * cd /var/www/project && php artisan schedule:run >> /dev/null 2>&1

Make sure to update /var/www/project to your laravel project folder, This will run the command php artisan schedule:run on your laravel app every minute, making sure all your schedule commands on app/Console/Kernel.php run as expected.

Step 11, Increasing maximum upload file size in Nginx and php level:

By default, Nginx has an upload limit of 1MB, this means if your users are uploading files larger than 1MB nginx will reject the request. To increase this limit we will have to edit the file /etc/nginx/nginx.conf so run the command

sudo nano /etc/nginx/nginx.conf

and add the line following (change the size to what makes sense for your app):

http {
  client_max_body_size 25M;

make sure the client_max_body_size is inside of the http directive.

Also, php-fpm has a default file upload size of 2MB and we need to increase this as well.

sudo nano /etc/php/8.0/fpm/php.ini

Now modify the upload_max_filesize and post_max_size variables:

upload_max_filesize = 20M
post_max_size = 22M

Also change the size to what makes sense for your app.

If you liked this post consider sharing it :

You may also like

stopwatch6 MINS 
By Mohammed Omer | 15th Mar 2022
#laravel #tooling #php
stopwatch6 MINS 
By Mohammed Omer | 15th Jan 2022
#laravel #testing
stopwatch4 MINS 
By Mohammed Omer | 1st Dec 2021
#laravel #security #testing
stopwatch4 MINS 
By Mohammed Omer | 21st Dec 2021
#laravel #performance
stopwatch3 MINS 
By Mohammed Omer | 7th Dec 2021
#laravel #linux
stopwatch2 MINS 
By Mohammed Omer | 27th Mar 2022
#laravel #productivity #tooling #shorts 🔥