How to setup a web server on ElasticHosts

24-07-2014

By Rob Stevenson

This post details steps to create a set of LAMP servers that will serve a php website with MySQL sitting behind CloudFlare's CDN, Cache and optimizer.

Skip to the start

server topology

Over the last few years I've been maintaining a virtual private server at a cloud hosting company. I've built it up from a minimal stock CentOS 5 image and it's mostly been doing well. Recently it's got a bit cluttered as I've added more and more to it and I've hit a few memory issues and less than brilliant performance. Nothing against the current host, but it was time to upgrade and I happened to stumble upon ElasticHosts at the exact point I was ready to go.

What makes this host appealing to me over my previous host is their new Elastic Containers. Rather than fixing down a system to a set CPU speed, fixed RAM and disk space, this technology allows seamless burts of any of these over your set package. No reboot necessary.

Basically a container isn't a full virtual machine. It's a sand-boxed environment which sees all the available resource and is allocated it dynamclally by the underlying system on demand, up to a pre-determined (by me) maximum. So rather than paying for the 3GB of RAM I need to cope with a weekly peak-usage, I pay for 768MB and for the two hours per week when I need more, it uses it at a cost of a few pence.

[root@cache ~]# free -m
            total   used    free  shared  buffers  cached
Mem:       128878  12789  116089       0       10    3853
-/+ buffers/cache:  8925  119952
Swap:       49151      0   49151

[root@cache ~]# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       477G  110G  366G  24% /

[root@cache ~]# du -sh / 2>/dev/null
610M    /

Total use: 12MHz, 768MB RAM, 4GB SSD. 1 IP. 1 VLAN. Subscriptions: 500MHz, 768MB, 5GB SSD. 1 IP. 1 VLAN.

Base image

I've based this on a pre-build minimal CentOS 6 image. There are a few common features so I created a base container which I dupliacted to create the other systems.

From the control panel hit 'Add', 'Server (Container)...' and give it a name. Select max sizes (default 2000MHz, 1024MB is probably fine), pre-installed system and pick centos-6 from the dropdown.

Setup a Virtual LAN

Add: Private VLAN
Connect

By default, you can login using ssh with the username toor. The IP address & password you'll need is shown on your control panel. Mac and Linux users can fire up a terminal and type:

ssh 203.0.113.123 -l toor

Windows users will need some extra software: download PuTTY plus PuTTYgen and Pageant for later, too.

Next we need to do some standard maintenence setup; update the system, name it and set the time zone. This example uses Europe/London but follow this link for others.

yum update -y
ln -sf /usr/share/zoneinfo/Europe/London /etc/localtime
cat >/etc/sysconfig/clock <<EOF
ZONE="Europe/London"
UTC=true
ARC=false
EOF

cat >>/etc/sysconfig/network <<EOF
HOSTNAME=base.example.com
EOF

Setup the VLAN. Create a new interface config file - Also pick an ip address.

NEWMAC=`ifconfig eth1 | grep HWaddr | awk '{print $5}'`
cat >/etc/sysconfig/network-scripts/ifcfg-eth1 <<EOF
DEVICE=eth1
BOOTPROTO="static"
HWADDR="$NEWMAC"
NM_CONTROLLED="yes"
ONBOOT="yes"
TYPE="Ethernet"
NETMASK=255.255.0.0
IPADDR=172.16.x.d
EOF
Network security

Prevent access to unused ports with iptables. At this stage we just need to block all access except for ssh.

cat >/etc/sysconfig/iptables <<EOF
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
EOF

And start service and make sure it starts on boot:

service iptables start
chkconfig iptables on

Configure the ssh daemon to prevent password logins and setup a private key. This is optional but makes connecting easier and more secure. From your computer, generate an ssh key if you haven't already. If you don't know, ~/.ssh/id_rsa will probably exist if you already have one. If you're using Windows, PuTTYgen will create a key for you and Pageant will allow you to manage it without having to type in your key's password every time you connect. Once you've got your key, grab the public key (id_rsa.pub), and paste it into the SSH Keys section at the bottom of the profile/authentication page.

This will create two files ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub. id_rsa should be kept very private while id_rsa.pub should be added to ~/.ssh/authorized_keys of any system you'd like to access with this key.

