Translations of this page:
  • en

In this wiki page, I will be telling you how to deploy a laravel application on a VDS server. I used Debian 12 OS and assume the bible of the page is Laravel Deployment page.

1- Essentials for Server

Essetial steps for the deployment written here.

Step 1: Setup the Server and Install the OS

First of all we need to setup the VDS or VPS server. After the setup, we should install an OS. I prefer to install Debian12. Then you can connect the server over SSH connection with the ROOT credentials (root, password or SSH key-pair).

ssh root@SERVER_IP_ADDR

You will be keep using the root account until creating a new user.

Step 2: Update & Upgrade

After OS installation, we need to update and upgrade the system.

apt update
apt upgrade

Step 3: Firewall

We should install, edit and activate the UFW (Uncomplicated Firewall). UFW is easy to use and effective solution for firewall. Let's install:

apt install ufw

Secondly, defaults must be set up. For normal users the following defaults will do just fine.

ufw default deny incoming
ufw default allow outgoing

After the installation we need to set the firewall rules & ports. The ports we will allow are listed below:

Port Role
22 Used for SSH connection
80 Used for HTTP requests
443 Used for HTTPS requests

To allow requests from the given ports you should allow them one by one with the given commands:

ufw allow ssh # or sudo ufw allow 22 
ufw allow http # or sudo ufw allow 80 
ufw allow https # or sudo ufw allow 443

You can check the port statuses / actions with the following command

ufw status

If the ports look as given above, you can activate the ufw as default firewall with the given command below:

ufw enable

2- Create a User

In Linux, creating user accounts is essential for managing access, improving security, and organizing a multi-user environment. Each user account has its own permissions, file system space, and unique credentials, ensuring controlled access and preventing unauthorized modifications.

adduser <USERNAME> # for example: adduser vicky

enter a password for the vicky and re-enter the password. It will ask for other informations such name, email, etc, you can fill the fields however you want.

Use the usermod command to add the user to the sudo group:

usermod -aG sudo <USERNAME> #for example: usermod -aG sudo vicky

If everything works as expected you can test sudo access. To test that the new sudo permissions are working, first use the su command to switch to the new user account:

su - <USERNAME> # for example: su - vicky

It will ask you for the password, if it accept to log in, it's okay; it's done. You can access the server with the new user and its credentials right now. To do this, type exit on the console and end the current root ssh connection. Afterwards:

ssh USERNAME@SERVER_IP_ADDR

For example:

ssh vicky@123.45.67.89

We will use this user exclusively for the rest of this article. Now we are done with the Root account. After a successfull login of the created user, you can run update and upgrade commands to be sure is server up-to-date or not.

sudo apt update 
sudo apt upgrade 

3- Bind a Domain

Connect a domain to a server can be done by simply changing the DNS records of the domain. You will typically manage these records through your domain registrar's website or a separate DNS hosting provider's control panel. Lets assume the VDS server's IP address as 100.45.213.77.

Here is an example DNS record table you might configure:

Type Name Content TTL
A @ 100.45.213.77 Auto
A * 100.45.213.77 Auto
A sub1 100.45.213.77 Auto
AAAA @ Public IPv6 Auto
  • @ stands for the base domain address (often called the root domain or FQDN) like vicky.com.
  • For IPv6, you should create an AAAA record pointing to your server's public IPv6 address.
  • * stands for a wildcard, representing all subdomains that don't have a specific A record defined, like anything.vicky.com.
  • sub1: I wrote sub1 but it can be anything. I'll assume secret instead of sub1, this record stands for secret.vicky.com.
  • You can add more specific subdomains as needed by creating additional A records.
  • TTL (Time To Live) is the duration that DNS resolvers are told to cache your record before requesting a fresh copy. 'Auto' is usually fine, letting the provider decide.

 Example screenshot of a DNS configuration interface (placeholder)

You don't have to add explicit records for every single subdomain if you use the * wildcard record. However, note that an explicit record for a specific subdomain will take precedence over the wildcard record.

DNS Propagation: After saving your DNS changes, it can take some time for these updates to propagate across the internet's DNS servers. This process, called propagation, can range from a few minutes to several hours, sometimes up to 48 hours, though typically faster for A records. You can use online tools or command-line utilities like dig or nslookup to check the status of your DNS propagation from different locations.

Once these DNS records are pointing to your server's IP address, you will configure your web server (Nginx) in the next section to respond to requests for these domain names.

