Total upgrade: NginX 1.9 + PHP7 + MySQL 5.6 on Ubuntu 14.04


It has been a year since I created the server to run multiple WordPress websites. A number of software has been upgraded since then.

The following software had been upgraded:

  • PHP v.7-й, it is still in beta state but we can try it on. It is risky to use it for big production systems so I’ll use it as it is. For others I’d recommend to run php v.5.6.10 which is the most recent stable release. The approach will not differ but you’ll need to download onther package.
  • Nginx v.1.9.3 had been released few weeks ago.
  • MySQL server v.5.6 had been released too. The developers say that it is much faster than the previous version
  • I decided to start new hosting year with the new server and new software. Unfortunately there is no Ubuntu v.15.04 in the list of the oficial OS for the EC2 instances so I’ll work with Ubuntu 14.04

    Another issue is in fact that most of the new packages don’t have the installation candidates in the software repositiories. This is why I’ll compile almost everything from the source packages.

    ModSecurity:

    In order to sleep good at night I’ll include the mod_security support into NginX

    Install dependencies first:

    apt-get install libxml2 libxml2-dev apache2-dev libcurl4-openssl-dev

    I’m going to work in the following folder:

    cd /usr/local/src

    Download and unpack it:

    wget https://www.modsecurity.org/tarball/2.9.0/modsecurity-2.9.0.tar.gz
    tar xf modsecurity-2.9.0.tar.gz
    cd modsecurity-2.9.0

    Let’s prepare the build. No need to install it:

    ./configure –enable-standalone-module
    make

    PHP 7

    Install dependencies first:

    apt-get install libpng++-dev libpng12-dev libmcrypt-dev spawn-fcgi -y

    Download and unpack it:

    cd /usr/local/src
    wget https://downloads.php.net/~ab/php-7.0.0beta2.tar.gz
    tar xf php-7.0.0beta2.tar.gz
    cd php-7*

    The configuration options don’t differ from the previous versions

    ./configure –bindir=/usr/bin –with-config-file-path=/etc –with-curl –with-mhash –enable-mysqlnd –with-mysqli –with-gd –with-pdo-mysql –with-mcrypt –enable-mbstring –with-openssl –with-pcre-regex –enable-soap –with-zlib
    make -j 2
    make install

    There are the following configuration files in the source folder:

    • php.ini-development
    • php.ini-production

    Feel free to use one of them as php.ini. For the first run I’ll use the development version:

    cp php.ini-development /etc/php.ini

    Let’s create the init.d script:

    vim /etc/init.d/php-fastcgi

    /etc/init.d/php-fastcgi

    #!/bin/bash
    
    PHP_SCRIPT=/usr/bin/php-fastcgi
    FASTCGI_USER=nginx
    FASTCGI_GROUP=nginx
    PID_DIR=/tmp/
    PID_FILE=/tmp/php-fastcgi.pid
    RET_VAL=0
    
    case "$1" in
        start)
          if [[ ! -d $PID_DIR ]]
          then
            mkdir $PID_DIR
            chown $FASTCGI_USER:$FASTCGI_GROUP $PID_DIR
            chmod 0770 $PID_DIR
          fi
          if [[ -r $PID_FILE ]]
          then
            echo "php-fastcgi already running with PID `cat $PID_FILE`"
            RET_VAL=1
          else
            $PHP_SCRIPT
            RET_VAL=$?
          fi
      ;;
        stop)
          PIDS=$(pidof php-cgi);
          if [ "${#PIDS[@]}" -ne "0" ];
          then
            for p in $PIDS;
            do
              kill -9 $p;
            done
          fi
      ;;
        restart)
          if [[ -r $PID_FILE ]]
          then
            kill -9 $(cat $PID_FILE)
            rm $PID_FILE
            RET_VAL=$?
          else
            echo "Could not find PID file $PID_FILE"
          fi
          $PHP_SCRIPT
          RET_VAL=$?
      ;;
        status)
          if [[ -r $PID_FILE ]]
          then
            echo "php-fastcgi running with PID `cat $PID_FILE`"
            RET_VAL=$?
          else
            echo "Could not find PID file $PID_FILE, php-fastcgi does not appear to be running"
          fi
      ;;
        *)
          echo "Usage: php-fastcgi {start|stop|restart|status}"
          RET_VAL=1
      ;;
    esac
    exit $RET_VAL

    Second script:

    vim /usr/bin/php-fastcgi

    /usr/bin/php-fastcgi

    #!/bin/bash
    
    FASTCGI_USER=nginx
    FASTCGI_GROUP=nginx
    ADDRESS=127.0.0.1
    PORT=9000
    PIDFILE=/tmp/php-fastcgi.pid
    CHILDREN=10
    PHP5=/usr/bin/php-cgi
    
    /usr/bin/spawn-fcgi -a $ADDRESS -p $PORT -P $PIDFILE -C $CHILDREN -u $FASTCGI_USER -g $FASTCGI_GROUP -f $PHP5

    Let’s make them executable:

    chmod +x /usr/bin/php-fastcgi
    chmod +x /etc/init.d/php-fastcgi

    Enable autoload on system startup:

    /usr/sbin/update-rc.d -f php-fastcgi defaults

    Nginx:

    I’ll compile NginX with mod_security, geoip and srcache. Srcache allows NginX to use Memcache as a cache storage.
    Prepare GeoIP. The approach doesn’t differ from this one:

    cd /usr/local/src
    wget http://www.maxmind.com/download/geoip/api/c/GeoIP-latest.tar.gz
    tar xf GeoIP-latest.tar.gz && cd GeoIP-1.*
    ./configure
    make
    make install

    During the installation I got the following message:

    Libraries have been installed in:
    /usr/local/lib

    GeoIP libraries have been installed into /usr/local/lib/ folder. Let’s create a symbolic link:

    ln -s /usr/local/lib/libGeoIP.so.1.6.0 /usr/lib/libGeoIP.so.1

    Prepare srcache
    The list or releases is available on the following page:
    https://github.com/openresty/srcache-nginx-module/tags

    There is the installation manual.
    Let’s download, unpack it and include into the NginX with --add-module=:

    cd /usr/local/src
    wget https://github.com/openresty/srcache-nginx-module/archive/v0.30.tar.gz
    tar xf v0.30.tar.gz

    Download NginX and unpack it:

    cd /usr/local/src
    wget http://nginx.org/download/nginx-1.9.3.tar.gz
    tar xf nginx-1.9.3.tar.gz
    cd nginx-1.9.3

    Include mod_security and http_stub_status_module into configure options. I need http_stub_status_module get better statistics in NewRelic:

    ./configure –prefix=/etc/nginx –user=nginx –group=nginx –with-http_ssl_module –with-http_spdy_module –with-http_realip_module –with-http_gzip_static_module –with-http_auth_request_module –add-module=/usr/local/src/modsecurity-2.9.0/nginx/modsecurity –with-http_stub_status_module –add-module=/usr/local/src/srcache-nginx-module-0.30
    make -j 2
    make install

    Unfortunately init script is not included into the source package but I found it on theoficial website:

    vim /etc/init.d/nginx

    /etc/init.d/nginx
    #! /bin/sh
    ### BEGIN INIT INFO
    # Provides:          nginx
    # Required-Start:    $remote_fs $syslog
    # Required-Stop:     $remote_fs $syslog
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # Short-Description: nginx init.d dash script for Ubuntu <=9.10.
    # Description:       nginx init.d dash script for Ubuntu <=9.10.
    ### END INIT INFO
    #------------------------------------------------------------------------------
    # nginx - this Debian Almquist shell (dash) script, starts and stops the nginx 
    #         daemon for ubuntu 9.10 and lesser version numbered releases.
    #
    # description:  Nginx is an HTTP(S) server, HTTP(S) reverse \
    #               proxy and IMAP/POP3 proxy server.  This \
    #		script will manage the initiation of the \
    #		server and it's process state.
    #
    # processname: nginx
    # config:      /usr/local/nginx/conf/nginx.conf
    # pidfile:     /acronymlabs/server/nginx.pid
    # Provides:    nginx
    #
    # Author:  Jason Giedymin
    #          <jason.giedymin AT gmail.com>.
    #
    # Version: 2.0 02-NOV-2009 jason.giedymin AT gmail.com
    # Notes: nginx init.d dash script for Ubuntu <=9.10.
    # 
    # This script's project home is:
    # 	http://code.google.com/p/nginx-init-ubuntu/
    #
    #------------------------------------------------------------------------------
    #                               MIT X11 License
    #------------------------------------------------------------------------------
    #
    # Copyright (c) 2009 Jason Giedymin, http://Amuxbit.com formerly
    #				     http://AcronymLabs.com
    #
    # Permission is hereby granted, free of charge, to any person obtaining
    # a copy of this software and associated documentation files (the
    # "Software"), to deal in the Software without restriction, including
    # without limitation the rights to use, copy, modify, merge, publish,
    # distribute, sublicense, and/or sell copies of the Software, and to
    # permit persons to whom the Software is furnished to do so, subject to
    # the following conditions:
    #
    # The above copyright notice and this permission notice shall be
    # included in all copies or substantial portions of the Software.
    #
    # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    #------------------------------------------------------------------------------
     
    #------------------------------------------------------------------------------
    #                               Functions
    #------------------------------------------------------------------------------
    . /lib/lsb/init-functions
     
    #------------------------------------------------------------------------------
    #                               Consts
    #------------------------------------------------------------------------------
    PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
    DAEMON=/usr/local/sbin/nginx
     
    PS="nginx"
    PIDNAME="nginx"				#lets you do $PS-slave
    PIDFILE=$PIDNAME.pid                    #pid file
    PIDSPATH=/var/run
     
    DESCRIPTION="Nginx Server..."
     
    RUNAS=root                              #user to run as
     
    SCRIPT_OK=0                             #ala error codes
    SCRIPT_ERROR=1                          #ala error codes
    TRUE=1                                  #boolean
    FALSE=0                                 #boolean
     
    lockfile=/var/lock/subsys/nginx
    NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"
     
    #------------------------------------------------------------------------------
    #                               Simple Tests
    #------------------------------------------------------------------------------
     
    #test if nginx is a file and executable
    test -x $DAEMON || exit 0
     
    # Include nginx defaults if available
    if [ -f /etc/default/nginx ] ; then
            . /etc/default/nginx
    fi
     
    #set exit condition
    #set -e
     
    #------------------------------------------------------------------------------
    #                               Functions
    #------------------------------------------------------------------------------
     
    setFilePerms(){
     
            if [ -f $PIDSPATH/$PIDFILE ]; then
                    chmod 400 $PIDSPATH/$PIDFILE
            fi
    }
     
    configtest() {
    	$DAEMON -t -c $NGINX_CONF_FILE
    }
     
    getPSCount() {
    	return `pgrep -f $PS | wc -l`
    }
     
    isRunning() {
            if [ $1 ]; then
                    pidof_daemon $1
                    PID=$?
     
                    if [ $PID -gt 0 ]; then
                            return 1
                    else
                            return 0
                    fi
            else
                    pidof_daemon
                    PID=$?
     
                    if [ $PID -gt 0 ]; then
                            return 1
                    else
                            return 0
                    fi
            fi
    }
     
    #courtesy of php-fpm
    wait_for_pid () {
            try=0
     
            while test $try -lt 35 ; do
     
                    case "$1" in
                            'created')
                            if [ -f "$2" ] ; then
                                    try=''
                                    break
                            fi
                            ;;
     
                            'removed')
                            if [ ! -f "$2" ] ; then
                                    try=''
                                    break
                            fi
                            ;;
                    esac
     
                    #echo -n .
                    try=`expr $try + 1`
                    sleep 1
            done
    }
     
    status(){
    	isRunning
    	isAlive=$?
     
    	if [ "${isAlive}" -eq $TRUE ]; then
                    echo "$PIDNAME found running with processes:  `pidof $PS`"
            else
                    echo "$PIDNAME is NOT running."
            fi
     
     
    }
     
    removePIDFile(){
    	if [ $1 ]; then
                    if [ -f $1 ]; then
            	        rm -f $1
    	        fi
            else
    		#Do default removal
    		if [ -f $PIDSPATH/$PIDFILE ]; then
            	        rm -f $PIDSPATH/$PIDFILE
    	        fi
            fi
    }
     
    start() {
            log_daemon_msg "Starting $DESCRIPTION"
     
    	isRunning
    	isAlive=$?
     
            if [ "${isAlive}" -eq $TRUE ]; then
                    log_end_msg $SCRIPT_ERROR
            else
                    start-stop-daemon --start --quiet --chuid $RUNAS --pidfile $PIDSPATH/$PIDFILE --exec $DAEMON \
                    -- -c $NGINX_CONF_FILE
                    setFilePerms
                    log_end_msg $SCRIPT_OK
            fi
    }
     
    stop() {
    	log_daemon_msg "Stopping $DESCRIPTION"
     
    	isRunning
    	isAlive=$?
            if [ "${isAlive}" -eq $TRUE ]; then
                    start-stop-daemon --stop --quiet --pidfile $PIDSPATH/$PIDFILE
     
    		wait_for_pid 'removed' $PIDSPATH/$PIDFILE
     
                    if [ -n "$try" ] ; then
                            log_end_msg $SCRIPT_ERROR
                    else
                            removePIDFile
    	                log_end_msg $SCRIPT_OK
                    fi
     
            else
                    log_end_msg $SCRIPT_ERROR
            fi
    }
     
    reload() {
    	configtest || return $?
     
    	log_daemon_msg "Reloading (via HUP) $DESCRIPTION"
     
            isRunning
            if [ $? -eq $TRUE ]; then
    		`killall -HUP $PS` #to be safe
     
                    log_end_msg $SCRIPT_OK
            else
                    log_end_msg $SCRIPT_ERROR
            fi
    }
     
    quietupgrade() {
    	log_daemon_msg "Peforming Quiet Upgrade $DESCRIPTION"
     
            isRunning
            isAlive=$?
            if [ "${isAlive}" -eq $TRUE ]; then
    		kill -USR2 `cat $PIDSPATH/$PIDFILE`
    		kill -WINCH `cat $PIDSPATH/$PIDFILE.oldbin`
     
    		isRunning
    		isAlive=$?
    		if [ "${isAlive}" -eq $TRUE ]; then
    			kill -QUIT `cat $PIDSPATH/$PIDFILE.oldbin`
    			wait_for_pid 'removed' $PIDSPATH/$PIDFILE.oldbin
                            removePIDFile $PIDSPATH/$PIDFILE.oldbin
     
    			log_end_msg $SCRIPT_OK
    		else
    			log_end_msg $SCRIPT_ERROR
     
    			log_daemon_msg "ERROR! Reverting back to original $DESCRIPTION"
     
    			kill -HUP `cat $PIDSPATH/$PIDFILE`
    			kill -TERM `cat $PIDSPATH/$PIDFILE.oldbin`
    			kill -QUIT `cat $PIDSPATH/$PIDFILE.oldbin`
     
    			wait_for_pid 'removed' $PIDSPATH/$PIDFILE.oldbin
                            removePIDFile $PIDSPATH/$PIDFILE.oldbin
     
    			log_end_msg $SCRIPT_ok
    		fi
            else
                    log_end_msg $SCRIPT_ERROR
            fi
    }
     
    terminate() {
            log_daemon_msg "Force terminating (via KILL) $DESCRIPTION"
     
    	PIDS=`pidof $PS` || true
     
    	[ -e $PIDSPATH/$PIDFILE ] && PIDS2=`cat $PIDSPATH/$PIDFILE`
     
    	for i in $PIDS; do
    		if [ "$i" = "$PIDS2" ]; then
    	        	kill $i
                            wait_for_pid 'removed' $PIDSPATH/$PIDFILE
    			removePIDFile
    		fi
    	done
     
    	log_end_msg $SCRIPT_OK
    }
     
    destroy() {
    	log_daemon_msg "Force terminating and may include self (via KILLALL) $DESCRIPTION"
    	killall $PS -q >> /dev/null 2>&1
    	log_end_msg $SCRIPT_OK
    }
     
    pidof_daemon() {
        PIDS=`pidof $PS` || true
     
        [ -e $PIDSPATH/$PIDFILE ] && PIDS2=`cat $PIDSPATH/$PIDFILE`
     
        for i in $PIDS; do
            if [ "$i" = "$PIDS2" ]; then
                return 1
            fi
        done
        return 0
    }
     
    case "$1" in
      start)
    	start
            ;;
      stop)
    	stop
            ;;
      restart|force-reload)
    	stop
    	sleep 1
    	start
            ;;
      reload)
    	$1
    	;;
      status)
    	status
    	;;
      configtest)
            $1
            ;;
      quietupgrade)
    	$1
    	;;
      terminate)
    	$1
    	;;
      destroy)
    	$1
    	;;
      *)
    	FULLPATH=/etc/init.d/$PS
    	echo "Usage: $FULLPATH {start|stop|restart|force-reload|status|configtest|quietupgrade|terminate|destroy}"
    	echo "       The 'destroy' command should only be used as a last resort." 
    	exit 1
    	;;
    esac
     
    exit 0
    

    Make them executable and enable autoload on system startup:

    sudo chmod +x /etc/init.d/nginx
    sudo /usr/sbin/update-rc.d -f nginx defaults

    Let’s create a symblic link of the logs folder:

    ln -s /etc/nginx/logs/ /var/log/nginx

    Create nginx system user:

    echo “/bin/false” >> /etc/shells
    useradd -d /etc/nginx -s /bin/false nginx

    Edit main configuration file:

    vim /etc/nginx/conf/nginx.conf

    Sent user to ‘nginx’:

    user nginx;

    Some security tweeks:

            client_body_buffer_size 4K;
            client_header_buffer_size 4k;
            large_client_header_buffers 4 4k;
            limit_conn_zone $binary_remote_addr zone=slimits:5m;

    Uncomment the ‘logformat’ and define the paths to log files:

        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
    
            ##
            # Logging Settings
            ##
            access_log /var/log/nginx/access.log main;
            error_log /var/log/nginx/error.log;
    

    Configure compression:

            ##
            # Gzip Settings
            ##
    
            gzip on;
    
            gzip_disable "msie6";
    
            gzip_vary on;
            # gzip_proxied any;
            gzip_comp_level 6;
            gzip_buffers 64 8k;
            gzip_http_version 1.1;
    # This is single line. 
            gzip_types text/plain text/html text/css application/json application/x-javascript text/xml application/xml alication/xml+rss text/javascript;

    Add the following into http section:

    include /etc/nginx/sites/*.conf;

    Create folder for configuration files of the websites:

    mkdir /etc/nginx/sites

    The example of the website configuration file:

    vim /etc/nginx/sites/mywebsite.conf

    Вносим в него следующие:

    server {
        listen 80;
        server_name mywebsite.com www.mywebsite.com;
    
        root /var/www/html;
    
        index index.php;
    
        if ($host ~ !^(mywebsite.com|www.mywebsite.com)$) {
            rewrite ^ http://www.mywebsite.com$request_uri? permanent;
        }
    
        # Limit methods, allowed on server to GET, HEAD and POST
        if ($request_method !~ ^(GET|HEAD|POST)$ ) {
            return 444;
        }
    
        location ~* \.(ico|gif|jpeg|jpg|png|eot|ttf|swf|woff)$ {
            root /var/www/html;
            expires 30d;
            access_log off;
        }
    
        location ~* \.(css|js)$ {
            root /var/www/html;
            expires 7h;
            access_log off;
        }
    
        location / {
            try_files $uri $uri/ /index.php;
        }
    
        location ~ \.(php|html)$ {
            root /var/www/html;
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }

    MySQL

    MySQL is the las in the line. This is the easiest part because there are deb packages available for download on the following page:
    http://dev.mysql.com/downloads/mysql/

    Get prepared:

    apt-get install man-db libaio1

    Download bundle file and unpack it:

    mkdir /usr/local/src/mysql
    cd /usr/local/src/mysql
    wget http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-server_5.6.26-1ubuntu14.04_amd64.deb-bundle.tar
    tar xf mysql-server_5.6.26-1ubuntu14.04_amd64.deb-bundle.tar

    Install the packages in the following order:

    dpkg -i mysql-common_5.6.26-1ubuntu14.04_amd64.deb

    Client depends on mysql-community-client :

    dpkg -i mysql-community-client_5.6.26-1ubuntu14.04_amd64.deb
    dpkg -i mysql-client_5.6.26-1ubuntu14.04_amd64.deb

    Server depends on mysql-community-server:

    dpkg -i mysql-community-server_5.6.26-1ubuntu14.04_amd64.deb
    dpkg -i mysql-server_5.6.26-1ubuntu14.04_amd64.deb

    I’d recommend to install the dev package just in case if you’ll need to compile something with mysql support. This step is not required:

    dpkg -i mysql-community-source_5.6.26-1ubuntu14.04_amd64.deb

    That is it. Now you can upload content and configure your websites. WordPress 4.2.4 ran without an issue.

    Useful articles:

    Additional resource used:
    http://sharadchhetri.com/2015/02/21/install-nginx-source-code-ubuntu-14-04-lts/

    Share Button

    Leave a Reply

    You must be logged in to post a comment.