Setting up a Linux server for WordPress/Drupal/Joomla
This is a practical guide to setting up a server for maximum performance running one of the blog-oriented CMS platforms: WordPress, Drupal, or Joomla.
I’ll cover the setup on a Linux Ubuntu server. You’ll need Nginx, PHP, MySQL, and Varnish.
1. MySQL
Starting from the end. The default MySQL settings are fine with me, so just install it:
apt-get install mysql-server-5.5 mysql-client-5.5
2. PHP
PHP needs to run in fast-cgi mode. NginX doesn’t have a module like libapache-modphp.
So you’ll need php-cli, php-common, and the rest. If you’re paranoid and want the latest php version, use this article and build it from source.
The version available in the repos works fine:
apt-get install php5-cgi php5-cli php5-common php5-curl php5-gd php5-json php5-mysql php5-readline spawn-fcgi
Download the init.d script. For Ubuntu it’s different from the one shown in this article
wget -O /etc/init.d/php-fastcgi /wp-content/uploads/2014/11/php-fastcgi-init
Also download the fastcgi handler itself:
wget -O /usr/bin/php-fastcgi /wp-content/uploads/2014/11/php-fastcgi-bin
Make them executable:
chmod +x /etc/init.d/php-fastcgi
chmod +x /usr/bin/php-fastcgi
Add php to startup:
sudo update-rc.d php-fastcgi defaults 80
Start it:
/etc/init.d/php-fastcgi start
Check:
netstat -tunlp |grep 9000
If php-fcgi started, you’ll get this output:
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN 6806/php5-cgi
3. Nginx
I’d use the version available in the repos. If you’re paranoid and want the latest version, build it from source using this article.
Install:
apt-get install nginx
Create a config file for the site:
nano /etc/nginx/sites-available/**mywebsite.com**
Put this in it:
server {
listen 8080;
server_name mywebsite.com www.mywebsite.com;
root /var/www/html;
index index.php;
if ($host ~ !^(mywebsite.com|www.mywebsite.com)$) {
rewrite ^ http://www.mywebsite.com$request_uri? permanent;
}
# Limit methods, allowed on server to GET, HEAD and POST
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 444;
}
location ~* \.(ico|gif|jpeg|jpg|png|eot|ttf|swf|woff)$ {
root /var/www/html;
expires 30d;
access_log off;
}
location ~* \.(css|js)$ {
root /var/www/html;
expires 7h;
access_log off;
}
location / {
try_files $uri $uri/ /index.php;
}
location ~ \.(php|html)$ {
root /var/www/html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
The sensitive spots of the blog are better locked down with basic auth.
WordPress:
location /wp-admin {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/auth/.ht.passwd_admin;
}
location ~* /wp-login.php {
try_files $uri $uri/ $uri/wp-login.php /wp-login.php;
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/auth/.ht.passwd_admin;
root /var/www/html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~* /xmlrpc.php {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/auth/.ht.passwd_admin;
try_files $uri $uri/ $uri/xmlrpc.php /xmlrpc.php;
root /var/www/html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Joomla:
location /administrator {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/auth/.ht.passwd_admin;
}
Drupal:
location /admin {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/auth/.ht.passwd_admin;
}
To create the passwords file we need apache2-utils. Install it:
apt-get install apache2-utils
Create the /etc/nginx/auth/.ht.passwd_admin file (the /etc/nginx/auth folder doesn’t exist yet):
mkdir /etc/nginx/auth
htpasswd -cmb /etc/nginx/auth/.ht.passwd_admin **user password**
Edit the main config file:
nano /etc/nginx/nginx.conf
Make the following changes to it:
Change the user to www-data:
user www-data;
Improve security:
client_body_buffer_size 4K;
client_header_buffer_size 4k;
large_client_header_buffers 4 4k;
limit_conn_zone $binary_remote_addr zone=slimits:5m;
This next block lets you see visitors’ real ip addresses in the log files:
##
# Logging Settings
##
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
log_format main '$remote_addr - $remote_user [$time_local] $status '
'"$request" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format forwarded '"$http_x_forwarded_for" - $remote_user [$time_local] $status '
'"$request" $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log forwarded;
error_log /var/log/nginx/error.log;
The final step is setting up file compression:
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
gzip_vary on;
# gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 64 8k;
gzip_http_version 1.1;
gzip_types text/plain text/html text/css application/json application/x-javascript text/xml application/xml alication/xml+rss text/javascript;
Make sure the /var/www/html folder exists:
mkdir -p /var/www/html
Last thing is to enable the site:
ln -s /etc/nginx/sites-available/mywebsite.com /etc/nginx/sites-enabled/mywebsite.com
and restart nginx for the changes to take effect:
service nginx restart
Nginx setup is done. Once started it’ll be available on port 8080. Left to set up Varnish so it accepts incoming connections on port 80.
4.Varnish
Install Varnish
apt-get install varnish
I covered Varnish configuration in detail in [A look at Varnish configuration](http://www.tech-notes.net/varnish-configuration-rewiev/), so I won’t chew through it again here.
First, edit /etc/default/varnish and bring the DAEMON_OPTS section to this shape:
DAEMON_OPTS="-a :80 \
-T localhost:6082 \
-t 120\
-f /etc/varnish/website.vcl \
-S /etc/varnish/secret \
-s malloc,256m"
Next, create the /etc/varnish/website.vcl file and fill it in:
backend default {
.host = "127.0.0.1";
.port = "8080";
}
acl purge { "localhost"; }
sub vcl_recv {
set req.http._sess = regsub( regsub( req.http.Cookie, ".*PHPSESSID=", "" ), ";.*", "" );
if (req.request == "PURGE") {
if (!client.ip ~ purge) {
error 405 "Not allowed.";
}
}
if (req.restarts == 0) {
if (req.http.x-forwarded-for) {
set req.http.X-Forwarded-For = req.http.X-Forwarded-For;
} else {
set req.http.X-Forwarded-For = client.ip;
}
}
if (req.request != "GET") { return (pipe); }
if (req.url == "^/(wp-admin|wp-login|admin|administrator)") {
return (pipe);
}
if (req.url ~ "^/(/wp-content|media|images|graphics)/.*\.(ico|gif|jpeg|jpg|png|eot|ttf|swf|woff)$") {
unset req.http.Cookie;
return (pipe);
}
if (req.http.Authorization || req.request == "POST" || req.http.Authenticate)
{
return (pass);
}
set req.backend = default;
return (lookup);
}
sub vcl_fetch {
# Remove Expires from backend, it's not long enough
unset beresp.http.expires;
# Set the clients TTL on this object
set beresp.http.cache-control = "max-age=900";
# Set how long Varnish will keep it (1 minute)
set beresp.ttl = 3600 s;
return (deliver);
}
sub vcl_deliver {
remove resp.http.Via;
remove resp.http.Age;
remove resp.http.X-Purge-URL;
remove resp.http.X-Purge-Host;
remove resp.http.X-CF-Powered-By;
return (deliver);
}
Pay attention to this construct in the vcl_recv section
if (req.url ~ "^/(/wp-content|media|images|graphics)/.*\.(ico|gif|jpeg|jpg|png|eot|ttf|swf|woff)$") {
unset req.http.Cookie;
return (pipe);
}
This is what routes static file requests straight to Nginx, bypassing varnish. If your site keeps images in a pictures folder, change this section to look like this:
if (req.url ~ "^/(/wp-content|media|images|graphics|pictures)/.*\.(ico|gif|jpeg|jpg|png|eot|ttf|swf|woff)$") {
unset req.http.Cookie;
return (pipe);
}
This way Varnish won’t cache static requests, non-GET requests, auth requests, or any admin panel access.
Restart Varnish for the changes to take effect:
service varnish restart
I’d recommend installing plugins that flush the Varnish cache when blog posts get updated. Such plugins exist for pretty much every CMS.
Wrapping up:
With this setup, WordPress on a server with 1GB RAM, 1CPU handles 500+ users per second. Couldn’t generate more within the free plan on blazemeter.com.
Now you can set up an ftp server and create virtual users, then upload content and install the CMS.
Also recommend setting up backups for your sites.
For extra peace of mind you can set up fail2ban
Once the content is uploaded, I’d recommend optimizing the images to speed up image delivery.