Introduction
These are the steps I followed to create a production web server on Ubuntu 7.10 Gutsy. If you use exactly the same versions of everything you should be able to follow these notes exactly. I expect they will work for at least Ubuntu 6 upwards, and for the versions of the components upwards. Each of Apache 2, MySQL 5 and PHP 5 included major changes from their previous major versions, so it’s unlikely that these notes will work for older releases.
If you just want to play with PHP/MySQL I strongly suggest installing XAMPP, as this is much easier to get up and running. However, if you are running a live web site with thousands of real users, this document might be helpful. It covers installing from source code, which involves compiling and linking with various options to optimise for speed. At the end of it you should come out with a web server and database configured with performance and security in mind.
When I set out to do this write up, I thought I would encourage good practice and use sudo for all the commands that need it. In practice typing those 5 extra characters all the time was just too much, so I was logged in as root as well as my normal user. Luckily I never make msitakes. So in the interests of sticking to the facts, these notes will occasionally assume you are the root user, indicated by the prompt in front of the command. The ones starting with the hash (pound) sign are to be done as root. The ones with a dollar are to be done as your normal user. If you find yourself typing or pasting this prompt into your own command line I’m afraid you’re in way over your head!
Tidy up
Before you start installing these applications, you should check if any are already installed, as will be the case if the “LAMP server” option was selected when Ubuntu was originally installed. So grep for the corresponding package names in the list of installed packages:
dpkg --list|grep -e httpd -e apache -e mysql -e php
Look for “ii” beside the package name, indicating it’s installed. If LAMP was already installed from binaries, you will probably see a list similar to this:
ii apache2 2.2.4-3build1 Next generation, scalable, extendable web se ii apache2-mpm-prefork 2.2.4-3build1 Traditional model for Apache HTTPD ii apache2-utils 2.2.4-3build1 utility programs for webservers ii apache2.2-common 2.2.4-3build1 Next generation, scalable, extendable web se ii libapache2-mod-php5 5.2.3-1ubuntu6 server-side, HTML-embedded scripting languag ii libdbd-mysql-perl 4.004-2 A Perl5 database interface to the MySQL data ii libmysqlclient15off 5.0.45-1ubuntu3 MySQL database client library ii mysql-client-5.0 5.0.45-1ubuntu3 MySQL database client binaries ii mysql-common 5.0.45-1ubuntu3 MySQL database common files ii mysql-server 5.0.45-1ubuntu3 MySQL database server (meta package dependin ii mysql-server-5.0 5.0.45-1ubuntu3 MySQL database server binaries ii php5-common 5.2.3-1ubuntu6 Common files for packages built from the php ii php5-mysql 5.2.3-1ubuntu6 MySQL module for php5
If you see any of these, you should uninstall them before continuing. My hosting company had installed Ubuntu for me, and when I first logged in Apache2 and mysql were running. If this is the case you can stop them as root:
# ps -wef|grep apache2 root 4006 1 0 09:41 ? 00:00:00 /usr/sbin/apache2 -k start www-data 4025 4006 0 09:41 ? 00:00:00 /usr/sbin/apache2 -k start www-data 4026 4006 0 09:41 ? 00:00:00 /usr/sbin/apache2 -k start www-data 4027 4006 0 09:41 ? 00:00:00 /usr/sbin/apache2 -k start www-data 4028 4006 0 09:41 ? 00:00:00 /usr/sbin/apache2 -k start www-data 4029 4006 0 09:41 ? 00:00:00 /usr/sbin/apache2 -k start www-data 4140 4006 0 14:24 ? 00:00:00 /usr/sbin/apache2 -k start www-data 4141 4006 0 14:24 ? 00:00:00 /usr/sbin/apache2 -k start www-data 4142 4006 0 14:24 ? 00:00:00 /usr/sbin/apache2 -k start root 4157 4125 0 14:36 pts/0 00:00:00 grep apache2 # apache2ctl stop # ps -wef|grep apache2 root 4162 4125 0 14:36 pts/0 00:00:00 grep apache2 # ps -wef|grep mysql root 3857 1 0 09:41 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe mysql 3897 3857 0 09:41 ? 00:00:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --user=mysql --pid-file=/var/run/mysqld/mysqld.pid --skip-external-locking --port=3306 --socket=/var/run/mysqld/mysqld.sock root 3898 3857 0 09:41 ? 00:00:00 logger -p daemon.err -t mysqld_safe -i -t mysqld root 4355 4310 0 18:54 pts/1 00:00:00 grep mysql # /etc/init.d/mysql stop * Stopping MySQL database server mysqld [ OK ] # ps -wef|grep mysql root 4395 4310 0 18:55 pts/1 00:00:00 grep mysql
Now remove the offending packages:
# 3 remove php5-mysql libapache2-mod-php5 php5-common # apt-get remove libdbd-mysql-perl mysql-server mysql-server-5.0 mysql-client-5.0 # apt-get remove libmysqlclient15off mysql-common # apt-get remove apache2 apache2-mpm-prefork apache2.2-common apache2-utils
Apache2 was also configured to start at reboot. To remove this, as root:
# rm /etc/init.d/apache2 # update-rc.d apache2 remove
Prerequisites
In order to build we’ll need make, GNU C, etc. These are bundled in the package build-essential. We also need ncurses development for MySQL to compile:
# aptitude install build-essential # aptitude install libncurses5-dev
TIP: If like me, your CD is corrupt, or if putting the CD in is just too much hassle anyway, you can tell aptitude to look on the internet for packages. Edit /etc/apt/sources.list and comment out the line that starts “deb cdrom”
I saw this error after trying to install build-essential:
Failed to fetch http://security.ubuntu.com/ubuntu/pool/main/l/linux-source-2.6.20/linux-libc-dev_2.6.20-16.29_i386.deb 404 Not Found [IP: 91.189.88.31 80] E: Unable to fetch some archives, maybe run aptitude update or try with --fix-missing?
So I did “aptitude update” then reran the install command and it worked. How easy was that? I can’t believe I persevered with RPM all these years!
Get the sources
- Apache from http://httpd.apache.org/. Go to the download page and choose a mirror close to you. I used version 2.2.6.
- MySQL from http://dev.mysql.com/. The community server is the free one! Go to the bottom of this page where the source is, and pick a mirror for the tar.gz. Don’t get the download from the section marked Ubuntu because these are binaries. I used version 5.0.45.
- PHP from http://www.php.net/downloads.php. You want the complete source code, and bzip is a much smaller file than gzip. I used version 5.2.4.
Download them to your home directory, then unzip them in /usr/local/src:
cd /usr/local/src # tar xzf ~/httpd-2.2.6.tar.gz # tar xzf ~/mysql-5.0.45.tar.gz # tar xjf ~/php-5.2.4.tar.bz2
Warning: Lots of things can go wrong at this point. It is considered best practice to wear your lucky Ubuntu thong.
Compile MySQL
Add a group and a user to run MySQL as. These might already exist on your system, which is fine.
# groupadd mysql # useradd -g mysql -c "MySQL Server" mysql
Give ownership of the source to your current user so you can make. Let’s say my normal user is mjhinds, so as root:
# cd /usr/local/src/mysql-5.0.45 # chown -R mjhinds .
The Ubuntu help page on compiling MySQL suggests that if you’re on an Intel P4 you could optimise for this architecture by setting the following environment variables before compiling:
$ export CHOST="i686-pc-linux-gnu" $ export CFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer -msse -mmmx -mfpmath=sse" $ export CXXFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer -msse -mmmx -mfpmath=sse -felide-constructors -fno-exceptions -fno-rtti"
There are also settings for AMD and a generic Linux one. You should use one of these, if only to tell it to omit frame pointers. The MySQL documentation says compiling on x86 without frame pointers makes mysqld 1-4% faster. So export those variables as mjhinds, then generate your Makefile by running configure:
$ cd /usr/local/src/mysql-5.0.45/ $ ./configure \ --prefix=/usr/local/mysql \ --localstatedir=/usr/local/mysql/data \ --with-mysqld-user=mysql \ --with-unix-socket-path=/tmp/mysql.sock \ --without-debug \ --without-bench \ --with-mysqld-ldflags=-all-static \ --disable-shared \ --with-charset=utf8
To briefly explain those options:
- prefix: the default is /usr/local. /usr/local/mysql is much tidier!
- localstatedir: similarly, your data goes into /usr/local/var by default
- with-mysqld-user: tell it the user we created which we’ll run as
- with-unix-socket-path: hmm, this might be the default anyway. No harm specifying it. This is for local connections to the database.
- without-debug: turn off debugging for a decent speed improvement
- without-bench: turn off benchmarking
- with-mysqld-ldflags: static linking for the server is much faster
- disable-shared: don’t build shared library
- with-charset: utf8 is the most flexible while not using as much space as utf16. I couldn’t find a definitive answer, but I believe latin1 doesn’t include the euro symbol.
Check out this page which talks about compile time optimisations.
IMPORTANT! If you make changes to your configure option, you must do a “make distclean” before re-running configure. Otherwise it might pick up some cached settings, and you could end up with a broken installation.
$ make
Took 21 minutes on my 1GHz Pentium 3 () with half a gig of RAM. No, that’s not my production server 🙂 Now install the binaries as root:
# cd /usr/local/src/mysql-5.0.45/ # make install
And create MySQL’s data dictionary:
# ./scripts/mysql_install_db
Set the permissions on the installed files:
# chown -R root:mysql /usr/local/mysql # chown -R mysql:mysql /usr/local/mysql/data
Strip the symbols from the object file. Think this is a minor performance increase?
# strip /usr/local/mysql/libexec/mysqld
Copy the configuration file (this assumes a decent amount of memory):
# cp support-files/my-large.cnf /etc/my.cnf # chown root:sys /etc/my.cnf # chmod 644 /etc/my.cnf
There are a few lines we need to change in /etc/my.cnf:
- Add the line “innodb_file_per_table” under [mysqld]. This will make your life as a DBA easier.
- If you are running MySQL on the same machine as the web server, uncomment the line “skip-networking”. This means we can only connect via Unix sockets or named pipes.
- Uncomment all the lines that start with “innodb”.
Configure MySQL to start automatically. I have a feeling this isn’t necessary any more, but no harm trying to do it again:
# cp ./support-files/mysql.server /etc/init.d/mysql # chmod +x /etc/init.d/mysql # update-rc.d mysql defaults
Test this. The following command should display a few dots then a star. Then hopefully MySQL is running!
# /etc/init.d/mysql start
I prefer to add a link to the mysql binaries I use in /usr/bin than add MySQL’s bin directory to the path:
# ln -s /usr/local/mysql/bin/mysql /usr/bin/mysql # ln -s /usr/local/mysql/bin/mysqldump /usr/bin/mysqldump
Now we can connect:
mysql -u root Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1 Server version: 5.0.45-log Source distribution Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> show tables; ERROR 1046 (3D000): No database selected mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | test | +--------------------+ 3 rows in set (0.00 sec) mysql> exit Bye
Note the lack of security here. Let’s fix that:
/usr/local/mysql/bin/mysqladmin -u root password your-password
Test the new password:
mysql -u root -p
Now drop the test database and remove a couple of extra users that aren’t needed:
drop database test; use mysql; delete from db; delete from user where not (host="localhost" and user="root"); flush privileges;
# /etc/init.d/mysql stop
Compile Apache
Add a group and a user to run Apache as. These might already exist on your system, which is fine.
# groupadd httpd # useradd httpd -g httpd -c "Apache" -d /dev/null -s /sbin/nologin
Give yourself ownership of the source so you can make:
# cd /usr/local/src/httpd-2.2.6/ # chown -R mjhinds .
Set the environment vars appropriate for Apache and your architecture. A safe default would be “-O2”. For Pentium 4 we can try:
$ export CFLAGS="-O3 -march=pentium4 -msse -mmmx -funroll-loops -mfpmath=sse"
With Apache 2 a lot of modules are enabled by default. Check this page to see which ones you might be able to exclude. Now configure accordingly:
$ ./configure \ --prefix=/usr/local/apache \ --disable-cgi \ --disable-imap \ --disable-userdir \ --enable-rewrite=static \ --enable-so=static
IMPORTANT! If you make changes to your configure option, you must do a “make distclean” before re-running configure. Otherwise it might pick up some cached settings, and you could end up with a broken installation.
$ make
Took just 6 minutes on . Now install the binaries as root:
# cd /usr/local/src/httpd-2.2.6/ # make install
make apache auto start Process running: /bin/sh /usr/bin/mysqld_safe /usr/sbin/mysqld –basedir=/usr –datadir=/var/lib/mysql –user=mysql –pid-file=/var/run/mysql/mysql.pid –skip-external-locking –port=3306 –socket=/var/run/mysqld/mysqld.sock
PHP required packages
There are a few libraries we need to compile PHP with useful options. As root:
# aptitude install libxml2-dev libpng3 libpng12-dev mcrypt libncurses5 libjpeg62-dev libmcrypt-dev libmhash-dev libcurl3-dev
For JPEG support I had to hack in a link to get configure to find the JPEG library:
# ln -s /usr/lib/libjpeg.so.62.0.0 /usr/lib/libjpeg.so
Compile PHP
Give yourself ownership of the source so you can make:
# cd /usr/local/src/php-5.2.4 # chown -R mjhinds .
Set the environment vars appropriate for PHP and your architecture. A safe default would be “-O2”. For Pentium 4 we can try:
$ export CFLAGS="-O3 -march=pentium4 -msse -mmmx -funroll-loops -mfpmath=sse"
$ cd /usr/local/src/php-5.2.4 $ ./configure \ --prefix=/usr/local/php5 \ --with-apxs2=/usr/local/apache/bin/apxs \ --with-mysql=/usr/local/mysql \ --with-mysql-sock=/tmp/mysql.sock \ --with-mcrypt \ --with-mhash \ --with-curl \ --with-curlwrappers \ --disable-cgi \ --enable-mbstring=all \ --with-gd \ --enable-gd-native-ttf \ --with-ttf \ --with-jpeg-dir=/usr/lib \ --enable-magic-quotes
$ make
knocked this one out in 12 minutes. Install the binaries as root:
# cd /usr/local/src/php-5.2.4 # make install
Now copy the ini file “warmly recommended” for production:
# cp php.ini-recommended /usr/local/php5/lib/php.ini
Now edit /usr/local/php5/lib/php.ini and find the following line:
error_reporting = E_ALL
This is a good, finicky setting for dev, but with my sloppy legacy code I’d rather not have errors popping up all over production, so best to change to:
error_reporting = E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR
By default you also get the command line interface, which can be useful for scheduling tasks relating to your web application. Rather than add PHP’s bin directory to your path you can link to it from /usr/bin:
# ln -s /usr/local/php5/bin/php /usr/bin/php
Configure Apache
Edit /usr/local/apache/conf/httpd.conf and change the lines:
User daemon Group daemon
to
User httpd Group httpd
Find the following line and change to suit your domain:
ServerAdmin you@example.com
Uncomment the following line and change to suit your domain:
#ServerName www.example.com:80
Assuming you put your web pages in /web/domain/www, find following line and change to suit your domain:
DocumentRoot "/usr/local/apache/htdocs"
Under where it says “This should be changed to whatever you set DocumentRoot to.” you should do so!
Change
AllowOverride None
to
AllowOverride All
In the <Directory%gt; entry for your domain, change
Options Indexes FollowSymLinks
to
Options FollowSymLinks
(this stops directory listings)
Inside the <IfModule mime_module> add:
# Parse these types as PHP AddType application/x-httpd-php .html .php .xml .js
Inside the <IfModule dir_module> change to:
DirectoryIndex index.html index.php
Find the lines starting:
#ErrorDocument
Uncomment and point them to your own custom error pages.
Test apache startup:
# /usr/local/apache/bin/apachectl start
If all goes well you should be able to point your browser at the server on port 80. If it’s not working, have a look at /usr/local/apache/logs/error_log
When it works, make a link to apachectl for command line use:
# ln -s /usr/local/apache/bin/apachectl /usr/bin
And make it startup automatically when the server starts:
# cd /etc/init.d # rm apache2 # update-rc.d apache2 remove # ln -s /usr/local/apache/bin/apachectl apache # update-rc.d apache start 91 2 3 4 5 . stop 09 0 1 6 .
So now you can create your page root…
# mkdir -p /web/domain/www # chown -R httpd:httpd /web
…put a “hello world” in there, and fingers crossed you should be able to browse to it.
Quick note on rebuilding:
For any of these compile steps, if you need to make changes to the configuration and run ./configure again be sure to do a “make clean” before “make”.
If you recompile PHP, make sure Apache isn’t running. This is the polite way to bring down Apache:
$ sudo apachectl stop
…and of course start to start it again.
Optional stuff
PDFLib
You must pay for PDFLib, but it is pretty darn useful. You can trial it for free with full functionality, but every PDF has a watermark added. Well, I say watermark. More like a bloody great tea stain to be honest. Download the PHP version from www.pdflib.com and untar as root:
# cd /usr/local/src # tar xzf ~/PDFlib-7.0.2-Linux-php.tar.gz # mkdir /usr/local/php5/lib/php/extensions # cp PDFlib-7.0.2-Linux-php/bind/php5/php-520/libpdf_php.so /usr/local/php5/lib/php/extensions
Now edit /usr/local/php5/lib/php.ini and change the line that starts “extension_dir” to this:
extension_dir = "/usr/local/php5/lib/php/extensions/"
Also add this line with all the other extensions:
extension=libpdf_php.so
Cronolog
Cronolog is a very useful utility for rolling an application’s log files. There is an Ubuntu package of version 1.6.2, but I prefer the 1.7.0 beta version because it allows you to set ownership on the files. Download it from http://cronolog.org/patches/.
# cd /usr/local/src # tar xzf ~/cronolog-1.7.0-beta.tar.gz # cd cronolog-1.7.0 # CFLAGS="-O3 -DFILE_MODE=0640" ./configure # make # make install
I like to keep my log files in my /web directory beside the server’s page root. So:
$ cd /web/servername $ mkdir logs
Now configure Apache to use cronolog by editing /usr/local/apache/conf/httpd.conf. We need to change 2 lines, one for the access log and one for the error log. Find the lines that starts with ErrorLog and CustomLog and change them as follows (they’re not next to each other in the file):
ErrorLog "|/usr/local/sbin/cronolog --set-gid=httpd --symlink=/web/servername/logs/error.log --prev-symlink=/web/servername/logs/error.log.prev /web/servername/logs/%Y/%m/error_%Y%m%d.log" CustomLog "|/usr/local/sbin/cronolog --set-gid=httpd --symlink=/web/servername/logs/access.log --prev-symlink=/web/servername/logs/access.log.prev /web/servername/logs/%Y/%m/access_%Y%m%d.log" combined
memcached
This seems to be the caching daemon of choice for highly loaded websites. There are 3 things to install: the memcache daemon, libevent which it requires, and the PHP client library. The recommended way of building the PHP client is with PECL, but since I have no need or desire to install this on the production server I built the client on a separate machine and just copied over the .so file it created.
First, install libevent:
$ sudo aptitude install libevent-dev
Now download, build and install memcached:
$ wget http://www.danga.com/memcached/dist/memcached-1.2.6.tar.gz $ cd /usr/local/src $ sudo tar xzf ~/memcached-1.2.6.tar.gz $ cd memcached-1.2.6 $ ./configure $ make $ sudo make install
Now you can test it with the following command which uses just 10Mb of memory, listens on localhost port 11211 and runs as the nobody user:
$ memcached -m 10 -l 127.0.0.1 -u nobody -p 11211
We’ve left out the -d option so it runs in the foreground (or shows you errors if there are any). Use CTRL-C to stop it.
This is how I installed PEAR and built the PHP client (on a test server). It assumes you’ve already installed PHP to /usr/local/php5.
# aptitude install php-pear # aptitude install autoconf # cd /usr/bin # ln -s /usr/local/php5/bin/phpize # ln -s /usr/local/php5/bin/php-config # pecl install memcache
It will tell you where it has installed the library to, probably “/usr/local/php5/lib/php/extensions/no-debug-non-zts-20060613/memcache.so”.
Copy this file to the production server into your PHP extension directory as specified in php.ini.
$ cp ~/memcache.so /usr/local/php5/lib/php/extensions/
Edit /usr/local/php5/lib/php.ini to load it by adding this line with the other extensions:
extension=memcache.so
Now you should be able to call memcache from PHP. Start memcache as above and create the following test page:
<?php $memcache = new Memcache; $memcache->connect('localhost', 11211) or die ("Could not connect"); $version = $memcache->getVersion(); echo "Server's version: ".$version." \n"; $tmp_object = new stdClass; $tmp_object->str_attr = 'test'; $tmp_object->int_attr = 123; $memcache->set('key', $tmp_object, false, 10) or die ("Failed to save data at the server"); echo "Store data in the cache (data will expire in 10 seconds) \n"; $get_result = $memcache->get('key'); echo "Data from the cache: \n"; var_dump($get_result); ?>
There should also be a PHP admin page installed as part of the PHP client at /usr/share/php/docs/memcache/memcache.php. Copy this page from the test server to somewhere under your production page root. Note that you’ll have to change the $MEMCACHE_SERVERS array to suit your setup:
$MEMCACHE_SERVERS[] = '127.0.0.1:11211'; // add more as an array #$MEMCACHE_SERVERS[] = 'mymemcache-server2:11211'; // add more as an array
If you’re relying on the built in authentication you should change the user/password in the same file.
To get memcached to automatically start, create the file /etc/init.d/memcached as root as follows:
#!/bin/sh -e /usr/local/bin/memcached -d -m 10 -p 11211 -u nobody
Make it executable, and add it to the start up scripts:
# chmod u+x /etc/init.d/memcached # update-rc.d memcached defaults
More than one daemon
Once you’re comfortable running memcached you might want to increase the number of daemons in order to split out the various logical objects that you’re caching. The advantage of this is that you can flush one cache without affecting the others. To run another daemon, just add another line to /etc/init.d/memcached and use the next available port number. Of course you’ll have to update your application so it knows which ports map to which objects.
References
Most of this document was compiled by taking the best from the following sources:
- Securing MySQL: step-by-step
- MySQL Reference Manual, Source Installation Overview
- MySQL Reference Manual, Typical configure options
- PHP Apache notes
- PHP UTF-8 cheatsheet
- UTF-8 emails
- Config notes from Ubuntu
- Building a LAMP server
- Tuning a LAMP server
- Apache Features
- Optimizing MySQL: Hardware and the Mysqld Variables
- Cronolog
- memcached
- Installing memcached
- PHP, memcached & MySQL
http://www.michaelhinds.com/tech/linux/ubuntu-server.html#mysql