4- Nginx Installation & Configuration

nginx (“engine x”) is an HTTP web server, reverse proxy, content cache, load balancer, TCP/UDP proxy server, and mail proxy server. Before nginx installation we should be ensure about apache/apache2 is not running and enabled.

sudo systemctl status apache2

If apache2 is exists and running, we should stop:

sudo systemctl stop apache2

After stopping the apache2, it is a good practice to check the updates

sudo apt update && sudo apt upgrade

If everything is ok, let's get nginx:

sudo apt install nginx

After the installation completed, we mainly focused on two folders:

  1. /etc/nginx/sites-available/, and
  2. /etc/nginx/sites-enabled/

sites-available: This folder includes the configuration files for the domains and applications that we'll have.

sites-enabled: This folder includes symbolic links (symlink) of the configuration files.

Step 1: Create Configuration File

We need to create configuration file for each domain. Lets assume the main domain is vicky.com and subdomain is secret.vicky.com. Let's start with the main domain configuration.

cd /etc/nginx/sites-available
sudo touch vicky.conf #in general: sudo touch domain.conf
sudo touch secret.vicky.conf # in general: sudo touch sub.domain.conf

We need to write the configurations in it. Here is the general configuration for Laravel applications. Also you can read details from the Laravel's page.

vicky.conf
server {
    listen 80;                       # Listen port 80 for IPv4
    listen [::]:80;                  # Listen port 80 for IPv6
    server_name vicky.com;           # Domain Address
    root /var/www/vicky.com/public;  # Location of web application
 
    #add_header X-Frame-Options "SAMEORIGIN";
    #add_header X-Content-Type-Options "nosniff";
 
    index index.html index.php;
 
    charset utf-8;
 
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}
sub.vicky.conf
server {
    listen 80;
    listen [::]:80;
    server_name secret.vicky.com;
    root /var/www/secret.vicky.com/public;
 
    #add_header X-Frame-Options "SAMEORIGIN";
    #add_header X-Content-Type-Options "nosniff";
 
    index index.html index.php;
 
    charset utf-8;
 
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}

You can google the different types of configurations and apply your own configuration, but this template is a general and basic solution. You can copy and paste the given configurations, and use nano to do that:

sudo nano vicky.conf

Then CTRL+VPress XPress Y (To accept the changes) → Press Enter. And, it's done, all changes saved into the configuration file.

Let's move to the sites-enabled path.

cd /etc/nginx/sites-enabled

Now, we should create symlinks for each of the domains:

sudo ln -s /etc/nginx/sites-available/vicky.conf .
sudo ln -s /etc/nginx/sites-available/secret.vicky.conf .

You can check current symlinks with the following commands:

ls -l

After creating the symlinks, you can control the current configurations with the following command:

sudo nginx -t

Step 3: Create Application Folders

cd /var/www/
mkdir vicky.com
mkdir secret.vicky.com

After the configurations are done, reload the nginx to be ensure changes are applied:

sudo systemctl reload nginx

Also you can create public folders to test the current setup. If you want to test the setup, just create the public folder and create an index.html file in it. We did not install PHP yet so just create HTML file instead. Hereby, enter the domain address on your browser and it will display the page that you prepared in index.html file.

5- SSL Certification: Certbot

It is essential to secure your application with SSL/TLS encryption to protect data in transit and build user trust. Let's Encrypt provides free, automated, and open Certificate Authorities, and Certbot is a tool to easily obtain and manage these certificates, especially for web servers like Nginx.

You mentioned using “Full SSL Encryption” on Cloudflare. This means Cloudflare encrypts the connection between the user and Cloudflare, and also encrypts the connection between Cloudflare and your origin server. For Cloudflare's “Full SSL” to work correctly, your server still needs a valid SSL certificate. Certbot will provide this valid certificate.

To install Certbot and the Nginx plugin, first ensure your package list is up-to-date:

sudo apt update && sudo apt upgrade

Now we are ready to install Certbot:

sudo apt install certbot python3-certbot-nginx

After installation is completed, we will use the Certbot Nginx plugin, which can automatically configure SSL for domains specified in your Nginx configuration files located in /etc/nginx/sites-enabled/.

sudo certbot --nginx

