It is not a secret that I am using Gandi Simple Hosting to host sleeplessbeastie’s notes website. This solution is almost maintenance-free and does not require any additional work to use Jekyll, as hosting static content is dead simple (see shell script to automate build and upload process).

However, recently I started to feel limited by the inability to automatically build and publish scheduled posts. Now it is time to solve this issue by switching to VPS cloud hosting.

I will briefly describe the whole process of preparing a Debian-based VPS server to host a blog built using Jekyll static site generator.

Table of contents

Prerequisites

  • Debian-based server
  • Jekyll static web-site
  • SSL certificate

Create a regular user.

At first, please verify that sudo utility is already installed.

# which sudo

If the above command does not return path to the sudo utility, then install it using the following command.

# apt-get install sudo

Create blogger user and set the desired password.

# useradd -m -G sudo -s /bin/bash blogger
# passwd blogger

From now on, you can use sudo to acquire root privileges.

Disable root login over SSH

I assume that the OpenSSH service is already installed and running, but the default Debian configuration will permit root login over SSH, so you need to change this setting to the reasonable one.

$ sudo sed -i '/PermitRootLogin/ s/yes/no/' /etc/ssh/sshd_config

Reload SSH configuration to apply a change.

$ sudo service ssh reload

You have disabled root login over SSH, so use recently created blogger user.

Install HTTP server

I want to use Nginx with SPDY protocol support, which is not available directly in the Debian Wheezy repository. . Fortunately, we do not need to build it from source, but just use the wheezy-backports repository to get the latest stable Nginx version.

$ echo -e "deb http://cdn.debian.net/debian wheezy-backports main\ndeb-src http://cdn.debian.net/debian wheezy-backports main" | sudo tee /etc/apt/sources.list.d/wheezy_backports.list

Update the package list.

$ sudo apt-get update

Every package from the above repository is marked as not automatic but with automatic upgrades (see man apt-preferences).

$ apt-cache policy nginx
nginx:
  Installed: (none)
  Candidate: 1.2.1-2.2+wheezy2
  Version table:
     1.4.6-1~bpo70+1 0
        100 http://cdn.debian.net/debian/ wheezy-backports/main i386 Packages
     1.2.1-2.2+wheezy2 0
        500 http://ftp.task.gda.pl/debian/ wheezy/main i386 Packages
        500 http://security.debian.org/ wheezy/updates/main i386 Packages

Install the HTTP server using the recently added repository.

$ sudo apt-get -y -t wheezy-backports install nginx

Verify that server is up and running.

Configure HTTP server

Create a directory for static files.

$ sudo mkdir -p /var/www/blog.sleeplessbeastie.eu
$ sudo chown www-data:www-data /var/www/blog.sleeplessbeastie.eu

Create <a href="http://www.sleeplessbeastie.eu" rel="nofollow">http://www.sleeplessbeastie.eu</a> virtual host.

$ sudo vi /etc/nginx/sites-available/www
server {
	listen 80 default_server;
	server_name www.sleeplessbeastie.eu;
	return 301 $scheme://sleeplessbeastie.eu;
}

Create blog.sleeplessbeastie.eu virtual host.

$ sudo vi /etc/nginx/sites-available/blog
server {
	listen 80;
	listen 443 ssl spdy;
	server_name blog.sleeplessbeastie.eu;
	root /var/www/blog.sleeplessbeastie.eu;
	index index.html;
	location / {
		try_files $uri $uri/ =404;
	}
	ssl on;
	ssl_certificate     blog.sleeplessbeastie.eu.pem;
	ssl_certificate_key blog.sleeplessbeastie.eu.key;
	ssl_session_timeout 5m;
	ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
	ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
	ssl_prefer_server_ciphers on;
}

Please remember to create private key and certificate files.

Enable recently created virtual hosts and disable the default one.

$ sudo unlink /etc/nginx/sites-enabled/default
$ sudo ln -s /etc/nginx/sites-available/www /etc/nginx/sites-enabled/www
$ sudo ln -s /etc/nginx/sites-available/blog /etc/nginx/sites-enabled/blog

Reload nginx configuration.

$ sudo service nginx reload

Prepare git repository

Install git revision control system.

$ sudo apt-get -y install git

Create a bare repository that will be used to store source code.

$ git init --bare git/blog.sleeplessbeastie.eu.git

Track blog content

Enter the current Jekyll blog directory on the local machine.

$ cd /path/to/jekkyll_blog_source_code

Create .gitignore file, as we do not want to track _site directory (generated website).

