in Code, Tutorials

Installing Laravel 5.1 at Digital Ocean with PHP 7

PHP 7 is here!!! …well, almost. As of the writing of this post, we’ve reached the 7th Release Candidate (!) phase, so it shouldn’t be too much longer until a stable release is made official. With reports of between 25% and 70% speed improvements, all of us PHP devs should be very excited about this.

I hadn’t had a chance to play with PHP 7 yet, and I’m also about to launch a new project (no announcements yet!), so I figured I would take advantage of the opportunity to learn some new stuff, and maybe give back to the community from which I’ve taken so much. Happy Thanksgiving to the PHP/Laravel community!

The Specs

My plan for the new server was essentially to follow the Homestead template, leaving out anything that was unnecessary for production:

  • Ubuntu 14.04 LTS (with the unattended-upgrades package)
  • PHP 7 using fpm (with mcrypt) + Composer
  • Postgresql MariaDB (really wanted Postgres, but I’ll explain why I switched later)
  • Nginx
  • Redis (for cacheing, queues, and other optimizations)
  • Node (with Bower, Grunt, and Gulp)
  • Git

Once all this was set up, my plan was to configure it to automatically pull updates from my Github repo whenever changes have been pushed. (I’ll describe the push-to-deploy setup in a future post.) If it all works out, I’d have a super fast server that would require very little maintenance. To provide more detail, in the process of the above, I also set up:

  • Secure access to the server
  • A basic firewall
  • The server timezone
  • A swapfile to handle potential memory problems
  • Email notifications for hack attempts
  • My domain name, including configuring Mailgun for my MX server

I followed many wonderful tutorials in the process. I’ll point to many of them here, and summarize any places that I deviated from what those other incredibly smart people did.

Setting up Ubuntu 14.04 LTS

DigitalOcean also supports CoreOS, and for a while I considered learning how to use it with Docker to set up my server. After a little research, though, I decided that this was a bit over my head at the moment, and since I was already pushing the envelope by using PHP 7, I figured I would stick with something I already understood well enough, Ubuntu. So, in order, the tutorials I followed here were:

  1. How to Create Your First DigitalOcean Droplet Virtual Server
  2. How to Connect to Your Droplet with SSH
  3. Initial Server Setup with Ubuntu 14.04
  4. Additional Recommended Steps for New Ubuntu 14.04 Servers
  5. How To Protect SSH with Fail2Ban on Ubuntu 14.04

I followed these tutorials pretty much exactly and it all “just worked.” I have a sneaky suspicion that setting up ufw and fail2ban was a little redundant, but so far I haven’t found that they interfere with each other, and extra security can’t hurt, right? In case you’re curious, my final fail2ban jail.local file “jails” section ended up looking like this:

[bash] [ssh] enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 6

[ssh-ddos] enabled = true
port = ssh
filter = sshd-ddos
logpath = /var/log/auth.log
maxretry = 6

[php-url-fopen] enabled = true
port = http,https
filter = php-url-fopen
logpath = /var/log/nginx/access.log

[nginx-http-auth] enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/error.log

[mysqld-auth] enabled = true
filter = mysqld-auth
port = 3306
logpath = /var/log/mysql.log

[/bash]

This configuration should add some protection for SSH, Nginx, PHP, and MariaDB (MySQL) from brute force and DDOS attacks. Note that you have to manually stop and restart fail2ban after you update your jails.local file, and that it will fail to start if you don’t have the correct path to each of the log files listed above.

Also, I found that the unattended-upgrades package for automatic updates for Ubuntu was already installed on the DigitalOcean Ubuntu 14.04 droplet. However, I found that it was only configured to do the bare minimum when it comes to upgrades. After my changes, my /etc/apt/apt.conf.d/50unattended-upgrades file looked like:

[bash]

Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}:${distro_codename}-updates";
};
Unattended-Upgrade::Mail "admin@mydomain.com";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";

[/bash]