ssh-keygen -t rsa
cat .ssh/id_rsa.pub 

You might need to restart your server before your new key works. Or you can manually add it to ~/.ssh/authorized_keys, but I'm not sure if this file is special on ElasticHosts since it's added to automatically when you add keys to your profile.

You should also disable password logins as soon as you've confirmed you can login with your private key instead of passwords. To do this modify your sshd_config file and restart the ssh daemon.

patch /etc/ssh/sshd_config <<EOF
--- sshd_config         2014-08-06 11:32:04.916253729 +0100
+++ sshd_config.new     2014-08-06 11:23:06.848325452 +0100
@@ -63,7 +63,7 @@
 # To disable tunneled clear text passwords, change to no here!
 #PasswordAuthentication yes
 #PermitEmptyPasswords no
-PasswordAuthentication yes
+PasswordAuthentication no

 # Change to no to disable s/key passwords
 #ChallengeResponseAuthentication yes
@@ -94,7 +94,7 @@
 # PAM authentication, then enable this but set PasswordAuthentication
 # and ChallengeResponseAuthentication to 'no'.
 #UsePAM no
-UsePAM yes
+UsePAM no
EOF

service sshd restart
Email

Setup Postfix so we can receive email destined for the root account. (Courtesy of TecloTech's blog). I've used a Google Apps email account because it makes dealing with spam a non-issue, but this guide, or minor variation of this, should work for most email systems. Install Cyrus to allow secure connections, then create, hash (and secure) a file which will contain valid gmail credentials.

yum install cyrus-sasl-plain
cat >/etc/postfix/sasl_passwd <<EOF
smtp.gmail.com:587 [email protected]:emailpassword
EOF
chmod 600 /etc/postfix/sasl_passwd
postmap hash:/etc/postfix/sasl_passwd

Add some details to Postfix's config file

cat >>/etc/postfix/main.cf <<EOF
relayhost = smtp.gmail.com:587
smtp_tls_security_level = secure
smtp_tls_mandatory_protocols = TLSv1
smtp_tls_mandatory_ciphers = high
smtp_tls_secure_cert_match = nexthop

smtp_tls_CAfile = /etc/postfix/cacert.pem

smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
EOF

And create a certificate

cd /etc/pki/tls/certs
make hostname.pem
cp /etc/pki/tls/certs/hostname.pem /etc/postfix/cacert.pem
service postfix restart

Redirect local email to a real place

cat >>/etc/aliases <<EOF
root: [email protected]
EOF
newaliases

Test that it all works. This should be delivered to [email protected]

sendmail root <<EOF
Hello World!
EOF

Web server

First shutdown the base image and take a copy. Fire it up and modify the network config:

patch /etc/sysconfig/network <<EOF
--- network	2014-07-23 15:19:02.850816786 +0100
+++ network.new	2014-07-27 22:09:13.810328498 +0100
@@ -1,2 +1,2 @@
-HOSTNAME=base.example.com
+HOSTNAME=web1.example.com
 NETWORKING=yes
EOF

Update the VLAN network to reflect hardware address and IP address

NEWMAC=`ifconfig eth1 | grep HWaddr | awk '{print $5}'`
patch /etc/sysconfig/network-scripts/ifcfg-eth1 <<EOF
--- ifcfg-eth1  2014-07-23 15:19:31.146956710 +0100
+++ ifcfg-eth1.new      2014-07-28 13:20:38.404805597 +0100
@@ -1,8 +1,8 @@
 DEVICE=eth1
 BOOTPROTO="static"
-HWADDR="aa:aa:aa:aa:aa:aa"
+HWADDR="$NEWMAC"
 NM_CONTROLLED="yes"
 ONBOOT="yes"
 TYPE="Ethernet"
 NETMASK=255.255.0.0
-IPADDR=172.16.x.d
+IPADDR=172.16.x.b
EOF

ifdown eth1
ifup eth1

Install and start web server (Apache)

yum install httpd 
chkconfig httpd on
service httpd start

Open up the firewall to http requests (port 80)

patch /etc/sysconfig/iptables <<EOF
--- iptables    2014-07-28 14:03:45.874945252 +0100
+++ iptables.new        2014-07-28 14:03:58.745376763 +0100
@@ -8,6 +8,7 @@
 -A INPUT -p icmp -j ACCEPT
 -A INPUT -i lo -j ACCEPT
 -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
+-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
 -A INPUT -j REJECT --reject-with icmp-host-prohibited
 -A FORWARD -j REJECT --reject-with icmp-host-prohibited
 COMMIT
EOF
service iptables restart

If you need any other applications, install them too. In this example, I'm using php any mysqli

yum install php php-mysqli

The default web root is /var/www/html. Unless running multiple sites, this doesn't need to change. So put something in there to test that everything's working

cat >/var/www/html/index.php <<EOF
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Hello World!</title>
</head>

<body>
 <h1>Hello World!</h1>
 <?php echo "<h2>Hello from php!</h2>"; ?>
</body>
</html>
EOF

Database

This is optional, dependent on requirements. A database server could be setup on its own container or on the same container as the web server. If you're likely to be running at any kind of scale, it might be worth setting it up separately but if it will be small, it may as well stay on the same server.

If on a different system, take another copy of base, then setup hostname and VLAN as before:

patch /etc/sysconfig/network <<EOF
--- network     2014-07-23 15:19:02.850816786 +0100
+++ network.new 2014-07-27 22:09:13.810328498 +0100
@@ -1,2 +1,2 @@
-HOSTNAME=base.example.com
+HOSTNAME=db.example.com
 NETWORKING=yes
EOF

Update the VLAN network to reflect hardware address and IP address

NEWMAC=`ifconfig eth1 | grep HWaddr | awk '{print $5}'`
patch /etc/sysconfig/network-scripts/ifcfg-eth1 <<EOF
--- ifcfg-eth1  2014-07-23 15:19:31.146956710 +0100
+++ ifcfg-eth1.new      2014-07-28 13:20:38.404805597 +0100
@@ -1,8 +1,8 @@
 DEVICE=eth1
 BOOTPROTO="static"
-HWADDR="aa:aa:aa:aa:aa:aa"
+HWADDR="$NEWMAC"
 NM_CONTROLLED="yes"
 ONBOOT="yes"
 TYPE="Ethernet"
 NETMASK=255.255.0.0
-IPADDR=172.16.x.d
+IPADDR=172.16.x.c
EOF

ifdown eth1
ifup eth1

Next, where on the web server or a dedicated database server, install and configure mysql:

yum install mysql mysql-server
chkconfig mysqld on
service mysqld start

Create a database and user and change the root password. It's worth adding that granting all privileges is a particularly bad plan for security. You should only give your users the minimal privileges required to do the job.

mysql_secure_installation
mysql -u root <<EOF
SET PASSWORD = PASSWORD('dbrootpassword');
CREATE DATABASE databasename;
GRANT ALL PRIVILEGES ON databasename.*
  To 'databaseuser'@'172.16.x.c'
  IDENTIFIED BY 'dbpassword' WITH GRANT OPTION;
FLUSH PRIVILEGES;
EOF

Set the timezone:

mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
cat >>/etc/my.cnf <<EOF
default-time-zone=Europe/London
EOF
patch /etc/sysconfig/iptables <<EOF
--- iptables    2014-07-28 14:03:45.874945252 +0100
+++ iptables.new        2014-07-28 14:03:58.745376763 +0100
@@ -8,3 +8,4 @@
 -A INPUT -p icmp -j ACCEPT
 -A INPUT -i lo -j ACCEPT
+-A INPUT -m state --state NEW -m tcp -s 172.16.x.c -p tcp --dport 3306 -j ACCEPT
 -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
EOF
service iptables restart

That's it. Point your browser to the IP address (listed in the control panel) of the container running Apache (httpd), and hopefully you should see your 'Hello World!' page. Eg: 203.0.113.123

Hello World Example

CloudFlare

Another optional bit - but I can't see any great reason not to! It will even shortly be supporting https as standard.

Signup here. From memory, this is straigh forward enougt not to bother documenting. Let me know if I'm wrong! The Gist is to hand over dns control of your domain to CloudFlare. Check all your dns entries have been copied over and select which records you want CloudFlare to server. Magic, and easy!

comments powered by Disqus