Author: Alessandro Marchioro - Written and Published on 2024-09-15 12:44

How to properly (and securely) setup a Laravel server for development and production

Hi everyone!

Here I'm going to write a simple guide for properly setting up a Laravel server for development and production purposes, so you will have a complete instance for remote work and correct services configuration, file and folder permissions and much more!

The dependencies are the following:

Step 1: Installing the OS

Create a virtual machine on the virtual machine management software of your likings (in this case I'm using KVM with Virt Manager under an Arch Linux host), download the latest Debian ISO from official source and insert into the virtual CD/ROM reader of your virtual machine.

After that, boot the virtual machine and set up as per your likings, making sure to remember the usernames and passwords provided to the installation script so we can use them later, other than configuring a static IP for the server itself so we can rely on that to properly setup the Laravel side of things.

Step 2: Enabling remote SSH

Login into the Debian machine and enable SSH remote access with the following command:

sudo systemctl enable sshd.service --now

Test it via remote connection with a secondary PC and make sure that it works as intended with this command:

ssh username@ip_address

If everything is set up correctly, let's proceed to the following steps!

Step 3: Configuring a static IP

We now need to configure our machine with a static IP, in my case 10.0.0.167, by logging in as root on the machine and editing the file /etc/network/interfaces with your favourite text editor in the following way, by changing from dhcp to static on your iface interface, and adding the following additional configuration (adapted to your use-case) to make it like so:

iface enp1s0 inet static
address 10.0.0.167
netmask 255.255.255.0
gateway 10.0.0.1
dns-nameservers 1.1.1.1 8.8.8.8

Save the file and restart the networking service with this command:

systemctl restart networking

Now let's check if the network is correctly configured by running the ip addr command and looking for our network card and if it's configured properly.

Then, if it all checks out, let's test the network if ping via IP and via DNS server previously configured is working properly with these two commands:

ping 1.1.1.1
ping www.google.com

If all it's good, let's go on!

Step 4: Installing project's dependencies

Once connected remotely via SSH with the previous steps instructions, we're now gonna install the very much needed dependencies for making it all work, in particular:

Note: I suggest you NOT installing the 'sudo' package via apt, and use root user directly for that.

So, all this converts to the following command:

# For most of the packages, install those via this command
apt install php php-fpm composer nginx mariadb-client mariadb-server git acl

# For Laravel-specific PHP dependencies, install also those packages
apt install php-xml php-dom php-mysql

# For NodeJS and NPM, we're gonna add the latest LTS version via the official "nvm" (node version manager) (updates to the script can be found here: https://nodejs.org/en/download/package-manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
nvm install 20

Step 5: Creating the project

Now we're going to create and setup our project, in this guide we're calling it playground.

Let's make a project folder under /srv/services and give it to our low-privileged app user, in my case mrcz, like so:

mkdir /srv/services
mkdir /srv/services/playground
chown -R mrcz:www-data /srv/services/playground

With that, let's switch to our app user with this command

su - mrcz

and create our project with this command

cd /srv/services/playground
composer create-project laravel/laravel .

You should now have an empty Laravel project!

Let's start safe by initializating a local Git repository with the following commands:

git init .
git add .
git commit -m "Initial project status"

(you can configure where and with which user to push to a remote repository later by yourself!)

Step 6: Configure the project and related services

Now that we have the project installed, let's configure it by editing the .env file under these specific keys, and uncomment those if they are currently disabled with a comment:

APP_NAME=Playground
APP_URL=http://playground.goldmark.local

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=playground
DB_USERNAME=playground
DB_PASSWORD=forsurethatssafebruh123

After that, we need to configure Nginx to reply to our requests and redirect those to the Laravel application, and for that we need a new nginx block configured for the purpose, which we will put it here under /etc/nginx/sites-available/100-playground.conf with the following content:

server {
    listen 80;
    listen [::]:80;
    server_name playground.goldmark.local;
    root /srv/services/playground/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 ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }
 
    location ~ /\.(?!well-known).* {
        deny all;
    }
}

Then, enable the service:

ln -sf /etc/nginx/sites-available/100-playground.conf /etc/nginx/sites-enabled/100-playground.conf

and restart Nginx to activate it:

systemctl restart nginx

Now we're gonna configure our database by doing the following commands:

mysql -u root
MariaDB [(none)]> CREATE USER `playground` identified by 'forsurethatssafebruh123';
MariaDB [(none)]> CREATE DATABASE playground;
MariaDB [(none)]> GRANT ALL PRIVILEGES ON playground.* TO `playground`;
MariaDB [(none)]> FLUSH PRIVILEGES;
MariaDB [(none)]> exit

Then, we will go back to the project folder as our app user and run the database migrations with

php artisan migrate

If it all checks out, you should see some migrations running!

Step 7: Add record to local DNS or network DNS

If you noticed before, we used playground.goldmark.local both on Nginx and Laravel app configuration, that's because Nginx needs to know the name of the service he is exposing to make it work, so we need to add on your local DNS or on your network DNS the "A record" that matches your IP to the name you used, in my case I added 10.0.0.167 playground.goldmark.local to my local network DNS and now it works!

You can also use your local machine DNS service, or hosts file that you can find in the /etc/hosts path if you want :)

Step 8: Configuring the scheduler

One way to configure the Laravel scheduler to run background tasks is with a cronjob, which can be configured this way by logging in as your app user (mrcz in this case) and running the following command:

crontab -e

and pasting this at the end of the file

* * * * * cd /srv/services/playground && php artisan schedule:run >> /dev/null 2>&1

This makes sure that task scheduler is correctly working for Laravel!

Step 9: Configuring proper permissions for the project's folder

If you tried connecting to the project, you would see that it's throwing file permissions errors all over the place, and that's because the project is currently being accessed and run by PHP-FPM and Nginx which are both running under www-data user, and the current project owner and group owner are currently mrcz:mrcz, so we need a way to set it up so that the group of the Laravel project has always www-data-accessible files and folders for working properly.

Let's start by correcting the current status of the project and migrating from mrcz:mrcz to mrcz:www-data as follows:

cd /srv/services/playground
chown -R mrcz:www-data ./
chmod -R ug+rwx storage bootstrap/cache

And now, let's configure for future use a proper mask so it will always write folders and files with the mrcz:www-data match!

su -
chmod -R g+s /srv/services/playground
setfacl -Rdm user:mrcz:rwx,group:www-data:rwx /srv/services/playground/

We should be good to go by now!

Step 10: Testing

If you now go to "https://playground.goldmark.local" with a browser, you should see the Welcome screen of Laravel ready to work on! 🔥

Step 11: Have fun!

You didn't see it coming, did you? 😜

Conclusions

So now you have a properly configured Laravel server ready to develop with!

For production uses, you should also make sure to install ufw for firewall purposes but other than that, it should be ready to go!

Thanks for reading it to the end :)

I hope it helped someone as it always has helped me in my work and homelab projects!


If you fancy see what I'm working on, have a look at my GitHub or write me an email!