VPS / (Personnal) Virtual Server mini HowTo

Introduction

I recently moved from a hosting web service (Dreamhost) to a VPS (Ramnode). It was surprisingly easy and I want now to share this experience, particularly on informations that were uneasy to find.

Why Ramnode

For 8$/qrt I have 120Go ssd cached disk, 1000GB bandwidth and 256 MB memory. After installation of all services I still have 167MB memory free and 101G disk free. So this cheap hosting is enough.  My ramnode vps is faster than dreamhost ( for a wordpress page: 0.6 sec (max 0.9 sec) against 0.9 sec (max 2 sec)), cheaper and I have my own IP.

Installed services

I have multiple domains so I use virtual domains when appropriate.

Why choosing Debian

I use linux since a long time (before kernel 1.0), I switched from distributions using the shining distribution of the moment. Debian is not the brightest of the moment (it’s Ubuntu) and after several changes I know that stability is a great quality, and for this quality Debian is by far the brightest distribution. For the others qualities Debian is also a very good choice so …

FireWall

I use the package iptables-persistent to restore the firewall at start.

I started with this good base: https://gist.githubusercontent.com/jirutka/3742890/raw/c025b0b8c58af49aa9644982c459314c9adba157/rules-both.iptables

I put the configuration here : /etc/iptables/rules.v4

To update the firewall:

iptables-restore < /etc/iptables/rules.v4

Web server

SSL

I use https://letsencrypt.org/ to have automatic and free certificates. Now web servers and navigators can use SNI so all domains on a unique IP can use https.

What’s follow will change quickly because letsencrypt is new, this is valid in December 2015.

Installation on a stable debian needs some work :

I had to install these packages :

  • jessie : dialog python ca-certificates python-mock python-ndg-httpsclient python-openssl python-pkg-resources python-pyasn1 python-requests python-rfc3339 python-six python-tz python-werkzeug python-configargparse python-configobj python-dialog python-openssl python-parsedatetime python-psutil python-zope.component python-zope.interface python-enum34 python-augeas
  • jessie-backports : python-cryptography python-rfc3339
  • testing : python-dialog python-configargparse
  • Experimental : python-acme python-letsencrypt python-letsencrypt-apache letsencrypt

After that I needed to run one command :

letsencrypt --apache --server https://acme-v01.api.letsencrypt.org/directory --agree-dev-preview

but I had to replace some regexp that letsencrypt couldn’t parse in the apache web sites configurations. So I replaces « ^index\.php$ » by « ^index[.]php$ » which has the same meaning.

Now I must work on the automatic certificate update (every 3 months).

Check compromised web site which send spams

I red that 90% of spams come from compromised web sites. To check if a web site is compromised and used as relay allow logs of sent mail by php: in /etc/php5/apache2/php.ini uncomment « mail.log = syslog » (or write it). The log appear in messages, syslog and user.log .

Awstats

Statistics from the log files are easy: nothing to add in all web pages and then small footprint. This works almost out of the box.

You can use Include directive to split the configuration, this way it’s small and clean.

To look the statistics for a domain use for example:

https://global.bobu.eu/cgi-bin/awstats.pl?config=home.emmanuel.FieoF

for the corresponding configuration : awstats.home.emmanuel.FieoF.conf

I add a minimal password in the configuration’s file name, then I can give an url for somebody and he can’t find statistics for other domains.

Seedbox

RamNode allow a seedbox for their US servers, limited to 20Mb/sec (arround 2MB/sec). This is ok. I use transmission-remote-gtk at home and it’s almost as easy as local transmission. I limited the upload to 500Mo/sec and download to 1500Mo/sec.

To get the files I use sshfs with a special user on the server (p2p-read). The autofs configuration is:

/mpnts/bobup2p -fstype=fuse,rw,nodev,nonempty,noatime,allow_other,max_read=65536 :sshfs\#p2p-read@global.bobu.eu\:/var/lib/transmission-daemon/downloads

For autofs we need to install a passwordless ssh connection between root@local to p2p-read@server.

DNS

resolf.conf is modified by ramnode (at least at boot time) to select only google’s DNS, to use my own DNS as first choice I stick the file by:

chattr +i /etc/resolv.conf

I used bind9, but it crashed 2 times, and logs found attacks at the time of the crash. The security advisories of bind shows around 5 problems per year, but for powerdns it’s just 2 or less (click « Security » menu). So I switched to powerdns (packages pdns-server pdns-backend-mysql on debian) with the mysql backend. I use Poweradmin (manual and easy installation) for an easy web GUI configuration tool.

DDNS