When you run this command, Certbot will:

  • Prompt you for an email address for urgent renewal or security notices.
  • Ask you to agree to the terms of service.
  • Scan your Nginx configuration files for server_name directives to find the domains you want to secure.
  • Present a list of identified domains and ask you to select which ones you want certificates for.
  • Communicate with the Let's Encrypt servers to verify domain ownership (usually by serving a temporary file through your web server).
  • If successful, it will obtain the SSL certificates.
  • Automatically modify your Nginx configuration file(s) in /etc/nginx/sites-enabled/ to:
    • Add the listen 443 ssl; directive for HTTPS.
    • Point to the correct ssl_certificate and ssl_certificate_key files it just obtained.
    • (Optionally) Ask if you want to redirect HTTP traffic (port 80) to HTTPS (port 443). It is highly recommended to choose the redirect option for better security and SEO.
  • Automatically set up a renewal mechanism (usually a systemd timer or cron job) that will attempt to renew your certificates before they expire (Let's Encrypt certificates are valid for 90 days).

After Certbot completes its process and potentially modifies your Nginx configuration, it will usually handle reloading or restarting Nginx itself. However, it's a good practice to test the Nginx configuration and explicitly reload to be sure:

sudo nginx -t # Test Nginx configuration for syntax errors
sudo systemctl reload nginx # Reload Nginx to apply changes

You can verify that SSL is working by visiting your domain(s) in a web browser and checking for the padlock icon in the address bar and ensuring the URL starts with https://.

Important: The certbot --nginx command only needs to be run the first time to obtain the certificate and configure Nginx. Certbot's automatic renewal process handles keeping your certificates up-to-date in the future.

6- PHP Installation

Let's proceed to uninstall the currently installed PHP packages and then install a specific version (like PHP 8.2) cleanly using the Ondřej Surý PPA, which is the recommended way for up-to-date PHP versions on Ubuntu/Debian.

Step 1: Uninstalling Existing PHP Packages

To install PHP and relevant packages, we can follow an easy way. Before the beginning, it is good to check is there any other version of PHP's or its packages installed or not. You can run the following command to list the currently installed PHP packages:

apt list --installed | grep php

The given command will display the installed packages that includes 'PHP' word. If there are any packages, it would be good to remove them with the following commands:

sudo apt remove php php-bcmath <PACKAGE_NAMES>

After removing, autoremove will clean up orphaned dependencies:

sudo apt autoremove

You might also want to purge configuration files (optional, but good for a “clean” state):

sudo apt purge php php-bcmath <PACKAGE_NAMES>

And then:

sudo apt autoremove --purge

Step 2: Add the Ondřej Surý PPA (if not already added)

The PPA provides up-to-date PHP versions for Ubuntu/Debian. If you've already done this for a previous PHP attempt, you might not need to re-add it, but an update is good.

sudo apt update
sudo apt install -y software-properties-common ca-certificates apt-transport-https
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update

You might get error while you want to add the relevant repository.You can examine the following page for an expected error that I get.

Step 3: Install PHP 8.2 (or your desired version) and Required Extensions

Now, install PHP 8.2 and all the necessary extensions for laravel.

sudo apt install -y php8.2 php8.2-fpm php8.2-cli php8.2-common php8.2-mysql php8.2-curl php8.2-bcmath php8.2-mbstring php8.2-xml php8.2-tokenizer php8.2-zip php8.2-gd php8.2-intl php8.2-opcache
  • php8.2: Meta-package, might not be strictly necessary if you list specifics.
  • php8.2-fpm: Crucial for Nginx. This is the FastCGI Process Manager.
  • php8.2-cli: Command-line interface (for Composer, Artisan commands).
  • php8.2-common: Common files.
  • php8.2-mysql: For MySQL database connectivity.
  • php8.2-curl: For making HTTP requests (Guzzle, etc.).
  • php8.2-bcmath: For arbitrary precision mathematics.
  • php8.2-mbstring: For multibyte string functions.
  • php8.2-xml: For XML manipulation.
  • php8.2-tokenizer: Required by Laravel for parsing PHP code.
  • php8.2-zip: For handling zip archives.
  • php8.2-gd: For image manipulation (if your app needs it).
  • php8.2-intl: For internationalization features.
  • php8.2-opcache: For performance (bytecode caching).

Step 4: Verify Installation

php -v
# Should show PHP 8.2.x
 
php -m
# Should list installed modules, including the ones you just installed (e.g., bcmath, curl, mbstring, mysql, opcache, xml, zip, etc.)

Step 5: Restart PHP-FPM and Nginx

sudo systemctl restart php8.2-fpm
sudo systemctl enable php8.2-fpm # Ensure it starts on boot
sudo systemctl status php8.2-fpm # Check it's running
 
# Ensure your Nginx config still points to the correct socket
# fastcgi_pass unix:/run/php/php8.2-fpm.sock; # Or /var/run/php/php8.2-fpm.sock
# Make sure it matches what's in www.conf
 
sudo nginx -t # Test Nginx configuration
sudo systemctl reload nginx # If test is successful

Now, try accessing your domain.xyz site to see the expected page that you can prepared.

7- MySQL Installation

For installing MariaDB, you can follow the instructions given on their page. Select your operating system and follow the instructions. Here is the brief installation guide.

Step 1: Install MariaDB

With the given command, you will be able to access MySQL.

sudo apt-get update
sudo apt-get install mariadb-server

After installation you should run the mysql_secure_installation.

Step 2: MySQL Secure Installation

Run the following command:

sudo mysql_secure_installation

It will ask you for root password

In order to log into MariaDB to secure it, we'll need the current
password for the root user. If you've just installed MariaDB, and
haven't set the root password yet, you should just press enter here.
 
Enter current password for root (enter for none): 
# Press Enter

Then maybe after some questions, it will ask you for setting a new root password.

Change the root password? [Y/n] Y
New password: # Enter a New Root Password
Re-enter new password: # Enter Again the New Root Password
Password updated successfully!

Then when you see the following message, it means the installation of MySQL is done and ready to go:

All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.
 
Thanks for using MariaDB!

Step 3: Create a User

Let's enter into the MySQL engine with the following command:

sudo mysql -u root -p<ENTER_ROOT_PASSWORD>

Now we need to create a user:

CREATE USER 'USER_NAME'@'localhost' IDENTIFIED BY 'PASSWORD';

After creating the user you can control the user from the following table:

SHOW DATABASES;
USE mysql;
SHOW TABLES;
SELECT * FROM user;

Then it will display the users. Now the next step is creating a new database for the Laravel project and set grant privileges to the user that we created on the database.

Step 4: Create a New Database

CREATE DATABASE laravel_db

You can change the database name however you want. For the tutorial I set the name as laravel_db.

Step 5: Grant Privileges

Now we should set grant privileges to user on the laravel_db database.

GRANT ALL PRIVILEGES ON `laravel_db`.* TO 'USER_NAME'@'localhost';

It's good practice to flush the privileges to ensure the changes take effect immediately without needing to restart the MySQL server.

FLUSH PRIVILEGES;

Now, if you get Query OK message, it's time to exit.

exit;

Step 6: MySQL Service Chech

Now it is time to check the service:

sudo systemctl status mysql
sudo systemctl status mariadb

If they are not activated and enabled, we should do:

sudo systemctl enable mysql
sudo systemctl start mysql
sudo systemctl enable mariadb
sudo systemctl start mariadb

8- Install Composer

Download the composer installer file:

wget -O composer-setup.php https://getcomposer.org/installer

To install Composer globally inside the /usr/local/bin directory by running the following command.

sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer

If a new Composer version is available, update the package by running the following command.

composer self-update

Finally, you can run the following command to see the composer version:

composer -V

9- Clone the Repository

We will use git to fetch the project into the folder. I'll use `kkrgzz/vocabx-api` as an example project. You can use your own.

git clone https://github.com/kkrgzz/vocabx-api 

Copy whole files in the project into the root folder (/var/www/vicky.com/). You are ready to configure the file permissions and ownership.

10 - Configure File Permissions

We need to set the ownership of the folder.

sudo chown -R username:username /var/www/example_domain.com/

For example:

sudo chown -R vicky:vicky /var/www/vicky.com/

Laravel will need to write to the bootstrap/cache and storage directories, so you should ensure the web server process owner has permission to write to these directories.

We need to set the directory permissions. If you wonder about the file permissions you can deep dive the Arch Linux Wiki Page

The method given below is not a safe approach!

sudo chmod -R 0777 storage
sudo chmod -R 0777 bootstrap/cache

Using chmod -R 0777 is indeed a significant security risk because it grants full read, write, and execute permissions to all users on the system for those directories and everything within them. This means if any other user account on your server were compromised, they could potentially modify or inject malicious files into your application's storage or cache directories.

Laravel - Safer & More Standard Approach

The goal is to grant write permissions only to the specific user or group that the web server (Nginx/PHP-FPM) runs as, while keeping ownership and primary access with your deployment user.

On Debian systems, the web server user and group is typically www-data.

Here's a safer and more standard approach:

Step 1: Ensure Correct Ownership

First, make sure the entire application directory is owned by your deployment user (vicky) and their primary group (vicky). You already have this step, which is correct:

sudo chown -R vicky:vicky /var/www/vicky.com/

Step 2: Change Group Ownership for Writable Directories

Change the group ownership of the storage and bootstrap/cache directories (and their contents) to the web server group (www-data).

sudo chgrp -R www-data /var/www/vicky.com/storage
sudo chgrp -R www-data /var/www/vicky.com/bootstrap/cache

Step 3: Set Appropriate Permissions

Now, set the permissions. A common pattern is 775 for directories and 664 for files.

  • 775 for directories means:
    • Owner (vicky): Read, Write, Execute (7) - Can list contents, create/delete files, navigate into.
    • Group (www-data): Read, Write, Execute (7) - Can list contents, create/delete files, navigate into.
    • Others: Read, Execute (5) - Can list contents, navigate into, but cannot write or delete.
  • 664 for files means:
    • Owner (vicky): Read, Write (6) - Can read and modify the file.
    • Group (www-data): Read, Write (6) - Can read and modify the file.
    • Others: Read (4) - Can only read the file.

You can apply these recursively using find for better control over files vs. directories:

# Set permissions for directories within storage and bootstrap/cache
sudo find /var/www/vicky.com/storage -type d -exec chmod 775 {} +
sudo find /var/www/vicky.com/bootstrap/cache -type d -exec chmod 775 {} +
 
# Set permissions for files within storage and bootstrap/cache
sudo find /var/www/vicky.com/storage -type f -exec chmod 664 {} +
sudo find /var/www/vicky.com/bootstrap/cache -type f -exec chmod 664 {} +

Note: You could also do sudo chmod -R 775 /var/www/vicky.com/storage /var/www/vicky.com/bootstrap/cache as a simpler command, but this would also give execute permissions to files, which is usually not necessary and slightly less secure than 664 for files. Using find is more precise.

To ensure that any new files or directories created within storage and bootstrap/cache by the web server process (running as www-data) automatically inherit the www-data group, you can set the SetGID bit on these directories.

sudo chmod g+s /var/www/vicky.com/storage
sudo chmod g+s /var/www/vicky.com/bootstrap/cache

Static App - Appropriate Permissions

Generally do not need to set the same 775 group write permissions or change the group ownership to www-data for your static application's folder. The write operations happen during the deployment process when you, as the deployment user (vicky), build the app locally (or on the server) and copy the output files to the server directory /var/www/static-page.vicky.com.

The goal for static assets is to allow the web server (www-data user) to read and execute/navigate through the directories to find the files, while ensuring your deployment user (vicky) has full control to update the files, and restricting write access for others.

A standard and safe permission set for static web content folders is:

Ownership: The files and directories should be owned by your deployment user (vicky) and their primary group (vicky).

sudo chown -R vicky:vicky /var/www/static-page.vicky.com

Permissions:

  • Directories: 755 (Owner: Read, Write, Execute; Group: Read, Execute; Others: Read, Execute). This allows the owner (vicky) full control and allows the web server user (www-data - which falls under “Others” unless specifically added to vicky's group) to navigate into directories and read their contents.
  • Files: 644 (Owner: Read, Write; Group: Read; Others: Read). This allows the owner (vicky) to read and modify the files and allows the web server user (www-data) to read the files.

You can set these permissions recursively:

# Ensure ownership is correct
sudo chown -R vicky:vicky /var/www/static-page.vicky.com
 
# Set directory permissions to 755
sudo find /var/www/static-page.vicky.com -type d -exec chmod 755 {} +
 
# Set file permissions to 644
sudo find /var/www/static-page.vicky.com -type f -exec chmod 644 {} +

A slightly simpler, though less precise, common alternative for static sites is just:

# Ensure ownership is correct
sudo chown -R vicky:vicky /var/www/static-page.vicky.com
 
# Set recursive permissions to 755 (grants execute to files, which is harmless for static assets)
sudo chmod -R 755 /var/www/static-page.vicky.com

11- Setup the Application

composer install

12- Migration

We can run the remaining application-related codes.

php artisan key:generate
php artisan jwt:token
php artisan storage:link
php artisan migrate
Edit this page
Back to top