$ echo "_site/"	| tee .gitignore

Init git repository.

$ git init

Add remote location (using just ssh protocol) to the repository.

$ git remote add origin blogger@blog.sleeplessbeastie.eu:~/git/blog.sleeplessbeastie.eu.git

Add all files to the repository and commit changes.

$ git add --all
$ git commit -m "Initial import"

Push all changes to the remote.

$ git push --all

Additional notes – How to clone git repository?

You can clone the repository from everywhere using the following command.

$ git clone blogger@blog.sleeplessbeastie.eu:/home/blogger/git/blog.sleeplessbeastie.eu.git

Install Jekyll static website generator

Install required dependencies.

$ sudo apt-get install ruby ruby-dev libgsl-ruby rubygems

Install jekyll.

$ sudo gem install jekyll

Create a simple automated build system

Create a simple shell script to automate the build process.

$ sudo vi /usr/bin/build_website.sh
#!/bin/sh
# Clone git repository, build and publish Jekyll blog
# git repository
repo="/home/blogger/git/blog.sleeplessbeastie.eu.git"
# temporary directory
temp_dir="/tmp/$(date +"%d%m%Y_%H%M")"
# destination directory
dest_dir="/var/www/blog.sleeplessbeastie.eu"
# path to jekyll executable
jekyll="/usr/local/bin/jekyll"
# clone repository
git clone $repo $temp_dir
# build jekyll web-site
LC_ALL=C.UTF-8 $jekyll build --lsi -s $temp_dir -d ${temp_dir}/_site
# fix permissions
chown -R www-data:www-data ${temp_dir}/_site
# publish web-site
rsync -r --delete ${temp_dir}/_site/ $dest_dir
# remove temporary directory
rm -r $temp_dir

Set the execute permission for the above shell script.

$ sudo chmod +x /usr/bin/build_website.sh

Create cron entry to build and publish this website every day at 00:30.

echo "30 0    * * * www-data /usr/bin/build_website.sh > /dev/null" | sudo tee /etc/cron.d/website

Additional notes

Remember to set future option to false inside your _config.yml blog configuration file to store future posts inside _posts directory.

$ cat _config.yml | grep future
future: false

Monitor server statistics

This step is purely optional but highly recommended as it will allow to monitor resource usage and avoid potential problems in the future.

Install collectd system statistics collection daemon.

$ sudo apt-get install -y --no-install-recommends collectd rrdtool

Edit configuration file and adjust it to your needs.

$ sudo vi /etc/collectd/collectd.conf

Use the following Perl script to quickly generate HTML page with all rrd data gathered by collectd.

/usr/share/doc/collectd-core/examples/collectd2html.pl

Additional notes – the resolution of the collected data

By default collectd will use an interval of 10 seconds, but such granularity is not needed.
You can safely increase the interval to 30 or even 60 seconds.

Please remember to delete rrd files after altering the interval option.

