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.
certbot
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)
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
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.
sudo apt install mysql-server
mysql_secure_installation
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!
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 DATABASE project;
CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON project.* TO 'username'@'localhost';
FLUSH PRIVILEGES;
make sure to change username and password to the username and password you want for your database.
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
Then run the command nano post-receive
to edit it and paste the following code:
#!/bin/bash
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.
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://user@domain.com:/var/projects/project
Change user
to your username on the server, and domain.com
to your domain name if you set it up, or you can set domain.com
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:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db_database
DB_USERNAME=db_username
DB_PASSWORD=db_password
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 -type d -exec chmod 755 {} \;
sudo chown -R $USER:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap
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;
server_name domain.com;
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 domain.com
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!
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
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:
[program:queue-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/project/artisan queue:work --tries=3
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=5
redirect_stderr=true
stdout_logfile=/var/www/project/storage/logs/queue.log
stopwaitsecs=3600
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.
Then 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
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.
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.