Deploying a Laravel 5.5 Web App on Ubuntu

The Right Way with SSL, Server Updates, a Firewall and More

You don’t need a team of Dev-Op professionals to deploy and maintain your Web App!

This guide aims to:

  • Follow industry best practices,
  • Standardise tooling configuration steps to minimise ‘wrong-turns’ and incompatibilities among different software,
  • Minimise assumptions regarding prior knowledge (Such as using nano),
  • Assure standard security measures are configured such as auto-updates and Fail2Ban protection,
  • Accomodate modern dev tooling such as package managers and git.

Written for all deploying web apps in popular languages such as Python, Ruby, Nodejs. However, it’s especially applicable for deploying PHP apps based on the Laravel Framework.

You will need

  • A ‘blank’ Ubuntu server (Use the Ubuntu 16 LTS version) (1)
  • SSH access to that server through a terminal (2)
  • Your project hosted with git and git installed on your local computer (3)
1. This likely would be using a service such as AWS EC2 or DigitalOcean (DO). I would recommend DigitalOcean(DO) as a server considering their price and ease of use, here is a tutorial on how to spin up a new server on DO.
2. Here is a DO tutorial on how to set up SSH access. SSH is used to connect to the server directly and be able to run commands as well as transfer files via the protocol. We don’t use ftp, it’s not secure.
3. Here is a guide to installing git. Here is a guide to setting up your project as a git project if it’s not already. For free private repositories I suggest Bitbucket.

Once, you are able to login (via ssh) on your new Ubuntu , we can move on to deploying!

Any text that is highlighted in a light brown text box like this is a command you should enter into your terminal

Step 1: Login as root

Using your favourite ssh compatible terminal. Login like so:

ssh root@YourServersIP

If you have any problems with this step you should consult this tutorial.

Step 1: Automatic Updates

We want to make use of that Long Term Support that our Ubuntu 14 .04 OS supports. As such I recommend installing a program called Unattended Upgrades which allows the server to be updates with the latest security fixes automatically so we don’t have to monitor it ourselves.

sudo apt-get update
sudo apt-get install -y unattended-upgrades

We can take a look at what unattended updates are enabled through taking a look in nano

sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

I recommend that only (as should appear by default) the line with security in it should not have a ‘//’ like so to maintain compatibility if a breaking change is included in an update.

Unattended-Upgrade::Allowed-Origins {
“${distro_id}:${distro_codename}-security”;
// “${distro_id}:${distro_codename}-updates”;
// “${distro_id}:${distro_codename}-proposed”;
// “${distro_id}:${distro_codename}-backports”;
};

Now quit the editor by pressing Control + x.

Here only the security upgrades will be automatically installed. This improves security while preventing your web apps breaking.

Step 2: Disable Password Login (Optional)

This will make sure that your password isn’t guessed or cracked by a hacker trying to gain entry into the server. This will only allow ssh logins which are more .

sudo nano /etc/ssh/sshd_config

