Setup dependencies

Install NGINX through apt:

$ apt install nginx 

Install php dependencies

$ apt install php7.2 php7.2-curl php7.2-common php7.2-cli php7.2-mysql php7.2-mbstring php7.2-fpm php7.2-xml php7.2-zip php7.2-bcmath

Update php.ini properties:

$ nano /etc/php/7.2/fpm/php.ini
#edit according to your setup
memory_limit = 1024M

Setup MariaDB

We need to install mariaDB server, create a dedicated database and user

$ apt install mariadb-server mariadb-client

Secure the setup:

$ mysql_secure_installation
NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

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
you haven't set the root password yet, the password will be blank,
so you should just press enter here.

Enter current password for root (enter for none):

Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.

Set root password? [Y/n] Y

[...]

Password updated successfully!
Reloading privilege tables..
 ...Success!

By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them.  This is intended only for testing, and to make the installation
go a bit smoother.  You should remove them before moving into a
production environment.

Remove anonymous users? [Y/n] Y

Normally, root should only be allowed to connect from 'localhost'.  This
ensures that someone cannot guess at the root password from the network.

Disallow root login remotely? [Y/n] Y

... Success!

By default, MariaDB comes with a database named 'test' that anyone can
access.  This is also intended only for testing, and should be removed
before moving into a production environment.

Remove test database and access to it? [Y/n] Y
- Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!

Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.

Reload privilege tables now? [Y/n] Y

... Success!

Cleaning up...

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

Thanks for using MariaDB!

Use mysql cli to create database schema and user:

$ mysql -p -u root

Enter password: 

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 49
Server version: 10.1.41-MariaDB-0ubuntu0.18.04.1 Ubuntu 18.04

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> 

Create databse:

MariaDB [(none)]> create database <database_name>;
Query OK, 1 row affected (0.00 sec)

Create user and grant access to database:

MariaDB [(none)]> CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';

Grant privileges:

MariaDB [(none)]> GRANT ALL PRIVILEGES ON *.* TO 'newuser'@'localhost';
MariaDB [(none)]> FLUSH PRIVILEGES;

Setup composer

$ apt install composer

Laravel Deployment

Our Laravel app will be cloned from source control:

$ cd /path-to-app && git clone <app_git_repo>

Then create the .env file: nano .env

APP_NAME=""
APP_ENV=production
APP_KEY=base64:......
APP_DEBUG=true
APP_URL=https://laravel.app.domain

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db_name
DB_USERNAME=db_username
DB_PASSWORD=db_password

BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120

REDIS_HOST=
REDIS_PASSWORD=
REDIS_PORT=
MAIL_DRIVER=
MAIL_HOST=
MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=
MAIL_FROM_ADDRESS=
MAIL_FROM_NAME=""
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

Install composer dependencies:

# from official documentation
# Autoloader Optimization

When deploying to production, make sure that you are optimizing Composer's class autoloader map so Composer can quickly find the proper file to load for a given class:

composer install --optimize-autoloader --no-dev

⚠️ If your run into the following error: SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes, update AppServiceProvider file:

use Illuminate\Support\Facades\Schema;

public function boot()
{
    ...
    Schema::defaultStringLength(191);
}

Setup Queue connections

$ php artisan queue:table
$ php artisan migrate

Setup supervisor:

$ apt install supervisor 

Configure supervisor for laravel queue:

$ nano /etc/supervisor/conf.d/queue.conf
[program:queue]
process_name=%(program_name)s_%(process_num)02d
command=php /path-to-app/artisan queue:work --tries=3 --daemon --queue=notifications -vvv
user=www-data
autostart=true
autorestart=true
numprocs=3
redirect_stderr=true
stdout_logfile=/path-to-app/storage/logs/queue.log

Reload supervisor:

$ supervisorctl reread
$ supervisorctl update
$ supervisorctl restart all

Setup directory permissions: follow this (opens new window) tutorial

Setup Nginx

Install Let’s Encrypt Certificate

$ add-apt-repository ppa:certbot/certbot
$ apt-get update
$ apt-get install python-certbot-nginx

Obtain the certificate:

$ certbot --nginx -d example.com -d www.example.com

Force redirect to HTTPS:

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel)

Edit nginx site configuration:

server {
	listen 80 ;
	listen [::]:80 ;

	root /path-to-laravel-app/public;

	# Add index.php to the list if you are using PHP
	server_name laravel.app.name; # managed by Certbot

	add_header X-Frame-Options "SAMEORIGIN";
	add_header X-XSS-Protection "1; mode=block";
	add_header X-Content-Type-Options "nosniff";

	index index.html index.htm 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/php7.2-fpm.sock;
	        fastcgi_index index.php;
        	fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
	        include fastcgi_params;
    	}

    	location ~ /\.(?!well-known).* {
        	deny all;
	}

	listen [::]:443 ssl ipv6only=on; # managed by Certbot
	listen 443 ssl; # managed by Certbot
	ssl_certificate /etc/letsencrypt/live/laravel.app.name/fullchain.pem; # managed by Certbot
	ssl_certificate_key /etc/letsencrypt/live/laravel.app.name/privkey.pem; # managed by Certbot
	include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
	ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}