I like to have a host name for my changing IP at home (when we want to connect to our own computer when we are far from home). I want also to remove our own home’s computers from the web statistics.

To do that I call this script from home (in cron.hourly), this way I get my home’s IP:

#!/bin/bash

IP_NOW=$(echo -n "$SSH_CLIENT" |cut -d' ' -f 1)
IP_OLD=$(cat /var/cache/IP-maison/maison.ip)

maj_ip_awstats () {
        echo "SkipHosts=\"$IP_NOW\"" >/etc/awstats/SkipHosts.inc
}

maj_ip_dns () {
    mysql -u root -e "UPDATE pdns.records SET content = '$IP_NOW' WHERE records.name = 'personalpc.dynamic.bobu.eu';" pdns
    pdns_control purge personalpc.dynamic.bobu.eu
}

if [ "$IP_NOW" != "$IP_OLD" ]; then
        echo -n "$IP_NOW" >/var/cache/IP-maison/maison.ip
        maj_ip_awstats
        maj_ip_dns
fi

I modifie the db of powerdns, the password is in the /root/.my.cnf

Dynamic dns need a special time to leave, so I use a TTL of 3600.

Secondary server

A domain must have two name servers using different IP. Some secondary servers are free, like www.buddyns.com. I use it with success.

 SMTP/Pop3

Why postfix

The real actual choice is between exim and postfix. I know exim from a long time. Exim offer a smart flexible configuration language. Debian add a smart splited configuration file full of macros and ifdef. Smartness is good some times and sometimes it’s just awfully a pain. You don’t want to know the physic properties of the water to drink a glass of water and you probably don’t want to use exim as well, except if you need some very specials things you don’t find in postfix or if you have simple needs and you are sure to follow the debian preconfigured cases. Also postfix had less security problems than exim last years.

postfix & dovecot together

I want to use a flat users file because I don’t have a lot of users, and I want to share this file between postfix and dovecot. I found a solution using pam. You must use « imap » for the pam service name, it’s fixed by saslauthd which is only configured by « /etc/default/saslauthd » (extract):

MECHANISMS="pam"
OPTIONS="-c -r -m /var/spool/postfix/var/run/saslauthd"

/var/spool/postfix/var/run/saslauthd is the socket communication path fixed by postfix.

Modify /etc/dovecot/conf.d/10-auth.conf to « include auth-system.conf.ext ».

The auth-system.conf.ext:

passdb {
 driver = pam
 # [session=yes] [setcred=yes] [failure_show_msg=yes] [max_requests=<n>]
 # [cache_key=<key>] [<service name>]
 args = max_requests=10 imap
}
userdb {
 driver = static
 args = uid=vmail gid=vmail home=/var/spool/vmail/%d/%n
}

The imap pam’s configuration file:

#%PAM-1.0
auth required pam_userdb.so crypt=none db=/etc/postfix/users 
account required pam_userdb.so crypt=none db=/etc/postfix/users

The corresponding /etc/postfix/users.txt file

toto@emmanuel.bobu.eu
longPassword
titi@emmanuel.bobu.eu
longPassword2
...

Convert it to the users.db (the db extension is hidden in the pam’s configuration file):

db_load -T -f /etc/postfix/users.txt -t hash /etc/postfix/users.db

We put the mails in  maildirs (postfix/main.cf extract):

# Virtual domain
virtual_mailbox_domains = /etc/postfix/vhosts
virtual_mailbox_base = /var/spool/vmail
virtual_mailbox_maps = hash:/etc/postfix/vmaps
virtual_minimum_uid = 1000
virtual_uid_maps = static:1002
virtual_gid_maps = static:1002

where 1002 is the uid and gid of vmail user and group.

Donc forget to use postmap for /etc/postfix/vmaps,  /etc/postfix/virtual and  /etc/aliases.

For exemple to add a user from my home I use :

USER=toto; DOMAIN=emmanuel.bobu.eu; echo "$USER@$DOMAIN $DOMAIN/$USER/" | ssh root@global.bobu.eu "cat >>/etc/postfix/vmaps; postmap /etc/postfix/vmaps"

PS: I know that root connection is « baaaad » but my firewall limit the brute force ssh attack and my passwords are very long. Thereby if a hacker can login as root it’s because ssh is compromised.

Infinites email in one email

Add the line « recipient_delimiter = + » in postfix/main.cf and all email addresses « toto@example.com », « toto+cat@example.com » or « toto+dog@example.com » etc arrive to the email address « toto@example.com ». In these times where the most virtuous companies don’t see any problem to spam their customers, this is a safe practice.

Backup storage service

