With increasingly faster internet access, these days everyone is waiting for pages to load faster and faster.

So if your page does not load fast enough, you can lose too many visitors. According to studies, people wait up to 5 seconds for a page to load, if it does’nt they leave the site.

Another reason to lose visitors, is when one of your pages becomes popular on sites like digg, reddit, slashdot or stumble upon, and your server does not support the load.

This post will guide you step by step in the process of installing a robust server, capable of serving hundreds of pages per second, without losing performance. While simultaneously serving the pages fast enough to avoid you loosing visitors due to excessively long waiting times.

This Article shall have the following parts

  1. Arch Linux configuration
  2. Despite the fact that this tutorial is based on a Arch Linux server, you may easily adapt it to Debian, Ubuntu, CentOS or any other Linux distribution you may prefer.
  3. Security implementation
  4. Here, fail2ban is going to be used as the guardian of the server, you may prefer other way, and can easily choose from DenyHosts or Knockd or even a mix of this two methods knockd and fail2ban
  5. Basic configuration of Apache.
  6. Apache will act as an interpreter of PHP, and Nginx will send all the PHP code to the Apache server.
  7. Basic instalation of MySQL
  8. PHP installation
  9. Instalation of Drupal
  10. In fact, here we'll only provide links to the drupal page which covers this topic
  11. Instalation and configuration of Nginx
  12. Optional: Keep Nginx and Apache/PHP/MySQL on separate servers


  • You have Arch Linux installed and up to date
  • You have root access to the server
  1. Arch Linux configuration

  2. To install Arch Linux you can follow the Official Arch Linux Install Guide, once it is installed, you can follow these configuration hints.

    Set the Hostname

    With your favorite editor open the file /etc/rc.conf and look for this text:
    Modify it accordingly, it may look like this:
    Be sure to also edit the file /etc/hosts, it may end up looking like this.               localhost.localdomain   www localhost

    Set the time zone

    Once again edit the file /etc/rc.conf, look for the entry TIMEZONE and change it accordingly, you may find your zone and the format you should use in this file: /usr/share/zoneinfo, mine looks like this:

    Set the locale

    Edit the file /etc/rc.conf, and this time look for the entry LOCALE, change it to fit your needs, you may find the options in this file /etc/locale.gen, be sure to uncomment the option you choose to use in the /etc/rc.conf file. Now generate the locales by running:


    Create your admin user

    It is strongly recommended not to administrate your server as root, but instead create an admin user account and use the sudo command, it also recommended not to create a user "admin", better choose another user name, because admin and root are usually the targets of dictionary attacks. Create the user:

    useradd -m -g users -s /bin/bash -G wheel YOURUSERNAME

    Assign it a strong password:


    Install sudo program, and assign admin rights to the newly created user

    Install sudo:

    pacman -S sudo

    Edit the sudoers file:


    Look for this line and uncomment it:
    %wheel  ALL=(ALL) NOPASSWD: ALL
    Create it if it does not exist

    Install some useful and needed software

    I personally use this software, you may want to install others here:

    sudo pacman -Sy vim screen gzip htop

    If you get this error while installing vim:
    vim: /usr/bin/rview exists in filesystem
    Fix it, running:

    sudo rm /usr/bin/{view,rview}

    And then try again.
  3. Security implementation

  4. In order to secure the server we will use fail2ban: From Wikipedia

    Fail2Ban is an intrusion prevention framework written in the Python programming language. It is able to run on POSIX systems that have an interface to a packet-control system or firewall installed locally (for example, iptables or TCP

    Installing fail2ban

    sudo pacman -S fail2ban

    Configuring fail2ban Edit the file /etc/fail2ban/jail.conf and look for [ssh-iptables] section and change it to this:
    enabled  = false
    filter   = sshd 
    action   = iptables[name=SSH, port=ssh, protocol=tcp]
    logpath  = /var/log/auth.log                                                     
    maxretry = 5  
    You may also like to change the ban time, I use to set it to 24 hours, so once again in /etc/fail2ban/jail.conf and look for bantime, change 600 (default) to 86400 seconds.
    bantime  = 86400
    If you have a fixed IP that is used to access your server in a timely basis, add this ip to the ignoreip directive in /etc/fail2ban/jail.conf Start the fail2ban daemon

    sudo /etc/rc.d/fail2ban start

    Finally add fail2ban to the daemons list in /etc/rc.conf file.
    DAEMONS=(syslog-ng network netfs crond sshd fail2ban)
  5. Basic configuration of Apache.

  6. Apache will be used to interpret php only, it will listen to port 8080 on interface (localhost).

    Installing Apache

    sudo pacman -S apache

    Basic configuration

    Make Apache listen only to localhost at 8080 port. File excerpt: /etc/httpd/conf/httpd.conf
    Enable the needed modules, by default a lot of modules are enabled, is not in the scope of this tutorial, an indeep Apache fine tuning, but be sure you have these enabled.
    LoadModule rewrite_module modules/mod_rewrite.so
    LoadModule rpaf_module modules/mod_rpaf-2.0.so
    LoadModule php5_module modules/libphp5.so
    Also add this to the configuration file
    Include conf/extra/php5_module.conf
    AddType application/x-httpd-php .php
    AddType application/x-httpd-php-source .phps
    RPAFenable On
    # Enable reverse proxy add forward
    # which ips are forwarding requests to us
    In this tutorial is assumed that in Apache we will only run one server the root server, so the configuration for that virtual server is like this:
    DocumentRoot "/srv/http/nginx"
    <Directory />
        Options FollowSymLinks
        AllowOverride All
        Order deny,allow
        Deny from all
    <Directory "/srv/http/nginx">
        Options Indexes FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
    <IfModule dir_module>
        DirectoryIndex index.html
    <FilesMatch "^\.ht">
        Order allow,deny
        Deny from all
        Satisfy All
    ErrorLog "/var/log/httpd/error_log"
    LogLevel warn
    <IfModule log_config_module>
        LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
        LogFormat "%h %l %u %t \"%r\" %>s %b" common
        <IfModule logio_module>
          LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
        CustomLog "/var/log/httpd/access_log" common
    <IfModule alias_module>
        ScriptAlias /cgi-bin/ "/srv/http/cgi-bin/"
    <IfModule cgid_module>
    <Directory "/srv/http/cgi-bin">
        AllowOverride None
        Options None
        Order allow,deny
        Allow from all
    DefaultType text/plain
    <IfModule mime_module>
        TypesConfig conf/mime.types
        AddType application/x-compress .Z
        AddType application/x-gzip .gz .tgz
    Include conf/extra/httpd-multilang-errordoc.conf
    Include conf/extra/httpd-autoindex.conf
    Include conf/extra/httpd-languages.conf
    Include conf/extra/httpd-userdir.conf
    Include conf/extra/httpd-default.conf
    <IfModule ssl_module>
    SSLRandomSeed startup builtin
    SSLRandomSeed connect builtin

    Install mod_rpaf

    This will make Apache logs to actually show the IP of the client asking the web page rather than the, because actually is Nginx who is going to be passing requirements to Apache. mod_rpaf is in AUR so we will have to Install yaourt, once installed run:

    yaourt rpaf

    1 aur/mod_rpaf 0.6-2 (5)
        rpaf is for backend Apache servers what mod_proxy_add_forward is for frontend Apache servers
    ==>  Enter n° (separated by blanks, or a range) of packages to be installed
    ==>   ----------------------------------------------
    Select number 1, and follow instructions.

    Start Apache

    sudo /etc/rc.d/httpd start

    And finally add it to the daemons section in /etc/rc.conf
  7. Basic instalation of MySQL

  8. Install mysql:

    sudo pacman -S mysql

    Start mysql:

    sudo /etc/rc.d/mysql start

    Add this to /etc/mysql/my.cnf
    This will make MySQL to does't listen on a TCP/IP port at all, as Apache and PHP will be installed on the same server with MySQL there is no need to listen to TCP ports. If you are installing on a VPS with low memory, you may also want to read this Set the root password:

    mysqladmin -u root password password

    And restart MySQL

    sudo /etc/rc.d/mysql restart

    Add mysql to the daemons section in /etc/rc.conf file.
  9. PHP installation

  10. Install php:

    sudo pacman -S php php-apache

    Edit /etc/php/php.ini And uncomment by removing the ";" (semicolon) from this line
    All other configuration has been already done in the Apache section above.
  11. Installation of Drupal

  12. It is not in the scope of this Tutorial, Drupal installation, besides it is really well documented in Drupal Installation guide. Be sure to install boost module
  13. Instalation and configuration of Nginx

  14. Nginx will be used for serving static content, by using the boost module of Drupal, we will have the entire site as .html and .gz, so that will be static content. We will have to compile Nginx with the option --with-http_gzip_static_module, so Nginx will deliver the files compressed by the boost module for Drupal. Let's go on.

    Compile Nginx from sources

    I have already written a how-to for this, so please read Compile Nginx with gzip-static support, and then come back here. That is all with Nginx, we just need to create the configuration file: Here is mine
            worker_processes  4; # Default 1
            events {
                worker_connections  512; # Default 1024
            http {
                    include       mime.types;
                    default_type  application/octet-stream;
                    keepalive_timeout  65;
                    ## Compression
                    gzip              on;
                    gzip_buffers      16 8k;
                    gzip_comp_level   9;
                    gzip_http_version 1.1;
                    gzip_min_length   10;
                    gzip_types        text/plain text/css application/x-javascript text/xml;
                    gzip_vary         on;
                    gzip_static       on; #Needs compilation with gzip_static support
                    gzip_proxied      any;
                    gzip_disable      "MSIE [1-6]\.";
                    server {
                            listen       80;
                            server_name  www.go2linux.org;
                            root /srv/http/nginx/go2linux;
                            client_body_buffer_size 1m;
                            proxy_buffering on;
                            proxy_buffer_size 4k;
                            proxy_buffers 8 32k;
                        # To avoid bandwidth steal
                        location ~* \.(js|css|jpg|jpeg|gif|png|svg)$ {
                            valid_referers server_names blocked none ;
                            if ($invalid_referer) {
                                    return 403;
                            if (-f $request_filename) {
                                    expires      35d;
                                    add_header Cache-Control public;
                    location ~* ^/(index|boost_stats).php$ { #Add update in the parenthesis if you need to run update.php, same thing with cron, you can run cron locally, direct to 8080 port.
                            proxy_pass http://localhost:8080; 
                            proxy_set_header X-Real-IP  $remote_addr;
                            proxy_set_header Host $host;
                            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            location / {
                             try_files $uri @cache;
                    location @cache {
                            if ($query_string ~ ".+") {
                                    return 405;
                            if ($http_cookie ~ "DRUPAL_UID" ) {
                                    return 405;
                            if ($request_method !~ ^(GET|HEAD)$ ) {
                                    return 405;
                    error_page 405 = @drupal;
                    add_header Expires "Tue, 08 Sep 1973 07:00:00 GMT";
                    add_header Cache-Control "must-revalidate, post-check=0, pre-check=0";
                    try_files /cache/normal/$host/${uri}_.html /cache/perm/$host/${uri}_.css /cache/perm/$host/${uri}_.js /cache/$host/0$uri.html /cache/$host/0${uri}/index.html @drupal;
                    # This sends the php to Apache, running on port 8080
                    location @drupal {
                            proxy_pass http://localhost:8080;
                            proxy_set_header X-Real-IP  $remote_addr;
                            proxy_set_header Host $host;
                            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    # redirect server error pages to the static page /50x.html
                    error_page   500 502 503 504  /50x.html;
                    location = /50x.html {
                        root   html;
  15. Optional: Keep Nginx and Apache/PHP/MySQL on separate servers

  16. For even better performance, Nginx can run in an independent server, in that case these changes need to be done.


    In the /etc/httpd/conf/httpd.conf add
    Listen APACHE-SERVER-IP 8080


    Change the proxy_pass from to the IP of the Apache server.


    You may want to add some security by inserting an Iptables rule, and make the Apache server only accept connections to 8080 port from the Nginx server IP. If you go this way, try enabling boost crawling, as that server is going to be resting give them some work, and put it to generate the cache in advance. Also, install rsync in both servers, configure the nginx server to be able to log into the apache server through ssh with not password, be sure to do it as root. Then on the nginx server as root, enter this cron job.
    */2 * * * * rsync -e 'ssh -ax' -avz --delete --delete-excluded root@your-apache-server:/srv/http/nginx/ /srv/http/nginx/

If you find ways to improve this configuration, please share your ideas.

This tutorial was possible mainly thanks to this articles.

http://groups.drupal.org/node/46404 http://www.linode.com/wiki/index.php/Arch_Linux http://wiki.archlinux.org/index.php/LAMP