Laravel Dev Environment in Linux
Tags: webdev, laravel, nginx, php
Published: 2023-12-07 17:09:01

This article guides you through installing and configuring Nginx for local development on Linux. It covers installing Nginx, PostgreSQL, PHP, and Laravel's required extensions. Additionally, it explains configuring Nginx for multiple domains and subdomains using virtual hosts.

## Install nginx

```shell
sudo apt update
sudo apt install nginx
```

## Verify Nginx installation: 

Open your browser and navigate to http://localhost. You should see the Nginx welcome page. But if your distro comes with apache2 pre-installed you should see the welcome page of apache2. No, worries we will fix that later.

## Install Postgresql

See [details here](https://www.postgresql.org/download/linux/ubuntu/)

```shell
# Create the file repository configuration:
sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'

# Import the repository signing key:
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -

# Update the package lists:
sudo apt-get update

# Install the latest version of PostgreSQL.
# If you want a specific version, use 'postgresql-12' or similar instead of 'postgresql':
sudo apt-get -y install postgresql
```

After successful Installation, open terminal and do the following:

Switch to postgresql console. It will ask for sudo (super user password)

```shell
sudo -u postgres psql
```

Change password and remember it carefully. This password is not the password of your system user, rather it is your postgres admin password.

```shell
\password
```

## Install `php`

Add repository

```shell
sudo add-apt-repository ppa:ondrej/php
sudo apt update
```

Now, run the following command to install latest php,

```shell
apt install php -y
```

## Verify PHP version

```shell
php -v
```

If it shows the php version, then it is installed successfully.

## Install Laravel required php extensions

```bash
sudo apt install php-ctype php-curl php-xml php-fileinfo php-mbstring php-pdo php-tokenizer php-session php-hash php-dom php-filter php-pgsql
```

Now, as you have nginx installed, you might not need apache. So, it can be removed from your system by following the
below method:

```shell
sudo service apache2 stop
sudo apt-get purge apache2 apache2-utils apache2.2-bin apache2-common
sudo apt-get autoremove
```

Our required components are installed. Now, its time for configuration.

In [https://laravel.com/docs/10.x/deployment#nginx](https://laravel.com/docs/10.x/deployment#nginx) Laravel provided a basic nginx configuration to run laravel projects. But for most of the cases its perfectly fine.

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

You can make this configuration file for each sites, or this can be
like [https://serverfault.com/questions/623327/configuring-multiple-domain-in-nginx-in-one-file](https://serverfault.com/questions/623327/configuring-multiple-domain-in-nginx-in-one-file).
Anyway, I am not going for that, you can try it if you like.

## Domain and Subdomains for Local Development

Before further discussion, lets know about the mechanism. First, when you visit a website by using a domain, the OS
looks up for the IP in DNS cache, then it visits the website by IP after resolving it.

To, access websites hosted locally by local domains or subdomains, we need to tell the OS how to handle it. This is done
by vhosts. The configuration for vhosts is at `/etc/hosts. If you see the contents you will find something like this:

<pre>
127.0.0.1	localhost
127.0.0.1	local1.test
127.0.0.1	local2.test
127.0.0.1	subdomain.local2.test
127.0.1.1	wovosoft
</pre>

See the top-most loop back ip 127.0.0.1 and 127.0.1.1. Each line is paired with IP and domain name. The lines instructs the OS to forward all the requests for local1.test, local2.test and localhost to the same IP address 127.0.0.1 on port 80. Now the question is how, the server will know, for which domain which application should handle the request? Well, this is determined by server applications like apache or nginx. Here we will go for nginx.

Now, reconsider the above nginx configuration provided by laravel. We, can use that sample to instruct nginx how to
handle the request for different domains or sub-domains.

Configuration for local1.test

<pre>
server {
    listen 80;
    listen [::]:80;
    server_name local1.test;
    root /home/your_username/sites/local1/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;
    }
 
    location ~ /\.(?!well-known).* {
        deny all;
    }
}
</pre>

Configuration for local2.test

<pre>
server {
    listen 80;
    listen [::]:80;
    server_name local2.test;
    root /home/your_username/sites/local2/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;
    }
 
    location ~ /\.(?!well-known).* {
        deny all;
    }
}
</pre>

So, if you look carefully, you will understand that for local domains, all requests are forwarded to 127.0.0.1 at port
80 by using `/etc/hosts` configuration file. You need to append a line for each local websites in that file. After that you need to make the configuration files.

## Storing and instructing nginx to use configuration files

By default, nginx stores configuration files in `/etc/nginx/sites-available` and `/etc/nginx/sites-enabled`. And enables the sites by creating a symlink in `/etc/nginx/sites-enabled` to the configuration file in `/etc/nginx/sites-available`.

<pre>
sudo ln -s /etc/nginx/sites-available/your_config_file.conf /etc/nginx/sites-enabled
</pre>

But managing this procedure every time in local development is a bit of hassle. So, we will use a different approach.
We, will create folder outside the hidden area. For example `/home/your_username/nginx/domains`
or `/home/your_username/nginx/sites-enabled` anything else that is easily accessible by you.

Next, open the nginx configuration file at `/etc/nginx/nginx.conf` and add the following line in the http block.

<pre>
include /home/your_username/nginx/domains/*;
</pre>

If you want to use `/home/your_username/nginx/sites-enabled` then add the following line in the http block.
<pre>http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    # server_tokens off;

    gzip on;
    gzip_disable &quot;msie6&quot;;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_proxied any;
    gzip_vary on;

    gzip_types
    application/atom+xml
    application/javascript
    application/json
    application/rss+xml
    application/vnd.ms-fontobject
    application/x-font-ttf
    application/x-web-app-manifest+json
    application/xhtml+xml
    application/xml
    font/opentype
    image/svg+xml
    image/x-icon
    text/css
    text/plain
    text/x-component;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

    #PUT IT HERE
    include /home/your_username/nginx/sites-enabled/*;
}

</pre>

And if you want the configuration files to have `.conf` extension, then add the following line in the http block.
Actually, this is a good practice, and it can give you some idea about the file type.

<pre>
include /home/your_username/nginx/domains/*.conf;
</pre>

Now, do the same for `/etc/hosts` file. Add the following line at the bottom of the file.

<pre>
#include "/home/your_username/hosts/custom.conf"
</pre>

This custom.conf file is easily editable and accessible. So, you can add or remove domains or subdomains easily.


# Notable points
After applying all previous configurations, sometimes you may get errors about laravel.log file cannot be opened in append mode, or bootstrap/cache is not accessible. This happens because of current users or www-data users permission access. To fix this follow the next steps: 

Never set a directory to 777. you should change directory ownership. so set your current user that you are logged in with as owner and the webserver user (www-data, apache, ...) as the group. You can try this:

<pre>
sudo chown -R $USER:www-data storage
sudo chown -R $USER:www-data bootstrap/cache
</pre>

then to set directory permission try this:

<pre>
chmod -R 775 storage
chmod -R 775 bootstrap/cache
</pre>

See more about this configuration at:[https://stackoverflow.com/a/45673457](https://stackoverflow.com/a/45673457) 


** Happy Coding **