I like to backup my computers in a server far away, then my house can burn I keep my precious alive. I use duplicity and an sftp account on the server.

To activate the sftp server of ssh, put this line in sshd_config:

Subsystem sftp /usr/lib/openssh/sftp-server

Backup of the server

I use duplicity, it’s simple, efficient, secured and seems reliable.

The script is called from my home (to be sure to have my home’s computer running), it calls /root/local/bin/backup which contains:

#!/bin/bash

source /etc/backup-duplicity/main-env

# Count incremental backups to make a full backup after $MAX_INCREMENTAL incremental backups.
if [ ! -f $MARK_FILE ]; then
 N=1
else
 N=$(cat $MARK_FILE)
 N=$((N+1))
fi

if [ $N -ge $MAX_INCREMENTAL ]; then
 CMD=full
 N=0
else
 CMD="" # Sauvegarde en incremental si possible sinon en full
fi

echo $N >$MARK_FILE

mysqldump -u root --all-databases |bzip2 > /tmp/all-database.sql.bz2

nice duplicity $CMD --no-encryption --archive-dir=$ARCHIVE_DIR --name=$ARCHIVE_NAME --exclude-globbing-filelist /etc/backup-duplicity/main-exclude.list --include-globbing-filelist /etc/backup-duplicity/main-include.list --exclude "**" / sftp://${REMOTE_LOGIN}@${REMOTE_HOST}/${REMOTE_DIR}
rm /tmp/all-database.sql.bz2
if [ $? -ne 0 ]; then 
 echo "erreur Duplicity $?" | mailx -s "Erreur Duplicity" emmanuel@fanti.localnet; 
fi

if [ "$CMD" = "full" ]; then
 duplicity remove-all-but-n-full 2 --no-encryption --archive-dir=$ARCHIVE_DIR --name=$ARCHIVE_NAME --force sftp://${REMOTE_LOGIN}@${REMOTE_HOST}/${REMOTE_DIR}
 duplicity cleanup --no-encryption --archive-dir=$ARCHIVE_DIR --name=$ARCHIVE_NAME --force sftp://${REMOTE_LOGIN}@${REMOTE_HOST}/${REMOTE_DIR}
fi

The password for mysqldump is strored in the /root/.my.cnf file.

I want to protect my home computer if the server is compromised, so I use a « chrooted » sftp connection to my home (sshd_config extract):

Subsystem sftp internal-sftp
 Match user toto
 ChrootDirectory /var/backup/bobu/
 AllowTCPForwarding yes
 X11Forwarding no
 ForceCommand internal-sftp

I needed to change the umask for toto in the pam configuration /etc/pam.d/sshd

session optional pam_umask.so umask=0027

WordPress

The multi-site wordpress choice

As a good guy I’m lazy, so I hate to repeat update / installation. How to share wordpress between hosts ? There is two solutions, the wordpress one and the debian one.

The wordpress solution (called « network wordpress ») is multi-sub-domains but not multi-domains and share one database for all sites. You can use a hack to have multi-domain but it’s a hack and it seems weird (seems not open source).

The debian solution is light: a special configuration wp-config.php which redirect to /etc/wordpress/config-example.com.php where example.com is the accessed domain, and some separate directories to share the wordpress program, the plugins, languages and themes.

Debian advantages: databases are independents, multi-domains, wordpress sites are almost completly indenpendents.

WordPress advantages: graphic integration.

Debian disadvantages: plugins, themes and languages must be installed manually, which is very simple: download, unzip, copy, activate (in the wordpress interface). wordpress sites must be at the root of the url (http://www.example.com/ but not http://www.example.com/home/). This is mandatory because the wp-config.php use just the domain name.

WordPress disadvantages: no real multi-domains support, less isolation, more complex solution.

The multi-domain requirement made me choose the Debian solution.

Debian wordpress multi-domains architecture

You have some documentation here /usr/share/doc/wordpress/README.Debian and here http://www.byteme.org.uk/2013/12/02/wordpress-debian-multisite/

What I add here is what I needed to understand by myself which is not already explained before:

Directories:

  • /usr/share/wordpress : the wordpress program and the special config file
  • /var/lib/wordpress/wp-content/ : the plugins, themes and languages
  • /srv/www/wp-content/$HOSTNAME/ : the specific par of wordpress (wp-content) for an host name.

Apache configuration:

The debian file for wordpress assume that all web sites not configured before (in the sites-enabled directory) is a wordpress web site. I don’t like this kind of unclean configuration (it’s not symmetric: two packages can’t use this hack together) and I prefer to have a configuration file by domain, even if it’s more verbose (but you can use Include directive).