Hosting Laravel 5.6 on Digital Ocean

Digital Ocean is a cloud hosting provider that offers flexible virtual infrastructure with advanced orchestration tools. Digital Ocean virtual machines (‘droplets’) are dirt cheap and that makes them a terrific resource for hosting small-to-sized apps with ample scope to scale upward. I recently migrated a Laravel SAAS app from AWS to DO. Here’s the process I followed:

1 – Generate ssh keys

Depending on prior knowledge, this step may be redundant. Using key-based ssh authentication adds an additional layer of defence in protecting your environment from attack and is thoroughly recommended. We’re going to create two ssh keys: one for the ‘root’ user (required for initially commissioning a new DO droplet) and the second for a regular user account that is used everything else.

Generating public/private rsa key pair.
Enter file in which to save the key 

Then just follow the prompts. The contents of the .pub file are used to commission the server in step 2. You’ll want to also create a second key pair for the non-root user as recycling ssh keys is not a good idea.

See Atlassian’s guide to generating keys on other platforms

2 – Commission a DigitalOcean Droplet

Jump into the Digital Ocean and create a new Doplet.

  • Operating System: I’m using Ubuntu 18.04
  • Instance type: 1GB / 1vCPU Instance
  • Back ups: Enabling backups is a good idea. They’re charged at 20% of the droplet instance price and will save you in a bind but you should ensure your app’s architecture is resilient to worker nodes failing. Admittedly this is an issue more closely related load-balanced cloud environments and fortunately Laravel’s design philosophy promotes this approach.
  • The decision to add a storage block depends on the storage requirements of your application. In cloud environments you’ll want to avoid reading/writing to local filesystems. These resources (that can be managed independent of your instance) are designed to be more fault tolerant than using the local disk. Personally, I’d recommend sticking with S3 storage and DO’s S3 Spaces are perfect for this. From the Laravel perspective, they’re basically 100% comparable with an equivalent AWS S3 configuration and there’s a tonne of material available on how to set them up.

  • Region: Choose a region that suits your needs. I’m based in Australia so typically go with SGP1 – customise as appropriate to your needs.
  • Add your SSH keys: Add the ssh key created in step 1. The key you upload will be use to provision the root user in your new environment.
  • # Instances: Unless you’re feeling ambitious, it’s best to leave the number of instances at 1.
  • Choose a hostname: anything you like but I recommend keeping it practical.
  • Click create and wait for the instance to start.

    You’ll be able to see the IP address of your instance once it’s up and running. To login use ssh (change IP address and -i ssh key path as appropriate):

     $ssh root@111.222.333.444 -i ./.ssh/digitalocean
    Enter passphrase for key './.ssh/digitalocean': 
    Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-36-generic x86_64)
    Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
    applicable law.

    Game on!

    3 – Create a user account

    Logging into your server with the root account for routine tasks is not a good idea. DO droplets are only ever commissioned with a root account so this means one of your first steps should be to create a non-root account to access the server and grant sudo access for any tasks that might require root privileges.

    root@demo:~# adduser ben
    Adding user `ben' ...
    Adding new group `ben' (1001) ...
    Adding new user `ben' (1001) with group `ben' ...
    Creating home directory `/home/ben' ...
    Copying files from `/etc/skel' ...
    Enter new UNIX password: 
    Retype new UNIX password: 
    passwd: password updated successfully
    Changing the user information for ben
    Enter the new value, or press ENTER for the default
    	Full Name []: 
    	Room Number []: 
    	Work Phone []: 
    	Home Phone []: 
    	Other []: 
    Is the information correct? [Y/n] Y

    Give the new user account access to Sudo:

    root@demo:~#  usermod -aG sudo ben

    Finally, add the second ssh key for your new user account that we created in step 1 to /home/ben/.ssh/authorized_keys (change ‘ben’ as necessary). If you have problems with this step, be sure to triple-check you have correct permissions set on ./.ssh and the key files. You’ll want chmod 700 and make sure you chown user:user ./.ssh/ -R if needed.

    3a – disable remote access by the root user account

    This step is optional but also simple and a sensible action toward keeping your new server secure. It’s a very good idea to test that your new user account can access the server before doing this (especially that the 2nd ssh key has been correctly set). You might inadvertently block yourself from connecting, so be careful. Log in with you new user account:

    $sudo  vi /etc/ssh/sshd_config

    Find the setting for ‘PermitRootLogin’ and change the value from ‘yes’ to ‘no’,
    Save the file ([esc] :wq)
    Finally restart the ssh service:

    service ssh restart 

    4 – Install Dependencies

    Okay, time to start installing stuff! For brevity’s sake I’m only listing install commands here. If you’re aiming to install PHP 7.2 (required for Laravel 5.6) you need to add to apt sources:

    $sudo add-apt-repository ppa:ondrej/php
    $sudo apt-get update

    With that done, let’s go:

    $sudo apt-get update
    $sudo apt-get install language-pack-en
    $sudo apt-get install nginx
    $sudo apt-get install mysql-server
    $sudo ufw allow 'Nginx HTTP'
    $sudo apt-get install php7.2-mbstring php7.2-curl php7.2-zip php7.2-gd php7.2-mysql php7.2-xml composer unzip

    I strongly recommend running mysql_secure_installation following the mysql installation – this guided script will help with tying down some common mysql attack vectors that are left open in a default installation. I should also point out that running your database and web server on the same sever is not encouraged practice – it works for the purpose of demo but for production environments you will ultimately want to run the database on a separate instance with secure connections via a Private Network between your nodes.

    There maybe a few other packages or libraries to install depending on the requirements of your Laravel app so be sure to check your documentation for anything additional. Composer will also do a reliable job of alerting you to missing dependencies in the next step.

    Finally, don’t forget to create a database and user credentials for your app. A guide to this process can be found here: (—-configuring-mysql ) – make a note of the database name, user and password so that you can add them to your .env at end of step 5.

    5 – install your app

    This part should be familiar:

    $sudo mkdir /var/www/my_laravel_app
    $sudo git clone /var/www/my_laravel_app
    $sudo chown ben:ben /var/www/my_laravel_app
    $cd var/www/my_laravel_app
    $composer update
    $php artisan key:generate
    $sudo chgrp -R www-data storage bootstrap/cache
    $sudo chmod -R ug+rwx storage bootstrap/cache

    At this point you application and all dependencies should be installed. Don’t forget make any changes to the .env file necessary for your Laravel app. At minimum you’ll want to set the url for your application and set your database credentials created in the previous step.

    6 – configure Nginx

    $sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/
    $sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/
    $sudo rm /etc/nginx/sites-available/default
    $sudo vi /etc/nginx/sites-available/

    Your final Nginx config should look the following (the file paths, server name/ip will obviously be different):

    server {
        listen 80;
        listen [::]:80;
        . . .
        root /var/www/html/my_laravel_app/public;
        index index.php index.html index.htm index.nginx-debian.html;
        location / {
            try_files $uri $uri/ /index.php?$query_string;

    8 – Restart your web server

    $sudo systemctl reload nginx

    There’s a lot more to cover in deploying a full-scale production environment, such as enabling HTTPS/SSL, adding S3 storage, load balancing and using a more resilient approach to hosting the database (ie, a separate instances). Possible topics for next time!


LDAP authentication with Laravel and Adldap2

This post provides a short tutorial on integrating LDAP authentication with the Laravel framework. One of my recent projects has involved adding LDAP authentication to a Laravel web app using Steve Bauman’s excellent Adlap2 package.

Lightweight Directory Access Protocol (LDAP) needs no introduction and any developer who has spent time working on internal ‘enterprise’ software project will have encountered it. Simple integration might just focus on using an LDAP directory to authenticate users using a common username and password across a suite of enterprise applications. More advanced integration work might including binding to a driectory for interactive retrieval of corporate contact data or delegated management of groups and roles.

If you’re planning to use this tutorial to add LDAP support to an existing project some of the set-up and config may vary. It is assumed that you’re working Laravel’s built-in authentication scaffolding as it will be easier to demonstrate how Adldap2 can be layered onto an existing. I’m also assuming that you’re developing with Homestead but that doesn’t matter too much.

  1. First up, we need to satisfy a dependency on the php-ldap extension. This might seem obvious but version of the ldap extension must exactly match the version of PHP installed in your Homestead environment. At the time of writing, this is v7.2 but you might need to alter depending on your set-up.
    $sudo apt-get update
    $sudo apt-get install -y php7.2-ldap
    $php -v | grep ldap

    Once installed you will want to restart the Nginx web server to ensure the ldap extension is loaded. The easiest way to accomplish this is by running `vagrant provision` from your host’s Homestead directory.

  2. Next, use Artisan to create Laravel’s built-in authentication scaffolding. The Laravel auth scaffold is practically bullet-proof for most out-of-box app requirements and serves as an excellent starting point for LDAP integration with Adldap2.
    $php artisan make:auth
    $php artisan migrate


  3. Access to password recovery functions probably won’t make much sense for common LDAP authentication scenarios, so unless you are planning to support a mix of local Laravel authentication and LDAP, you might want to consider limiting the built-in routes. The following routing should work for most cases, but you might want to customise to suit your needs. Note that I’ve applied a middleware authentication restriction in these routes to protect the ‘/home’ route.
    $ php artisan route:list
    | Domain | Method   | URI                  | Name          | Action                                                  | Middleware   |
    |        | GET|HEAD | /                    |               | Closure                                                 | web          |
    |        | GET|HEAD | home                 | home          | App\Http\Controllers\HomeController@index               | web,auth     |
    |        | GET|HEAD | login                | login         | App\Http\Controllers\Auth\LoginController@showLoginForm | web,guest    |
    |        | POST     | login                |               | App\Http\Controllers\Auth\LoginController@login         | web,guest    |
    |        | POST     | logout               | logout        | App\Http\Controllers\Auth\LoginController@logout        | web          |


  4. Next, alter your User model and corresponding migration. Introduce an ldap username column so you can keep track of which users have been previous authenticated to use the application. After making changes to your migration, don’t forget to re-run your user table migration:
    $php artisan migrate:refresh

    Here’s a sample from my migration file – as you can see the changes required are minimal. My ldap identifier is ’ladap_username’:

         * Run the migrations.
         * @return void
        public function up()
            Schema::create('users', function (Blueprint $table) {
                $table->string('ldap_username')->unique( );


  5. Use composer to install Adldap2. Edit the “require” block of your composer.json file:
        "require": {
            "php": ">=7.0.0",
            "fideloper/proxy": "~3.3",
            "laravel/framework": "5.5.*",
            "laravel/tinker": "~1.0",
            "adldap2/adldap2-laravel": "^3.0",

    Then update composer and refresh the autoloader:

    $composer update
    $composer dump-autoload


  6. Configure your LDAP settings. Following is a list of the available config options with examples and short description of each:
    #LDAP user account query string fragment that precedes your 'username' value
    #LDAP user account query string fragment that follows your 'username' value
    #LDAP directory hostname(s)
    #LDAP distinguished name used as the basis for your user account queries
    #If your LDAP instance requires your app to authenticate, provide the
    #relevant username and password in these fields

    There are a few other options that can be configured but these are the typical minimum. These values are stored in ./config/adldap.php and ./config/adldap_auth.php, but you will want to set them in your .env file rather than editing directly.

  7. Customise your LoginController’s username to correspond with your Adldap2 settings. The LoginController uses an ‘email’ attribute by default but you might want to change this value to match your LDAP instance. This method is provided by the Illuminate\Foundation\Auth\AuthenticatesUsers trait.

         * We're doing LDAP Auth in this application
         * so use the ldap username instead of email address to login
         * This property corresponds with ‘ldap_username’ column in the user table
        public function username( ) {
            return ‘ldap_username’;


  8. Customise the login view field name. By default, the Laravel auth scaffold provides an email address username field. If your LDAP instance is set up to. use non-email usernames, you can avoid triggering the email field validator by changing the type from “email” to “username”.
    <input id="uid" type="username" class="form-control" name="uid" value="{{ old('uid') }}" autofocus>

By this stage you should hopefully have a successful LDAP integration and managing to authenticate users against it. The use-case covered in this tutorial relatively simple and there a stack of other advanced integrations that ADLDAP can support. If there’s a enough interest, I’ll consider working on a full user binding tutorial in the future.

Parting tips

  • LDAP implementations tend to vary greatly and don’t always follow standard conventions and structure. You should consult with the directory administrator and don’t make assumptions on how data is organised.
  • Spend a bit of time investigating the ldapsearch CLI tool. Testing your ldap base parameter and other queries in ldapsearch will be much faster than trying to work things out on-the-fly while developing your app. This will help you establish a point-of-failure boundary if you run into problems and can also assist in designing appropriate tests for application.
  • Consider in advance which model attributes you intend to read from LDAP and which you’ll store locally within your application. Reading data regularly from the directory will introduce a performance overhead cost, so caching data might be a sensible tactic in this case. Of course, caching can introduce performance and complexity issues of a different nature, so consider your approach carefully.

Installing PHP & Oracle PDO Drivers on Ubuntu

Given that PHP and Oracle databases are fairly mainstream platforms these days, you might expect that there would be a simple, straight-forward way to install and connect PHP with Oracle on Ubuntu.


If it’s not something you do regularly, or, unless you’ve got a fool-proof set of recent instructions, it is painful.

I faced the problem last week while configuring a a new virtual server. After a few hours of bumbling around I finally found a decent guide here for an earlier version of the Oracle instant client drivers. On the off-chance that it saves someone else some hassle, I thought I’d post a slight updated version that will work for PHP5.3 and Instant Client 11.2. Chances are that I’ll need to refer to this guide in the future, too.

I started with a brand-spanking new VM, running Ubuntu 12.04. Your mileage may vary if attempting to install over the top if earlier versions etc. You’ll need root privileges to run many of these commands, so you might want start a root shell (sudo su -).

First, fetch instant client and SDK packages from Oracle:
For 64bit Ubuntu, you’ll need to grab both “instantclient-basic-linux.x64-*.zip” and “instantclient-sdk-linux.x64-*.zip”.

Put both downloads somewhere convenient and extract:

mkdir -p /opt/oracle/instantclient
cp ~/instantclient-basic-*-*.zip /opt/oracle/instantclient/
cp ~/instantclient-sdk-*-*.zip /opt/oracle/instantclient/
cd /opt/oracle/instantclient
unzip ./instantclient-basic-*-*.zip
unzip ./instantclient-sdk-*-*.zip
mv instantclient*/* ./rmdir instantclient*/

Next, we need to create some symlinks so that the oracle shared libraries appear where they need to:

ln -s* libclntsh.soln -s*
echo /opt/oracle/instantclient >> /etc/

The following steps aren’t strictly necessary unless you need a TNS names config: You put the config details into sqlnet.ora file.

mkdir -p network/admin
touch network/admin/sqlnet.ora

Now install apache, php etc:

apt-get install --yes php5 php5-cli php5-dev php-db php-pear
apt-get install --yes build-essential libaio1

Install the pecl oci8 extension:

pecl install oci8

When prompted for the ORACLE_HOME path, enter “shared,instantclient,/opt/oracle/instantclient”

Add the configuration to your php.ini files:

echo "# configuration for php OCI8 module" > /etc/php5/cli/conf.d/oci8.ini
echo "" >> /etc/php5/cli/conf.d/oci8.ini

Now we install pdo_oci. It hasn’t been updated in a while so a few bits of fancy linking are in order…

cd /usr/include/
ln -s php5 php

cd /opt/oracle/instantclient

mkdir -p include/oracle/11.2/
cd include/oracle/11.2/
ln -s ../../../sdk/include client
cd -

mkdir -p lib/oracle/11.2/client
cd lib/oracle/11.2/client
ln -s ../../../../ lib
cd -

pecl channel-update
mkdir -p /tmp/pear/download/
cd /tmp/pear/download/
pecl download pdo_oci
tar xvf PDO_OCI*.tgz

### copy the lines below into the file "config.m4.patch"
*** config.m4 2005-09-24 17:23:24.000000000 -0600
--- /home/myuser/Desktop/PDO_OCI-1.0/config.m4 2009-07-07 17:32:14.000000000 -0600
*** 7,12 ****
--- 7,14 ----
if test -s "$PDO_OCI_DIR/orainst/unix.rgs"; then
PDO_OCI_VERSION=`grep '"ocommon"' $PDO_OCI_DIR/orainst/unix.rgs | sed 's/[ ][ ]*/:/g' | cut -d: -f 6 | cut -c 2-4`
+ elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.11.2; then
elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.10.1; then
elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.9.0; then
*** 119,124 ****
--- 121,129 ----
+ 11.2)
+ ;;
AC_MSG_ERROR(Unsupported Oracle version! $PDO_OCI_VERSION)

patch --dry-run -i config.m4.patch && patch -i config.m4.patch &&
phpize ORACLE_HOME=/opt/oracle/instantclient

./configure --with-pdo-oci=instantclient,/opt/oracle/instantclient,11.2

make && make test && make install && mv modules/ /usr/lib/php5/20090626/

Finally, add the configuration lines that enable the pdo_oci extension:

cat - < /etc/php5/apache2/conf.d/pdo_oci.ini# configuration for php PDO_OCI module

Check that everything succeeded:

php --info | grep oci