VPS Configuration for Apache, MySQL, PHP

After having tried out SliceHost and Amazon EC2 micro instance, Linode is the only VPS that worked out well for my needs even though EC2 Micro offers 768MB RAM versus Linode’s 512MB. The main bottleneck I faced was with WordPress sites which are taxing on CPU and disk IO. They also leak memory.

After trying out various LAMP configurations the one that worked best in the end was generated using Linode’s own configuration script presented here with minor changes. This script is for the default install which includes Apache 2 + PHP Prefork + MySQL 5 and modifies the config files directly (my.cnf, php.ini, apache2.conf).

The script also makes backups of the config file (though if you run it twice, the old backup will be overwritten so make your own backups just in case).

The script sets MaxRequestsPerChild to 500 which is helpful to mitigate WordPress/PHP memory leaks since this causes Apache to recycle worker processes every so often. You should experiment with higher settings here until you encounter memory leak issues.

#!/bin/bash
#
# StackScript Bash Library
#
# Copyright (c) 2010 Linode LLC / Christopher S. Aker 
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# * Neither the name of Linode LLC nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.

function mysql_tune {
   # Tunes MySQL's memory usage to utilize the percentage of memory you specify, defaulting to 40%
   # $1 - the percent of system memory to allocate towards MySQL

   MYSQLCONF=/etc/mysql/my.cnf
   cp $MYSQLCONF $MYSQLCONF.backup

   if [ ! -n "$1" ];
      then PERCENT=40
      else PERCENT="$1"
   fi

   # sed -i -e 's/^#skip-innodb/skip-innodb/' $MYSQLCONF # disable innodb - saves about 100M

   MEM=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo) # how much memory in MB this system has
   MYMEM=$((MEM*PERCENT/100)) # how much memory we'd like to tune mysql with
   MYMEMCHUNKS=$((MYMEM/4)) # how many 4MB chunks we have to play with

   # mysql config options we want to set to the percentages in the second list, respectively
   OPTLIST=(key_buffer sort_buffer_size read_buffer_size read_rnd_buffer_size myisam_sort_buffer_size query_cache_size)
   DISTLIST=(75 1 1 1 5 15)

   for opt in ${OPTLIST[@]}; do
      sed -i -e "/\[mysqld\]/,/\[.*\]/s/^$opt/#$opt/" $MYSQLCONF
   done

   for i in ${!OPTLIST[*]}; do
      val=$(echo | awk "{print int((${DISTLIST[$i]} * $MYMEMCHUNKS/100))*4}")
      if [ $val -lt 4 ]
         then val=4
      fi
      config="${config}\n${OPTLIST[$i]} = ${val}M"
   done

   sed -i -e "s/\(\[mysqld\]\)/\1\n$config\n/" $MYSQLCONF
}

function apache_tune {
   # Tunes Apache's memory to use the percentage of RAM you specify, defaulting to 40%
   # $1 - the percent of system memory to allocate towards Apache

   APACHECONF=/etc/apache2/apache2.conf

   cp $APACHECONF $APACHECONF.backup

   if [ ! -n "$1" ];
      then PERCENT=40
      else PERCENT="$1"
   fi

   PERPROCMEM=10 # the amount of memory in MB each apache process is likely to utilize
   MEM=$(grep MemTotal /proc/meminfo | awk '{ print int($2/1024) }') # how much memory in MB this system has
   MAXCLIENTS=$((MEM*PERCENT/100/PERPROCMEM)) # calculate MaxClients
   MAXCLIENTS=${MAXCLIENTS/.*} # cast to an integer
   MAXREQUESTSPERCHILD=500
   KEEPALIVETIMEOUT=2
   sed -i -e "s/\(^[ \t]*MaxClients[ \t]*\)[0-9]*/\1$MAXCLIENTS/" $APACHECONF
   sed -i -e "s/\(^[ \t]*MaxRequestsPerChild[ \t]*\)[0-9]*/\1$MAXREQUESTSPERCHILD/" $APACHECONF
   sed -i -e "s/\(^[ \t]*KeepAliveTimeout[ \t]*\)[0-9]*/\1$KEEPALIVETIMEOUT/" $APACHECONF
}

function php_tune {
   PHPINI=/etc/php5/apache2/php.ini

   # Tunes PHP to utilize up to 32M per process
   sed -i'-orig' 's/memory_limit = [0-9]\+M/memory_limit = 32M/' $PHPINI
}

mysql_tune 40
apache_tune 40
php_tune

/etc/init.d/apache2 reload
/etc/init.d/mysql reload

By default the script allocates 40% memory each to MySQL and Apache. You can change two lines near the bottom to read mysql_tune 50 and apache_tune 30 for example, if you want to allow MySQL to use 50% memory and Apache 30%. However, make sure the Apache + MySQL combined don’t take more than 80% otherwise the rest of the system won’t have any RAM left to work with.

6 Responses



This article is no longer open for comments