Which will update all packages, not just security updates, email me a report, get rid of old or unused packages, and automatically reboot the system, if necessary, at 2AM. In addition, my /etc/apt/apt.conf.d/10periodic file looked like:

[bash]

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";

[/bash]

Which will set the schedule to update everything daily, and remove unnecessary files once a week. Sweet! Let’s move on to PHP 7.

Setting up PHP 7 on Ubuntu 14.04

Since there is no stable build of PHP 7 as of this moment, you have two options for installing it on Ubuntu:

  1. Build from source
    This is not a bad option. It gives you fine-grained control of the configuration and you’ll have a much better understanding of how it all fits together. BUT, it also means that you have to install all of the many dependencies yourself, and figure out the configuration script, and it is not super easy to upgrade later.
  2. Use a pre-built package
    For ease of installation and maintenance, this is the easier option, and the one I went with. Fortunately, as we’ll see, there was an easy way to do this.

I used the PHP 7 install instructions from Zend along with Bjørn Johansen’s excellent tutorial to install PHP 7 with FPM. Since we’re going to be using Nginx, you’ll want to ignore the instructions for getting it to work with Apache.

First of all, I found that Zend’s instructions for adding their repo to your apt sources didn’t work for me from the command line. First I had to open up /etc/apt/sources.list using nano as follows:

[bash gutter=”false”]$ sudo nano /etc/apt/sources.list[/bash]

And then type/paste the following line in at the end of the file:

[bash]deb http://repos.zend.com/zend-server/early-access/php7/repos ubuntu/[/bash]

Save and exit, and then you can install the nightly build with the following:

[bash gutter=”false”]$ sudo apt-get update && sudo apt-get install php7-nightly[/bash]

From there on, I needed to follow Bjørn’s instructions for getting FPM up and running. There was one relatively small difference. The Zend install created some default configuration files, and so I had to separate the config options that Bjørn describes into two separate files. First, create /usr/local/php7/etc/php-fpm.conf and put the following in it:

[bash] [global] pid=/run/php7-fpm.pid
error_log=/var/log/php7-fpm.log