$ sudo rm -rf /var/lib/collectd/rrd/*

Additional notes – collect Nginx performance data

Create a virtual host with nginx status page.

$ sudo vi /etc/nginx/sites-available/stats
server {
	listen 80;
	server_name localhost;
	location /status {
		stub_status on;
		access_log  off;
		allow 127.0.0.1;
		deny all;
	}
}

Enable virtual host and reload nginx configuration.

$ sudo ln -s /etc/nginx/sites-available/stats /etc/nginx/sites-enabled/
$ sudo service nginx reload

Enable nginx plugin in collectd configuration file.

LoadPlugin nginx
<Plugin nginx>
        URL "http://localhost/status"
</Plugin>

Additional notes – … illegal attempt to update using time … syslog messages

This issue can be solved very quickly – just read Debian Bug report #710658.

Monitor web statistics

This step is not essential; however, web statistics can provide a lot of interesting insight.

Install awstats package.

$ sudo apt-get install -y awstats

Prepare configuration file – set SiteDomain, LogFormat and LogFile settings.

$ sudo cp /etc/awstats/awstats.conf /etc/awstats/awstats.blog.sleeplessbeastie.eu.conf
$ sudo sed -i '/SiteDomain/ s/""/"blog.sleeplessbeastie.eu"/' /etc/awstats/awstats.blog.sleeplessbeastie.eu.conf
$ sudo sed -i '/LogFile/ s/LogFile=".*"/LogFile="\/var\/log\/nginx\/access.log"/' awstats.blog.sleeplessbeastie.eu.conf
$ sudo sed -i '/LogFormat/ s/4/1/' awstats.blog.sleeplessbeastie.eu.conf

Disable automatic generation of static pages every night.

$ sudo sed -i "/AWSTATS_ENABLE_BUILDSTATICPAGES/ s/yes/no/" /etc/default/awstats

Rename default configuration file, so used shell scripts will not read it during execution time.

$ sudo mv /etc/awstats/awstats.conf /etc/awstats/awstats.default

Cron entry found in /etc/cron.d/awstats file will update statistics every ten minutes.

Now you can use the following command to generate HTML pages.

/usr/share/awstats/tools/awstats_buildstaticpages.pl -config=blog.sleeplessbeastie.eu -dir=/var/www/statistics/ -diricons=/statistics/icons/

Additional notes – Where to find icons?

You use icons found in /usr/share/awstats/icon/ directory.

Install and configure the firewall

Configure the firewall to control traffic flow.

Install shorewall firewall.

$ sudo apt-get install shorewall

Configure shorewall firewall.

$ sudo vi /etc/shorewall/interfaces
#
# Shorewall version 4 - Interfaces File
#
# For information about entries in this file, type "man shorewall-interfaces"
#
# The manpage is also online at
# http://www.shorewall.net/manpages/shorewall-interfaces.html
#
###############################################################################
FORMAT 2
###############################################################################
#ZONE	INTERFACE	OPTIONS
-	lo		ignore
net	all		physical=+,optional
$ sudo vi /etc/shorewall/policy
#
# Shorewall version 4 - Policy File
#
# For information about entries in this file, type "man shorewall-policy"
#
# The manpage is also online at
# http://www.shorewall.net/manpages/shorewall-policy.html
#
###############################################################################
#SOURCE	DEST	POLICY		LOG	LIMIT:		CONNLIMIT:
#				LEVEL	BURST		MASK
$FW	net	DROP
net	all	DROP
$ sudo vi /etc/shorewall/rules
#
# Shorewall version 4 - Rules File
#
# For information on the settings in this file, type "man shorewall-rules"
#
# The manpage is also online at
# http://www.shorewall.net/manpages/shorewall-rules.html
#
###################################################################################################################################################################################
#ACTION		SOURCE		DEST		PROTO	DEST	SOURCE		ORIGINAL	RATE		USER/	MARK	CONNLIMIT	TIME         HEADERS         SWITCH
#							PORT	PORT(S)		DEST		LIMIT		GROUP
#SECTION ALL
#SECTION ESTABLISHED
#SECTION RELATED
SECTION NEW
SSH(ACCEPT)	    net		$FW              -      -       -               -               s:ssh:3/min:5
Ping(ACCEPT)	net		$FW
HTTP(ACCEPT)    net     $FW
HTTPS(ACCEPT)   net     $FW
$ sudo vi /etc/shorewall/zones
#
# Shorewall version 4 - Zones File
#
# For information about this file, type "man shorewall-zones"
#
# The manpage is also online at
# http://www.shorewall.net/manpages/shorewall-zones.html
#
###############################################################################
#ZONE	TYPE		OPTIONS		IN			OUT
#					OPTIONS			OPTIONS
fw	firewall
net	ip

Start shorewall firewall at the system boot.

$ sudo sed -i "/startup=0/ s/0/1/" /etc/default/shorewall

Issue stop command, instead of clear when executing /etc/init.d/shorewall stop.

$ sudo sed -i "/SAFESTOP=0/ s/0/1/" /etc/default/shorewall

Additional notes – How to enable outbound traffic?

To enable outbound traffic execute the following commands.

$ sudo sed -i "/^\$FW/ s/DROP/ACCEPT/" /etc/shorewall/policy
$ sudo shorewall restart

Additional notes – What about IPv6 traffic?

To filter IPv6 traffic install shorewall6 package.

Future improvements

  • Use key-based authentication to protect against brute force attacks.
  • Investigate nginx caching mechanism.
  • Create an additional virtual host for statistics.
  • Use a log analyzer to block offending IP addresses.
  • Hide Nginx version.

A few words at the end

Currently nginx package in the repository is too old to safely enable SPDY protocol.

I ended up compiling recent nginx version from the source code.

I have used the following configuration arguments and just replaced nginx binary.

-with-cc-opt='-g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2' --with-ld-opt=-Wl,-z,relro --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi  --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_spdy_module --prefix=/opt
This blog post was mentioned on
VPS Chef
Cooking up the best in Virtual Private Servers.