then navigate the the line which says PasswordAuthentication. (If its commented out with a ‘#’ then delete the ‘#’) Make sure it is followed by a ‘no’ rather than a yes. As such it should appear like this:

PasswordAuthentication no

Now quit the editor by pressing Control + x (and save by answering the prompt with ‘y’). Now reload the ssh service for the changes to take effect by typing this into your console.

sudo service ssh restart

Step 3: Block Hackers with Fail2Ban

Fail2Ban is a program which blocks malicious hackers by using iptables (which have the ability of disallowing connections from specific IP addresses).

Fail2Ban detects potential hackers through scouring the servers logs and banning IP addresses that indicate attacks such as IPs trying to login multiple times but failing or trying random queries in your web software (i.e. your PHP page) seeking to find exploits.

Throw this command into terminal to install the program

sudo apt-get install -y fail2ban

Step : Install your Web Server

Here you have two choices; Apache or Nginx. For 95% of use cases, Nginx will be sufficient. And I mean that. If its a Web App/Site/ API Nginx is perfect and considering its faster than Apache its recently become more commonly suggested.

Now install Nginx by entering these commands one by one (If an error is output which is similar to “Unable to resolve host”, try the command without the ‘sudo’ prefixing it.)

sudo apt-get install -y software-properties-common
sudo add-apt-repository ppa:nginx/stable
sudo apt-get update
sudo apt-get install -y nginx
sudo service nginx restart

Now visit http://YourServersPublicIPAddress and if you don’t see this web page then it didn’t install correctly and go redo the installation steps.

Ignore that it says Debian and not Ubuntu it doesn’t matter. Ubuntu and Debian are just based off of the same Linux system.

Step 4: Necessary Software

Git

Most of us will need Git on the server for deployments. Curl and wget are also useful tools for HTTP interactions and are needed to install Composer. Lets install them all.

sudo apt-get install -y git curl wget

PHP 7 (Optional)

First we will get rid of php5 if its on the machine

sudo apt-get purge php5-fpm
sudo apt-get --purge autoremove

First we will have to add the relevant repositories

sudo apt-get install -y software-properties-common
sudo apt-get install -y python-software-properties
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update

Now lets put PHP 7 on the machine; the php-fpm extension necessary for Nginx to work with it, extensions required for popular frameworks like Larave, and the mysql extension necessary for working with mysql.

sudo apt-get install -y php7.0 php7.0-fpm php-mysql php7.0-mysql php-mbstring php-gettext libapache2-mod-php7.0 php-doctrine-dbal php7.0-pgsql php-xml redis-server

Apply sensible security defaults (These are one line commands)

sudo -- sh -c "echo 'cgi.fix_pathinfo=0' >> /etc/php/7.0/fpm/php.ini"
sudo -- sh -c "echo 'cgi.fix_pathinfo=0' >> /etc/php/7.0/cli/php.ini"
sudo systemctl restart php7.0-fpm

PHP 5

If you don’t want PHP 7 and you want PHP 5 (likely due to its compatibility)

sudo add-apt-repository -y ppa:ondrej/php5
sudo apt-get update
sudo apt-get install -y php5 php-cli php5-curl

Composer for PHP Dependency Management

curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer
Now type ‘composer’ into terminal and it should output this

MYSQL for Databases

sudo apt-get install -y mysql-server

now allow MYSQL’s use

sudo mysql_install_db

Alternatively, Install PostgreSQL for Databases

sudo apt-get install -y postgresql postgresql-contrib

Node and NPM

Use Node JS? NPM? Yarn? Express? Grunt? Gulp?

curl -sL https://deb.nodesource.com/setup | sudo bash -
sudo apt-get install -y npm
#Gulp, Grunt and Yarn
ln -s /usr/bin/nodejs /usr/bin/node
sudo npm install --global yarn gulp-cli grunt-cli

Ruby

Install Ruby

sudo apt-get install -y ruby

Python

Ubuntu 14.04 should have Python 2.7 on the server. You can check through this command

python --version

Optional: (Would not recommend unless you know you need Python 3)

I would not suggest this as it can cause incompatibilities with popular programs such as ‘lets encrypt’.

Remove Python 2.7 and install Python 3 and PIP

sudo rm /usr/bin/python
sudo ln -s /usr/bin/python3 /usr/bin/python
sudo apt-get install -y python3-pip

Step 5: Pulling in your Web App via Git

Navigate to the var/www folder like so

cd /var/www/

create a new directory for your app (No spaces please. Makes life easier)

sudo mkdir AppName

go into the created directory

cd yourAppName

Have a production ready Git branch of your App

  • For beginners who don’t have development and production branched already set up.

When we are developing our app we don’t want to have to make sure all our changes constantly need to be ready for production.

As such, we need to create a production version of our app which we can merge production ready changes into when they are ready (such as a new feature).

On your local machine where you are developing our app (assuming we have installed Git installed) open a terminal and navigate to the folder where you are developing and have your repo. Now create a production branch of the project. If you are already developing on a ‘development’ branch you know why you can skip this step.

//On your local/dev machine!
git branch production

Whenever your development or other branches are ready to have their changes into production we have to enter into the production branch.

So lets switch into the production branch. We use the ‘git checkout’ command to enter and exit branches. Take a look at all branches available using the ‘git branch -a’ command.

git checkout production

And merge our development changes (after you have made sure to commit your changes in the development branch!) (replace master with your dev branch)

git merge --no-ff master

Adding ‘ -- no-ff’ makes sure it creates a new commit so we can track development merges into production.

Now lets push this new branch so it appears on our remote repository hosting such as GitHub, GitLab or BitBucket.

git push origin production

(You may want to switch back to your dev branch in case you forget later you are on production. Simple use the command ‘git checkout master’ to switch back.)

Pull your production branch down onto your new server

Now on our new server in the AppName directory lets pull down our our production branch into our server

First lets go into our AppName Folder

cd /Var/www/AppName

Then, init a new git repo

git init

Now add your remote git repo on BitBucket, GitHub, whatever

git remote add origin https://github.com/user/repo.git

now pull in your production branch

git pull origin production

Great, now whenever we need to pull down the production branch of your project just cd into the directory and run ‘git pull origin production’.

Step 6: Configure the Server to serve our website

We need to configure nginx to appropriately serve the directory.

This can be a bit finicky so pay close attention here as one out-of-place semi-colon could mean your website cannot be served/accessed.

To configure Nginx (Which we will need to) open the Nginx config like so

sudo nano /etc/nginx/sites-available/default

now all our edits are going to be after the first

server {

line and before the closing ‘}’ to make sure the commands are changing the server and not flying out in no mans land where nothing happens.

Make your URLs pretty

No one likes to see .html and .php at the end of their web page so lets get rid of them by going to the Nginx config file.

Change the default ‘location / { }’ block’s contents from

try_files $uri $uri/ =404;

to

try_files $uri $uri/ /index.php?$query_string;

Disallow access to dot files

Adding this block to your Nginx config will disallow web access to files beginning with ‘.’ which are usually configuration files we don’t ever want users to see such as the ‘.git’ file. Lets disable access to those files like so

location ~ /\. 
{
deny all;
}

PHP

Find this line:

index index.html index.html index.nginx-debian.html;

And add index.php, changing it like so:

index index.html index.html index.nginx-debian.html index.php;

If you are going to use PHP 7 copy this block in:

location ~ \.php$ {
include snippits/fastcgi-php.conf;
fastcgi_pass unis:/run/php/php.7.0-fpm.sock;
}

If you are going to use PHP 5, replace this line in the above block

fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;

with this line and paste the block in (by right clicking).

fastcgi_pass unix:/var/run/php5-fpm.sock;

(I would suggest PHP 5 if PHP is not going to be the base of your main application as its has increased compatibility over PHP7. Otherwise I would choose PHP7 if PHP is your main platform as its faster.)

Edit the Nginx Virtualhosts file to serve the site

sudo nano /etc/nginx/sites-available/default

now change this line

root /var/www/html;

to the folder where your index.php is served from, in laravel its /public

root /var/www/AppName/public;

Exiting the editor:

Exit editing the nginx config through Control+X then typing Y to save the changes then enter again.

Check the Nginx config is valid

Running the command ‘nginx’ will let you know if the config is valid, if it

(excluding comments you have left in) your file should look similar to this

Restart Nginx

Now you’ve made changes to the config, lets restart Nginx

sudo service nginx restart

Setting Permissions

First of all set the permissions for the public directory (where the html/css/js files for your site are to be served from.

These folders are applicable to Laravel applications, check your framework’s documentation for it’s respective permissions.

sudo chown -R www-data:www-data /var/www/AppName/public
sudo chmod 755 /var/www
sudo chown -R 777 /var/www/AppName/bootstrap/cache
sudo chown -R 777 /var/www/AppName/storage

Step 7: Installing Dependencies

Install PHP dependencies via Composer

composer install

Install JS dependencies via NPM

You may skip this step if your JS is already compiled for production and was included in your production branch which we brought in via version control (git).

I’d generally suggest building your JS frontend dependencies locally (with the correct production settings then uploading to version control and deploying. However, if you insist on doing it on the server then run these commands accordingly)

npm install
npm run production //or whatever your command is to compile & minify

Step 8: Setting up your Database

Create a Database for your App

We now need to set up a DB for your App. If your app uses Postgresql then skip the mysql setup and visa-versa for those using mysql.

Mysql DB Setup

First lets create a mysql database: (remember the password you input when prompted)

mysql -u root -p
CREATE DATABASE appname_db;

Create a user for that database: (first command is one line)

GRANT ALL PRIVILEGES ON app_db.* TO 'appname_user'@'localhost' IDENTIFIED BY 'password-of-your-choosing';
flush privileges;

Exit the DB

exit

Postgresql DB Setup:

Create a global postgres user:

sudo su - postgres
psql

Create a database for your app:

CREATE DATABASE appname_db;

Create a user for your app’s database:

CREATE USER appname_user WITH PASSWORD 'password_of_your_choosing';

Allow our user access to the db:

(replace appname_user, appname_db)

GRANT ALL PRIVILEGES ON DATABASE appname_db TO appname_user;

Exit the DB configuration:

\q
exit

Set Up Your Environment

Create an environment file for your app if it requires one. Laravel does.

cd /var/www/AppName
sudo nano .env

In the nano editor copy your dependencies from your dev .env and change what is necessary for the production instance of your app.

i.e. Change:

APP_ENV=local
APP_DEBUG=true

to

APP_ENV=production
APP_DEBUG=false

Connect your Database

In your ENV, change the Database Name/Username/Password options to what we previously created.

Then migrate your database, in Laravel it’s done like so:

php artisan migrate:install
php artisan migrate

Step 9: Configure LetsEncrypt for SSL

LetsEncrypt is a great free SSL provider. We are going to use CertBot to install it on our server.

Run these commands to download Certbot:

sudo apt-get install -y software-properties-common
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install -y python-certbot-nginx

Set up the website’s virtualhost

Edit the existing virtualhost

sudo nano /etc/nginx/sites-available/default

Change (Replace example.com with your domain name)

server_name _;

to

server_name example.com www.example.com;

Change

listen 80 default_server;
listen [::]:80 default_server;

to

listen 80 default_server;
listen [::]:80;

Copy over the new virtualhost into its own file and then into sites-enabled:

sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/example.com
sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/

Now run this to install your SSL:

sudo certbot --nginx

When it asks you if you wish to have SSL for both example.com and www.example.com simply write 1,2 for both.

Voila!

Step 10: Check https://example.com to see your website in all its SSL glory

If you have any problems please reach out to me here:

I hope this helped you out, servers can be really quite hard to get your head around. Please let me know if you can think of any improvements!