; Load pool definition config files
include=/usr/local/php7/etc/php-fpm.d/*.conf

[/bash]

And then create /usr/local/php7/php-fpm.d/www.conf and put the following in it:

[bash] [www] user = www-data
group = www-data

listen = 127.0.0.1:9000

pm = dynamic
pm.max_children = 10
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6

access.log = /var/log/$pool.access.log
slowlog = /var/log/$pool.log.slow
request_slowlog_timeout = 30s

php_flag[display_errors] = off

[/bash]

Things to note:

  • Make sure you use the username of the account that will run Nginx for the user/group settings, in my case that was www-data
  • I followed Bjørn’s lead on the numbers of children/servers. I’m not actually sure what the “right” values should be here, and the ones I used are slightly higher than the defaults. In any case you can find a decent explanation in the comments in the /usr/local/php7/etc/php-fpm.d/www.conf.default file
  • I set up the access and slow log files for debugging and tuning later on
  • Since this is a production server, I set the php_flag[display_errors] to “off”. You can set any other directives here that you would normally find in php.ini.

Make sure you follow the rest of the step’s in Bjørn’s tutorial VERY CAREFULLY. Whenever he gives you a link for downloading code, I recommend that you use it, rather than running the mistake of mistyping it.

Finally, I symlinked all of the php executables into /usr/local/bin so that they would be available system-wide. You can do that with this command:

[bash gutter=”false”]$ sudo ln -s /usr/local/php7/bin/* /usr/local/bin/[/bash]

Then try it out! If it’s working, you should get output like mine.

[bash gutter=”false”]

$ php -v
PHP 7.0.1-dev (cli) (built: Nov 10 2015 20:10:21) ( NTS )
Copyright (c) 1997-2015 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2015 Zend Technologies

[/bash]

One more thing: the default install does NOT come with a php.ini file already installed. To do that you should create one at /usr/local/php7/etc/php.ini. My php.ini only contains a single directive:

[bash]cgi.fix_pathinfo=0[/bash]

Which I guess I could have set in the /usr/local/php7/php-fpm.d/www.conf file above. This directive will be important for getting PHP7/Nginx/Laravel to play nice with each other. Next we’ll get PHP 7 working with Nginx.

Installing and Configuring Nginx to work with PHP7 FPM

The first thing I did was to create the directory structure to house my website, make sure that the Nginx user would have access to it, and then install Nginx:

[bash gutter=”false”]

$ sudo mkdir -p /var/www/mysite/public

$ sudo chown -R myusername:www-data /var/www

$ sudo apt-get install nginx

[/bash]

The next step is to configure the site by modifying /etc/nginx/sites-available/default to look as follows:

[bash]

server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

root /var/www/mysite/public;
index index.php index.html index.htm;

#server_name mydomain.com;
server_name 111.222.233.244;

location / {
try_files $uri $uri/ /index.php?$query_string;
}

location ~ \.php$ {
try_files $uri /index.php =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

[/bash]

Note that the root directive should point to the directory we just set up. Also the server_name can either point to your domain name (if you’ve associated it with your IP address) or should be set to the IP address of your DigitalOcean droplet, which you can find from your account settings in the DigitalOcean website.

After you’ve finished the configuration, it may be necessary to restart (or start) the php7-fpm and nginx processes as follows:

[bash gutter=”false”]

$ sudo service php7-fpm restart

$ sudo service nginx restart

[/bash]

If you have any troubles, check the log files for clues as to what’s gone wrong. Once you do this, to test it out you create a new file at /var/www/mysite/public/index.php and adding the following content:

[php]<?php phpinfo();[/php]

Then point your web browser to http://your_ip_address and with any luck you’ll see the default PHP server configuration page. Woohoo!!

Installing and Configuring MariaDB

Installing MariaDB is relatively straightforward. First run:

[bash gutter=”false”]$ sudo apt-get install mariadb-server[/bash]

Follow the prompts for setting up a root password, then you can log in using:

[bash gutter=”false”]$ mysql -u root -p[/bash]

Once logged in, you’ll want to create a new database and a user to access that. In this example, I’m going to call both the username and database “laravel”:

[sql gutter=”false”]

MariaDB [(none)]> CREATE DATABASE laravel;

MariaDB [(none)]> GRANT all ON laravel.* TO laravel@localhost IDENTIFIED BY ‘AmAzing_Pa55werd’;

MariaDB [(none)]> quit

[/sql]

That’s about it. You may want to follow DigitalOcean’s tutorial How To Secure MySQL and MariaDB Databases in a Linux VPS for more details. You should now be ready to support your Laravel 5.1 app.

Also, I said earlier that I originally wanted to use Postgresql instead of MariaDB for this project. The main reason that I didn’t do this is that the Postgres drivers for PHP did not come installed by default on the PHP 7 package I got from Zend. It would probably not be too hard to recompile PHP and include the appropriate drivers, but in this case, it was more trouble to me than it was worth.

Other Stuff: Node, Redis, Composer

Installing NodeJS was relatively straightforward. Just make sure you follow the instructions on the NodeJS website, and don’t use the default distribution available from Ubuntu, i.e.:

[bash gutter=”false”]

$ curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash –

$ sudo apt-get install -y nodejs

$ sudo npm install -g bower grunt gulp

[/bash]

Also, I found that the build-essential package was already installed by the time I got to this point. Don’t know if it was on there from the start, but I didn’t need to install it.

DigitalOcean’s tutorial for installing Redis was spot on.

Installing composer required the standard two commands:

[bash gutter=”false”]

$ curl -sS https://getcomposer.org/installer | php

$ sudo mv composer.phar /usr/local/bin/composer

[/bash]

And if you’ve followed everything carefully up to this point, you should be ready for the final step. At this point, you may want to take a snapshot of your server image so that you have a base image for installing further Laravel droplets at DigitalOcean.

Associating Your Domain Name with Your IP Address

I got stuck a little trying to get my domain name associated with my IP address. This was primarily because I chose to use Mailgun to handle my email. Mailgun recommends that you associate a subdomain with their service rather than your main website domain:

Mailgun recommends that you use a subdomain when you set up their service.

Mailgun recommends that you use a subdomain when you set up their service.

I’ll get to that in a moment. First, to associate your domain with your DigitalOcean droplet, you should follow their tutorial How To Set Up a Host Name with DigitalOcean. When I did that, I was able to get to my main site in a browser, just fine.

The trick came when setting up DNS for getting verified on Mailgun. As I found out, I am not the only person who has had trouble with this. I tried various combinations of the settings in the forum I linked to, to no avail. Finally, it dawned on me that I might need to set up a second DNS record at DigitalOcean for this to work. This proved to be the solution. Here are some screenshots with sensitive info blurred out.

Note that the IP address you associate with your mail subdomain is the one provided by Mailgun and NOT the IP address associated with your DigitalOcean droplet. This step confused me greatly and took me a while to figure out.

Installing your Laravel 5.1 Site

So I’m assuming that you have your Laravel site in a git repo somewhere such as Github, GitLab, or Bitbucket. I think the only thing that may be slightly tricky here is making sure that the www-data user has access to all of the files and directories it needs to display your site.

So the first step is to remove any directories inside of /var/www. When we clone our project it will contain the /public directory which will be the web root that we set up in nginx above:

[bash gutter=”false”]

$ cd /var/www

$ sudo rm -fr mysite

[/bash]

Then clone your repo and cd into the root directory:

[bash gutter=”false”]

$ git clone https://github.com/myaccount/mysite.git mysite

$ cd mysite

[/bash]

The next thing you have to do is create your .env file to hold all of the production values for your environment variables. In my case this looks something like:

[bash]

APP_ENV=production
APP_DEBUG=false
APP_URL=http://mysite.com/
APP_KEY=d58e3582afa99040e27b92b13c8f2280
APP_TIMEZONE=America/New_York

DB_HOST=localhost
DB_CONNECTION=mysql
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=AmAzing_Pa55werd

CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_DRIVER=redis

[/bash]

Once you’ve updated the environment variables, then you can run composer, npm, and your migrations:

[bash gutter=”false”]

$ composer install

$ npm install

$ php artisan migrate

[/bash]

Once everything is downloaded and installed, you can run chown to make sure the nginx user has appropriate access:

[bash gutter=”false”]$ sudo chown -R :www-data .[/bash]

And voila! Your site should be up and live!!! Go check it out!

Closing Thoughts and Next Steps

Overall, I’m amazed at how smoothly this whole process went. If I knew what I was doing, I probably could have completed this entire setup in an hour or two. As it was, it only took me about half a day. Not only that, but it was cheap! DigitalOcean only charges $5/month for their lowest price servers, which is more than enough for my current needs (click here to get a $10 credit when you sign up for a new account at DigitalOcean). Mailgun is free if you send/receive fewer than 10,000 emails per month and it saves you the trouble of setting up your own SMTP server, hosting email, and all that nonsense.

In my next post, I’m going to demonstrate how to set up a push-to-deploy system like what you would find at Laravel Forge or Envoyer. With this setup, your live site will be automatically updated whenever you push changes to your git server. Between that and Ubuntu’s automatic upgrades, it will make for a live production server with very little overhead in terms of maintenance. Stay tuned!

Leave a Reply for James Scott Cancel Reply

Write a Comment

Comment

Webmentions

  • Finally! Free SSL Certs!!! Let’s Encrypt! | Morphatic March 31, 2016

    […] To start out, here are the specs of my server (here’s my tutorial on how to set up a server like this): […]