Following on from me trying to fit a WordPress install on a 128MB VPS while not sacrificing performance; I give you my rather comprehensive tutorial on how to install nginx with apc (php opt-code cache), varnish (caching proxy), wordpress and w3 cache.
Table Of Content
Creating a database for WordPress
Starting and stopping our services
Introduction
The purpose of this tutorial is to configure a single VPS with 128MB RAM to provide hosting for a single WordPress website by using Nginx as a webserver, MySQL as a database server, PHP-FPM as a fastCGI server, APC as an optcode cache, Varnish as a caching proxy and vsFTPd as a secure FTP server (optional step). I also use openSSH/SCP as the primary means to access the VPS with.
When I write [HOSTNAME] please subsitute everything (incl the brackets) with your hostname (eg. www.example.org).
When I write [USER] please subsitute it (incl the brackets) with your username (eg. example).
In fact, anything [something] please replace with your own values! (eg. [YourPassword]).
I’m running this on my new dedicated server running Proxmox (1.8) with kernel 2.6.32-4-pve and an openVZ debian 6 (32bit) guest, at 128MB RAM and 0MB swap*. This server is running on SSD’s (solid state disks). Running on SSD’s is really nice especially for high iops required to run a VM host.
*) Swap in an openVZ virtual machine is just memory, so 128 RAM + 128 swap would simply mean 256M overall memory. There is no difference. Hence why I just set RAM and not swap for openVZ vms.
Disclaimer
In case you don’t have this set-up, please be aware that you may not get this to work. Caveat emptor – your milage may vary. I suggest you run this on a test bed rather than anything you are using for production already! No warranties are given and no responsibility for time lost, and tears shed assumed. Now make a brew and let’s get on with the show.
Setting up the VPS
Removing stuff
Ref: http://www.lowendbox.com/blog/bootstraping-low-end-vps-with-pre-built-scripts/
We don’t really need rsyslog or portmap, so let’s remove them:
apt-get remove rsyslog portmap
Apt Sources
Adding dot-deb repo
Each line represents a separate command to be executed in sequence!
echo deb http://packages.dotdeb.org stable all >> /etc/apt/sources.list echo deb-src http://packages.dotdeb.org stable all >> /etc/apt/sources.list wget http://www.dotdeb.org/dotdeb.gpg cat dotdeb.gpg | apt-key add -
Adding main debian repos
Each line represents a separate command to be executed in sequence!
echo deb http://mirror.positive-internet.com/debian/ squeeze main non-free contrib >> /etc/apt/sources.list echo deb-src http://mirror.positive-internet.com/debian/ squeeze main non-free contrib >> /etc/apt/sources.list echo deb http://security.debian.org/ squeeze/updates main >> /etc/apt/sources.list echo deb-src http://security.debian.org/ squeeze/updates main >>/etc/apt/sources.list echo deb http://mirror.positive-internet.com/debian/ squeeze-updates main >> /etc/apt/sources.list echo deb-src http://mirror.positive-internet.com/debian/ squeeze-updates main >> /etc/apt/sources.list
Updating
apt-get update && apt-get upgrade
Adding base
apt-get install mc sudo iptables curl lsb-release
Adding Varnish repo
Ref: https://www.varnish-cache.org/installation/debian
Each line represents a separate command to be executed in sequence!
curl http://repo.varnish-cache.org/debian/GPG-key.txt | apt-key add - echo "deb http://repo.varnish-cache.org/debian/ $(lsb_release -s -c) varnish-3.0" >> /etc/apt/sources.list.d/varnish.list apt-get update apt-get install varnish
Adding a system user
adduser [USER]
Don’t forget to give your user a password, a strong one preferably.
Add user to sudo
visudo
– find:
root (ALL)
– add below:
[User] (ALL) ALL
Change SSH conf
mcedit /etc/ssh/sshd_config
–find:
PermitRootLogin yes
–change to:
PermitRootLogin no
–find:
X11Forwarding yes
–change to:
X11Forwarding no
–find:
Port 22
–change to:
7742
–add to end:
UseDNS no
service ssh restart
Now open a new terminal and connect to your VPS using the username you have chosen and make sure you are connecting to port 7742 and not the default 22! Once logged on, we can continue our journey with installing some software.
Installing Software
apt-get install nginx mysql-server mysql-client memcached php5 php-apc php-auth php-net-smtp php-net-socket php-pear php5-curl php5-gd php5-mcrypt php5-mysql php5-fpm php5-memcached php5-tidy vsftpd
Yes, it was as easy as that! Provided you answered ‘yes’ to the question whether you wanted to install the above and their dependencies, you are now almost done.
Configuring services
OK, I was lying. You are nowhere near done but it’s as good a time as any to make a fresh brew before we dive into configuring our services.
In this section we are setting up the following software:
- MySQL
- Nginx
- PHP5
- APC
- FPM
- memcache
- Varnish
- vsFTPd
Configuring MySql
References:
- Reducing MySQL Memory Usage for Low End Boxes
http://www.lowendbox.com/blog/reducing-mysql-memory-usage-for-low-end-boxes/
Each line represents a separate command to be executed in sequence!
mv /etc/mysql/my.cnf /etc/mysql/my.cnf.org mcedit /etc/mysql/my.cnf
–insert:
[client] port = 3306 socket = /var/run/mysqld/mysqld.sock [mysqld] user = mysql pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock port = 3306 basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp language = /usr/share/mysql/english skip-external-locking bind-address = 127.0.0.1 thread_stack = 192K myisam-recover = BACKUP expire_logs_days = 10 max_binlog_size = 100M bulk_insert_buffer_size = 8M connect_timeout=10 interactive_timeout=50 join_buffer=1M key_buffer=16K max_allowed_packet=1M table_cache = 4 max_connect_errors=10 max_connections=100 max_heap_table_size = 8M myisam_sort_buffer_size=8M query_cache_limit = 4M query_cache_size = 250M query_cache_type = 1 query_prealloc_size = 65K query_alloc_block_size = 128K read_buffer_size=1M read_rnd_buffer_size=768K record_buffer=1M safe-show-database skip-innodb skip-locking skip-networking sort_buffer=64K thread_cache_size=1024 thread_concurrency=8 tmp_table_size = 32M wait_timeout=500 [mysqldump] quick max_allowed_packet = 16M [mysql] no-auto-rehash [mysqld_safe] socket = /var/run/mysqld/mysqld.sock nice = -5 open_files_limit = 8192 [isamchk] key_buffer = 8M sort_buffer = 8M read_buffer = 4M write_buffer = 4M !includedir /etc/mysql/conf.d/
Configuring nginx
References:
- nginx wordpress config
http://wiki.nginx.org/Wordpress
- Nginx + PHP-FPM + MySql + APC + WordPress
http://shortbutuseful.com/88/install-nginx-phpfpm-mysql-apc-wordpress-debian-6/
- Optimizing WordPress with Nginx, Varnish, APC, W3 Total Cache, and Amazon S3
http://danielmiessler.com/blog/optimizing-wordpress-with-nginx-varnish-w3-total-cache-amazon-s3-and-memcached
Each line represents a separate command to be executed in sequence!
mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.org mcedit /etc/nginx/nginx.conf
–insert:
user www-data;
worker_processes 1;
pid /var/run/nginx.pid;
events {
use epoll;
worker_connections 512;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
error_log /var/log/nginx/error.log;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 3;
server_tokens off;
access_log off;
client_max_body_size 32m;
client_body_timeout 60;
client_header_timeout 60;
send_timeout 60;
reset_timedout_connection on;
# If using Cloudflare, uncomment the following to get proper originating IPs
#set_real_ip_from 204.93.240.0/24;
#set_real_ip_from 204.93.177.0/24;
#set_real_ip_from 199.27.128.0/21;
#set_real_ip_from 173.245.48.0/20;
#set_real_ip_from 103.22.200.0/22;
#set_real_ip_from 141.101.64.0/18;
#real_ip_header CF-Connecting-IP;
# end Cloudflare
gzip on;
gzip_disable "MSIE [1-6].(?!.*SV1)";
gzip_vary on;
gzip_static on;
gzip_proxied any;
gzip_comp_level 9;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Each line represents a separate command to be executed in sequence!
mv /etc/nginx/sites-available/default /etc/nginx/sites-available/default.org mcedit /etc/nginx/sites-available/default
–insert:
upstream php { server 127.0.0.1:9000; }
server {
listen 8080; ## listen for ipv4; this line is default and implied
#listen [::]:80 default ipv6only=on; ## listen for ipv6
root /home/[USER]/www;
index index.php index.html index.htm;
server_name [HOSTNAME];
location / {
root /home/[USER]/www/;
index index index.php;
try_files $uri/ $uri /index.php?q=$uri&&$args;
port_in_redirect off;
}
location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico|xml)$ {
access_log off;
log_not_found off;
expires max;
root /home/[USER]/www/;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
#NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
fastcgi_pass php;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /home/[USER]/www/$fastcgi_script_name;
include fastcgi_params;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_intercept_errors on;
fastcgi_ignore_client_abort off;
fastcgi_connect_timeout 60;
fastcgi_send_timeout 360;
fastcgi_read_timeout 360;
fastcgi_buffer_size 128k;
fastcgi_buffers 8 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
}
location ~ /.htaccess { deny all; log_not_found off; access_log off; }
location ~ /.htpasswd { deny all; log_not_found off; access_log off; }
location = /favicon.ico { allow all; log_not_found off; access_log off; }
location = /robots.txt { allow all; log_not_found off; access_log off; }
}
Fix CGI Path Info
mcedit /etc/php5/fpm/php.ini
–find:
cgi.fix_pathinfo
Uncomment the command by removing the ; in front and replace the defauly 1 with a 0!
Configuring PHP5-FPM
Each line represents a separate command to be executed in sequence!
mv /etc/php5/fpm/php-fpm.conf /etc/php5/fpm/php-fpm.conf.org mcedit /etc/php5/fpm/php-fpm.conf
–insert:
[global] pid = /var/run/php5-fpm.pid error_log = /var/log/php5-fpm.log log_level = notice emergency_restart_threshold = 5 emergency_restart_interval = 2 process_control_timeout = 2 daemonize = yes include=/etc/php5/fpm/pool.d/*.conf
Each line represents a separate command to be executed in sequence!
mv /etc/php5/fpm/pool.d/www.conf /etc/php5/fpm/pool.d/www.conf.org mcedit /etc/php5/fpm/pool.d/www.conf
–insert:
[www] ;prefix = /path/to/pools/$pool listen = 127.0.0.1:9000 listen.backlog = -1 listen.allowed_clients = 127.0.0.1 listen.owner = [USER] listen.group = [USER] listen.mode = 0666 user = [USER] group = [USER] pm = dynamic pm.max_children = 15 pm.start_servers = 5 pm.min_spare_servers = 2 pm.max_spare_servers = 10 pm.max_requests = 0 pm.status_path = /fpmstatus ping.path = /ping ping.response = pong request_terminate_timeout = 10 request_slowlog_timeout = 10 slowlog = /var/log/$pool.log.slow ;rlimit_files = 1024 ;rlimit_core = 0 ;chroot = ;chdir = / catch_workers_output = yes env[HOSTNAME] = $HOSTNAME env[PATH] = /usr/local/bin:/usr/bin:/bin env[TMP] = /tmp env[TMPDIR] = /tmp env[TEMP] = /tmp ;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f [email protected] php_flag[display_errors] = off php_admin_value[error_log] = /var/log/fpm-php.www.log php_admin_flag[log_errors] = on php_admin_value[memory_limit] = 32M php_admin_value[date.timezone] = Europe/London php_value[upload_max_filesize] = 10M php_value[max_execution_time] = 120
Configuring Varnish
Each line represents a separate command to be executed in sequence!
mv /etc/default/varnish /etc/varnish/default_old mcedit /etc/default/varnish
–insert:
START=yes NFILES=131072 MEMLOCK=82000 DAEMON_OPTS="-a :80 \ -T localhost:6082 \ -f /etc/varnish/default.vcl \ -S /etc/varnish/secret \ -s malloc,1G"
Each line represents a separate command to be executed in sequence!
mv /etc/varnish/default.vcl /etc/varnish/default.vcl.org mcedit /etc/varnish/default.vcl
–insert:
backend default { .host = "localhost"; .port = "8080"; }
acl purge { "localhost"; }
sub vcl_recv { if (req.request == "PURGE") { if (!client.ip ~ purge) { error 405 "Not allowed."; } return(lookup); }
if (req.url ~ "^/$") { unset req.http.cookie; } }
sub vcl_hit { if (req.request == "PURGE") { set obj.ttl = 0s; error 200 "Purged."; } }
sub vcl_miss { if (req.request == "PURGE") { error 404 "Not in cache."; }
if (!(req.url ~ "wp-(login|admin)")) { unset req.http.cookie; }
if (req.url ~ "^/[^?]+.(jpeg|jpg|png|gif|ico|js|css|txt|gz|zip|lzma|bz2|tgz|tbz|html|htm)(\?.|)$") {
unset req.http.cookie;
set req.url = regsub(req.url, "\?.$", "");
}
if (req.url ~ "^/$") { unset req.http.cookie; } }
sub vcl_fetch { if (req.url ~ "^/$") { unset beresp.http.set-cookie; }
if (!(req.url ~ "wp-(login|admin)")) { unset beresp.http.set-cookie; }}
Configuring vsftpd (optional)
I’m using vsftpd to provide a secure means of accessing the server which is bound to the system user, locked to their home directory and can only be accessed using TLS encryption. Access is also provided via SCP (SSH) of course but most people use sFTP still and there are a good few apps for iOS (etc) devices that only support (s)FTP rather than SSH/SCP. Feel free to omit this step if you don’t need FTP!
Ref: http://ubuntuforums.org/showthread.php?t=518293
Each line represents a separate command to be executed in sequence!
mv /etc/vsftpd.conf /etc/vsftpd.conf.org mcedit /etc/vsftpd.conf
–insert:
listen=YES anonymous_enable=NO local_enable=YES write_enable=YES dirmessage_enable=YES use_localtime=YES xferlog_enable=YES connect_from_port_20=YES idle_session_timeout=600 data_connection_timeout=120 nopriv_user=ftp #ascii_upload_enable=YES #ascii_download_enable=YES chroot_local_user=YES chroot_list_enable=NO secure_chroot_dir=/var/run/vsftpd/empty pam_service_name=vsftpd rsa_cert_file=/etc/ssl/private/vsftpd.pem userlist_deny=NO userlist_enable=YES userlist_file=/etc/vsftpd.allowed_users ssl_enable=YES allow_anon_ssl=NO force_local_data_ssl=YES force_local_logins_ssl=YES ssl_tlsv1=YES ssl_sslv2=NO ssl_sslv3=NO force_dot_files=YES max_per_ip=2 max_clients=20 pasv_min_port=12000 pasv_max_port=12100 require_ssl_reuse=NO
echo [USER] >> /etc/vsftpd.allowed_users
Enabling SSL
Ref: http://www.cyberciti.biz/tips/configure-vsfptd-secure-connections-via-ssl-tls.html
/usr/bin/openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout /etc/ssl/private/vsftpd.pem -out /etc/ssl/private/vsftpd.pem
Configure your SSL as you please. Eg. Country code ‘GB’, location ‘London’, name [yourhostname]
Installing WordPress
Each line represents a separate command to be executed in sequence!
Become the user, go into the web root in its home directory we created earlier. Then download, unpack and move wordpress files into place. Lastly we remove the wordpress directory.
su [USER] cd /home/[USER]/www wget http://wordpress.org/latest.tar.gz tar -zxvf latest.tar.gz mv wordpress/* /home/[USER]/www/ rmdir wordpress
Creating a database for WordPress
Each line represents a separate command to be executed in sequence!
mysql -uroot -p CREATE DATABASE wordpress; GRANT ALL PRIVILEGES ON wordpress.* TO admin@localhost IDENTIFIED BY '[YOUR_PASSWORD]' WITH GRANT OPTION; quit
Securing your VPS
I’m using Iptables as my firewall here. Note that blocking port 9000 (our PHP FPM) you will get Bad Gateway errors! There’s probably room for improvement here but for now adding the port to the allowed rules fixes this.
// Show what is loaded in our firewall
iptables -L
// Flush everything we have in our firewall
iptables -F
// Allow established sessions
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
or
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
// Allow services
iptables -A INPUT -p tcp --dport 7742 -j ACCEPT iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 8080 -j ACCEPT iptables -A INPUT -p tcp --dport 9000 -j ACCEPT iptables -A INPUT -p tcp --dport 21 -j ACCEPT iptables -A INPUT -p tcp --dport 12000:12100 -j ACCEPT
// End iptables by blocking everything that we didn’t explicitly allowed earlier
iptables -A INPUT -j DROP
// backup iptables to a file
iptables-save > /home/[USER]/iptables.rules
// to restore our rules
iptables-restore > /home/[USER]/iptables.rules
Making sure iptables rules are reloaded on reboot
Ref: http://www.debian-administration.org/articles/445
mcedit /etc/network/if-up.d/iptables
–insert:
#!/bin/sh iptables-restore < /home/[USER]/iptables.rules
chmod +x /etc/network/if-up.d/iptables
Reboot to make sure it is working (iptables -L)
Starting and stopping our services
Since I’m a lazy sysadmin I like to use scripts to help me do as little as possible. Here are two very simple scripts, not even very good ones, but one starts all services and the other stops them.
The Stop Script
mcedit stopscript.sh
–insert:
#!/bin/bash service mysql stop service memcached stop service varnish stop service nginx stop service cron stop service postfix stop service vsftpd stop /etc/init.d/php5-fpm stop
The Start Script
mcedit startscript.sh
–insert:
#!/bin/bash service mysql start service memcached start service varnish start service nginx start service cron start service postfix start service vsftpd start /etc/init.d/php5-fpm start
Note that Varnish prefers to be started before nginx for some reason, although the ports are different.
Chmodding +x the scripts will make them executable, which is handy for scripts ;o
chmod +x startscript.sh stopscript.sh
Run the scripts and have a look
sudo ./stopscript.sh
Sudo is important as we need superuser powers to mess with services! If all goes well you should see output from the commands in the terminal. Check to see stuff really is not running by issuing the following:
ps aux
Once satisfied all is off you may like to find out how little (or much) memory you are using:
free -m
Great, let’s start everything and do this again:
sudo ./startscript.sh ps aux free -m
Again you should have seen services starting, ps aux will show you what’s running and free -m will let you know how much memory is being used.
Problems
To see what’s running on which port:
netstat -tulpn
For some weird reason I have to stop nginx, then start varnish and then start nginx again as varnish will otherwise refuse to bind to port 80 despite nginx running on 8080! This is taken into account in my start/stop scripts above.
Closing Notes
Exim Installation and Configuration on Debian
May I point you in the general direction of an excellent how-to guide written by Phil Paradis over Linode?
Link: http://library.linode.com/email/exim/send-only-mta-debian-5-lenny
http://library.linode.com/email/exim/send-only-mta-debian-6-squeeze
You may have to do this:
apt-get install exim4-base exim4-config
then
apt-get install exim4-daemon-light
and lastly
dpkg-reconfigure exim4-config
Thanks to Dleonard0 (http://ubuntuforums.org/showpost.php?p=10411454&postcount=4)
To test your set-up you probably need something like the mail command or in fact mailx:
apt-get install bsd-mailx
This will enable you to do this:
echo "This is a test." | mail -s Testing [your email address]
Check what’s happening with:
mailq
Free Memory
Without nginx, php-fpm, mysql, memcached, varnish, vsftpd, cron, and postfix running the vps consumes ~14MB. Running are bash, ssh, iptables, and atd. Please note this is with a single active user session.
Using a different virtualisation technique these results may differ.
Also, using a more lightweight ssh server (like dropbear) will also save you some RAM.
I have 128MB in total, of which 14MB are used with just the bear neccesities running. This is our baseline.
With all services, but vsftpd running and one user accessing the wordpress powered website, the memory usage figures are somewhat higher.
Throwing vsftpd into the mix we end up roughly the same figure, give or take an MB or two.
Script to show memory usage
I just recently came across a very handy script written by Draig Brady at pixelbeat.org, that shows memory usage per program in Linux. You can find it on his website. Note that this script shows usage per program, not per process.
wget http://www.pixelbeat.org/scripts/ps_mem.py chmod +x ps_mem.py sudo ./ps_mem.py
The APT problem
This looks OK but sadly APT and Aptitude are consuming a lot of memory doing their stuff when updating and installing software. Especially Aptitude is eating a lot. (ref: http://blog.aplikacja.info/2009/12/aptitude-vs-apt-get-memory-usage-on-debian/)
There appears to be no way to tweak this other than installing packages manually. However, I rather bump up my VPS memory to 256 or 512MB than having to maintain things by hand the old fashioned (and more efficient way). Call me lazy but time is money ;D
Conclusions
In closing, yes you can run this all quite nicely on a small low end box but expect to work on it a lot harder, whether it’s tweaking settings or maintaining software, it’s great fun. If you really must have the smallest VPS and ignore APT then install from source and sort out dependencies manually.
However, if you are running a business like me, then spending a wee bit more a month on a slightly larger VPS and perhaps selecting a host that doesn’t have ‘rock bottom’ pricing but offers a little more help, makes a a more sensible choice.
Shameless self promotion
If you like, I have this VPS ready made for you to move in to and even configure it to suit your details (username, hostname, passwords). The latter which I suggest you change upon first login of course. A 128MB openVZ container as described here with 3GB space (extendible within reason free of charge!) can be yours for a token amount of just £9.99 including VAT (£8.32 exclusive).
Drop me an email stating ‘WordPress VPS’ in the subject line and the link to this post to get one today!
Small Print
Payment is via Paypal (monthly subscription) and you will get a proper VAT invoice from my UK LTD company. Support is via email, Skype, IM (various) or the old fashioned way by phone during business hours and via email at all other times. I promise to respond to your enquiries within the hour during business hours.
The price includes the (extortionate) charges PayPal levvies but it’s the simplest way to get things done.
There are no bandwidth restrictions as such and I encourage you to make use of Cloudflare’s excellent and free (as in beer) service to help reduce wastage. Your VPS shares a metred 100Mbit connection. The server is based in France and is hosted by OVH. It has 16GB of RAM and 120GB of SSD space in total which is shared amongst everyone. Most VPSs are my web design customers and I like to keep an eye out to make sure everybody receives the best service possible.
For other restrictions please see OVH’s terms as they will block the whole server if one customer is found in violation of them. Hence why I make sure this doesn’t happen by reserving the right to refuse service to anyone who I find might not play by the rules.
Other articles in this series
Related posts in this (little) series:
- How to: Testing Nginx with APC, Varnish, WordPress and W3 Cache on a 128MB VPS
- How to: Install Nginx with APC, Varnish, WordPress and W3 Cache on a 128MB VPS
- How to: Benchmark Nginx and Varnish for a WordPress Site
- How to: Make Money from Offering VPS Hosting
What are you doing for a living? How are you using this little tutorial? Let me know and leave me your comments below or drop me a tweet.


Pingback: Testing Nginx with APC, Varnish, Wordpress and W3 Cache on a 128MB VPS | Axel Segebrecht's Blog
Pingback: NGINX + WordPress | Paul Ruescher
Pingback: Blitz.io Tests Against Apache Worker, Apache Prefork, Apache with PHP-FPM, and Nginx with PHP-FPM in a 256MB RAM VPS - VirtuallyHyper
Pingback: Guide: WordPress with Maximum Performance and Speed