I recently purchased a VPS Classic from OVH to migrate my blog. It worked well on my previous host service, but it was a shared service, which means sometimes the access time to a page was too long (very very long!).
A difference with a VPS (Virtual Private Server) compared to a shared hosting service is that you need to manage all the server stuff: it’s an empty box you need to configure to do what you want. Since the base VPS I bought has only 512 MB of RAM, I tried to carefully select and tune all the installed services.
All the following instructions are for the CentOS Linux distribution.
Install the Web Server#
First of all you need to add some external (not base) repositories:
rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
cat > /etc/yum.repos.d/nginx.repo << EOF
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1
EOFNow you are ready to install all required packages:
yum --enablerepo=remi,remi-php55 install nginx php-fpm php-common php-mysqlnd php-xml php-gd php-pdo mysql-serverIf everything worked, your environment is ready for your WordPress blog.
Configuration#
Nginx (Engine X) web server does not include a module to use PHP as backend language, for this reason you should have an external “PHP server” to handle this kind of pages, for example PHP-FPM (PHP FastCGI Process Manager). In this setup we’ll leave the php-fpm configuration with all the default parameters (later we will tune it up…). That means it starts with a TCP listener (127.0.0.1:9000) and we must configure nginx to send all HTTP requests to it.
Create a file in /etc/nginx/conf.d named, for example, blog.conf. The following is my configuration file that is already optimized for the W3C TotalCache Plugin:
server {
listen 5.135.145.38:80;
server_name blog.mornati.net;
root /path/to/wordpress/file/blog;
index index.php index.html index.htm;
access_log /var/log/nginx/blog.access.log;
error_log /var/log/nginx/blog.error.log;
# Use gzip compression
# gzip_static on; # Uncomment if you compiled Nginx using --with-http_gzip_static_module
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_buffers 16 8k;
gzip_http_version 1.0;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript image/png image/gif image/jpeg;
# Rewrite minified CSS and JS files
location ~* \.(css|js) {
if (!-f $request_filename) {
rewrite ^/wp-content/w3tc/min/(.+\.(css|js))$ /wp-content/w3tc/min/index.php?file=$1 last;
# Use the following line instead for versions of W3TC pre-0.9.2.2
# rewrite ^/wp-content/w3tc/min/([a-f0-9]+)\/(.+)\.(include(\-(footer|body))?(-nb)?)\.[0-9]+\.(css|js)$ /wp-content/w3tc/min/index.php?tt=$1&gg=$2&g=$3&t=$7 last;
}
}
# Set a variable to work around the lack of nested conditionals
set $cache_uri $request_uri;
# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
set $cache_uri 'no cache';
}
if ($query_string != "") {
set $cache_uri 'no cache';
}
# Don't cache uris containing the following segments
if ($request_uri ~* "(\/wp-admin\/|\/xmlrpc.php|\/wp-(app|cron|login|register|mail)\.php|wp-.*\.php|index\.php|wp\-comments\-popup\.php|wp\-links\-opml\.php|wp\-locations\.php)") {
set $cache_uri "no cache";
}
# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp\-postpass|wordpress_logged_in") {
set $cache_uri 'no cache';
}
# Use cached or actual file if they exists, otherwise pass request to WordPress
location / {
try_files /wp-content/w3tc/pgcache/$cache_uri/_index.html $uri $uri/ /index.php?q=$uri&$args;
}
# Cache static files for as long as possible
location ~* \.(xml|ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
try_files $uri =404;
expires max;
access_log off;
}
# Deny access to hidden files
location ~* /\.ht {
deny all;
access_log off;
log_not_found off;
}
# Pass PHP scripts on to PHP-FPM
location ~* \.php$ {
try_files $uri /index.php;
fastcgi_index index.php;
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
}
}As you can see in the latest location configuration, all requests to a PHP file will be redirected to a fastcgi script (the php-fpm).
The important things to configure are:
- listen: address:port where your nginx webserver will listen for requests. Normally the base configuration
listen 80should work. On the OVH VPS you must specify the public IP address of your server to prevent errors on Nginx startup. - server_name: the variable to configure the virtual host of your webserver. That means all requests coming to your public IP address using the domain name specified in the server_name variable will be handled by the current configuration. That also means you could have different websites on the same Nginx server; you just need to assign a different server name.
- root: the folder on your server containing files you want to serve through the webserver. For example all the WordPress files should be copied inside the folder configured here.
You’re ready to extract WordPress (or any other PHP application) to the configured folder.
Start All Services#
Now you can test your configuration by starting up all the services:
service nginx start
service php-fpm start
service mysqld start
chkconfig nginx on
chkconfig php-fpm on
chkconfig mysqld onGoing to the configured domain name should give you access to your WordPress blog (or to the WordPress setup page if you need to configure a new blog).
Tuning#
Depending on the traffic of your blog, you can try tuning it to reduce resource usage.
For example, my blog has normally 200/300 visits per day from Europe and the USA, which makes it important to know the access time and calculate simultaneous connections.
An important thing to understand is the meaning of “simultaneous”: exactly in the same moment two (or more) users access a website page (producing a request to the webserver, a PHP page compilation, …). If a user requests a blog post and spends 5 minutes reading it, in those 5 minutes you could have 2 or 3 other users accessing your blog. These users are accessing simultaneously in real life, but not for your application server: a reading user is not using resources from your server!
So with 200 users a day you don’t have many simultaneous connections (in theory, but this is something you have to check), so you can configure the webserver (nginx and php-fpm) accordingly.
Nginx Tuning#
On the Nginx side, the important variables to start your tuning are worker_processes and worker_connections: the first one configures how many nginx processes are created on your server (1 by default) and the second one indicates how many connections (clients) can be handled by any process.
So you can calculate the maximum clients for Nginx with:
mx clients = worker_processes * worker_connections
On my server I left the default parameters for the nginx server, meaning 1 process with 1024 connections (file /etc/nginx/nginx.conf).
Why, when I just said there are few concurrent users?
Another important thing to know is how a browser works with a web page.
When we ask a page to a webserver (for example a PHP page), it compiles the PHP file (if needed) and sends back the HTML version to our browser (1 request to the webserver). Then, in the page, we normally have references to JavaScript files, stylesheets, images, fonts, … So the browser, to complete the page we requested, executes other requests to the webserver: one for each static file. So a single user accessing a single page on a webserver produces 10-20 requests (!!), or more, depending on your page. That means with 2 or 3 users we can easily reach hundreds of connections to the server.
PHP-FPM Tuning#
If you don’t tune the default configuration, the PHP “web server” can handle hundreds of connections without problems, BUT, it will use “a lot” of RAM: more than 200 MB.
It’s not really a high value, I know, but when you have a virtual machine with 512 MB of RAM, it means PHP-FPM uses half of your RAM.
The important configuration part is the one concerning the child processes.
; Choose how the process manager will control the number of child processes.
; Possible Values:
; static - a fixed number (pm.max_children) of child processes;
; dynamic - the number of child processes are set dynamically based on the
; following directives:
; pm.max_children - the maximum number of children that can
; be alive at the same time.
; pm.start_servers - the number of children created on startup.
; pm.min_spare_servers - the minimum number of children in 'idle'
; state (waiting to process). If the number
; of 'idle' processes is less than this
; number then some children will be created.
; pm.max_spare_servers - the maximum number of children in 'idle'
; state (waiting to process). If the number
; of 'idle' processes is greater than this
; number then some children will be killed.
; Note: This value is mandatory.A child process can handle a single user’s request. So, for example, 200 processes means 200 simultaneous users.
On my server, after some tests, I’m using this configuration:
pm = dynamic
pm.max_children = 4
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 2
pm.max_requests = 200That means I can have a maximum of 4 concurrent connections to the PHP part (for safety you can increase the max to a higher value): a PHP thread normally answers a user in less than a second (!).
When the PHP-FPM service starts up, it creates 2 child processes; the others will be created only if required. You should find the right number of child processes for your setup and put this number into start_servers variable: having the required processes ready allows for a faster response to the users (no time needed to create a new process).
With this configuration I can assure that php-fpm processes take only 70 MB of RAM on my server (any new php-fpm process takes about 24 MB of RAM more), and when I have moments with “many” concurrent users it could go up to about 120 MB.
[mmornati@vps38203 ~]$ sudo python ps_mem.py
Private + Shared = RAM used Program
4.0 KiB + 27.5 KiB = 31.5 KiB dbus-daemon
36.0 KiB + 31.5 KiB = 67.5 KiB atd
24.0 KiB + 51.0 KiB = 75.0 KiB mingetty (6)
60.0 KiB + 24.0 KiB = 84.0 KiB mdadm
224.0 KiB + 62.5 KiB = 286.5 KiB crond
156.0 KiB + 147.0 KiB = 303.0 KiB master
4.0 KiB + 337.5 KiB = 341.5 KiB mysqld_safe
220.0 KiB + 137.0 KiB = 357.0 KiB qmgr
332.0 KiB + 35.0 KiB = 367.0 KiB init
484.0 KiB + 81.5 KiB = 565.5 KiB rsyslogd
728.0 KiB + 173.0 KiB = 901.0 KiB nrsysmond (2)
660.0 KiB + 368.5 KiB = 1.0 MiB bash
928.0 KiB + 431.5 KiB = 1.3 MiB sudo
1.4 MiB + 230.0 KiB = 1.7 MiB nginx (2)
1.2 MiB + 481.0 KiB = 1.7 MiB pickup
940.0 KiB + 1.8 MiB = 2.7 MiB sshd (3)
3.3 MiB + 483.5 KiB = 3.7 MiB fail2ban-server
6.4 MiB + 236.5 KiB = 6.6 MiB mysqld
7.7 MiB + 175.0 KiB = 7.9 MiB named
74.4 MiB + 4.7 MiB = 79.1 MiB php-fpm (3)
---------------------------------
109.1 MiB
=================================To create this useful configuration I followed this guide. if !1 then 0 is an incredible technical blog :)