Skip to content

Setting up and securing web server (+MySQL +Rails/PHP) on Ubuntu 10.04 LTS

After repeating these operations many times in various setups, I decided to create a public set of instructions and share them with the world. This should be suitable for most of the simple web sites, utilizing Ruby on Rails or PHP.

The setup works on Ubuntu 10.04 Server LTS (scheduled end of life April 2015). Other components of the setup are Nginx as the web server, Phusion Passenger as application server.

I’m using this setup most often on Linode VPS, however none of the instructions are Linode specific.

Create non-root user with sudo rights

(Login as root via ssh)

Add a new user (this user will be an administrator of the server – will have the ability to log in via ssh and use sudo, it is different from user account used for the web application):

adduser username

Add this user to sudoers list:

visudo

in the editor add

username ALL=(ALL) ALL

Securing sshd

Edit /etc/ssh/sshd_config:

Change the following lines (note, you’re changing default port for ssh connections – you’ll need to take that into account when connecting to the server later):

Port 6668
PermitRootLogin no
X11Forwarding no
Protocol 2
PasswordAuthentication no
UsePAM no

Add the following lines (here username is the name of the administrator user that was created earlier):

UseDNS no 
AllowUsers username

Restart ssh daemon:

/etc/init.d/ssh restart

Add authentication keys for new administrator user

Note, that ‘user’ here is the name of the administrator user that was created earlier.

mkdir /home/user/.ssh 
vim /home/user/.ssh/authorized_keys

When editing authorized_keys file, insert a public of that user (e.g. your personal public key, if you want to make yourself an administrator).

chown -R user:user /home/user/.ssh 
chmod 600 /home/user/.ssh/authorized_keys

Now you can log out as root and log in as a sudo user you just created. NOTE! You won’t be able to log in as rot via ssh anymore.

Set up iptables firewall

Save existing rules

sudo sh -c 'iptables-save > /etc/iptables.up.rules'

Create test rules

sudo vim /etc/iptables.test.rules

Here’s an example of the firewall setup:

*filter

#  Allows all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT

#  Accepts all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

#  Allows all outbound traffic
#  You can modify this to only allow certain traffic
-A OUTPUT -j ACCEPT

# Allows HTTP and HTTPS connections from anywhere (the normal ports for websites)
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

# (alternative) Uncomment to allow HTTP connections only from frontend server
# -A INPUT -p tcp -m state --state NEW -s frontend.server.ip --dport 80 -j ACCEPT

# Allow SSH connections
# THE -dport NUMBER IS THE SAME ONE YOU SET UP IN THE SSHD_CONFIG FILE
#
-A INPUT -p tcp -m state --state NEW --dport 6668 -j ACCEPT

# Uncomment to allow MySQL connections from defined servers
#-A INPUT -p tcp -m state --state NEW -s app.server.1.ip --dport 3306 -j ACCEPT

# Uncomment to allow memcached connections from app servers
#-A INPUT -p tcp -m state --state NEW -s app.server.1.ip --dport 11211 -j ACCEPT

# Uncomment to allow ping
#-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

# log iptables denied calls
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

# Reject all other inbound - default deny unless explicitly allowed policy
-A INPUT -j REJECT
-A FORWARD -j REJECT

COMMIT

Apply firewall rules:

sudo iptables-restore < /etc/iptables.test.rules

Check if everything is correct

sudo iptables -L

If everything is fine, save the rules

sudo sh -c 'iptables-save > /etc/iptables.up.rules'

Make sure firewall rules are applied as soon as network interface comes up:

sudo vim /etc/network/interfaces

Add in the editor

pre-up iptables-restore < /etc/iptables.up.rules
after
iface lo inet loopback

Fix locales

sudo locale-gen en_GB.UTF-8 
sudo /usr/sbin/update-locale LANG=en_GB.UTF-8

Set timezone and hostname

sudo dpkg-reconfigure tzdata

Select your timezone

sudo sh -c 'echo "name" > /etc/hostname' 
sudo hostname -F /etc/hostname

Edit /etc/hosts (insert your VPS IP address and your server hostname here)

IP.ad.dr.ess hostname

Update the system

Uncomment universe repositories

sudo vim /etc/apt/sources.list

And run safe and full upgrades

sudo aptitude update 
sudo aptitude safe-upgrade 
sudo aptitude full-upgrade

Install build essentials

sudo aptitude install build-essential 
sudo apt-get install zlibc zlib1g-dev libcurl4-openssl-dev libreadline5-dev

Install setlock for crontab

sudo apt-get install daemontools

Set up Ruby Enterprise Edition 2012.02

Install ruby-ee (ruby 1.8.7 (2012-02-08 MBARI 8/0×6770 on patchlevel 358) [x86_64-linux], MBARI 0×6770, Ruby Enterprise Edition 2012.02):

mkdir ~/tmp 
cd ~/tmp 
wget http://rubyenterpriseedition.googlecode.com/files/ruby-enterprise-1.8.7-2012.02.tar.gz
tar xvzf ruby-enterprise-1.8.7-2012.02.tar.gz

Now enable tc_malloc large pages feature (32bit systems only): http://www.ivankuznetsov.com/2011/07/ree-segfaults-when-rails-application-has-too-many-localisation-files.html

sudo ./ruby-enterprise-1.8.7-2012.02/installer

Create soft links to ruby tools

sudo ln -s /opt/ruby-enterprise-1.8.7-2012.02/bin/ruby /usr/local/bin/ruby
sudo ln -s /opt/ruby-enterprise-1.8.7-2012.02/bin/gem /usr/local/bin/gem
sudo ln -s /opt/ruby-enterprise-1.8.7-2012.02/bin/irb /usr/local/bin/irb
sudo ln -s /opt/ruby-enterprise-1.8.7-2012.02/bin/rake /usr/local/bin/rake
sudo ln -s /opt/ruby-enterprise-1.8.7-2012.02/bin/bundle /usr/local/bin/bundle

Create parametrised ruby launcher

sudo vim /usr/local/bin/ruby-with-env

With the following content:

#!/bin/bash 
export RUBY_HEAP_MIN_SLOTS=1500000 
export RUBY_HEAP_SLOTS_INCREMENT=500000 
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1 
export RUBY_GC_MALLOC_LIMIT=50000000 
exec "/usr/local/bin/ruby" "$@"

and make this file executable:

sudo chmod +x /usr/local/bin/ruby-with-env

Set up necessary components

Install readline wrap

sudo apt-get install rlwrap

Install git

sudo apt-get install git-core

Install native dependencies for gems

sudo apt-get install libcurl4-gnutls-dev libxslt-dev libssl-dev

Install MySQL

sudo apt-get install mysql-server libmysqlclient-dev

Install Passenger 3.0.11 and let it compile nginx

cd /tmp wget http://nginx.org/download/nginx-1.0.14.tar.gz
tar xvzf nginx-1.0.14.tar.gz 
sudo /opt/ruby-enterprise-1.8.7-2012.02/bin/passenger-install-nginx-module

- choose installation option 2
- provide source path /tmp/nginx-1.0.14
- provide additional compilation options –with-http_realip_module –with-http_gzip_static_module –without-mail_pop3_module –without-mail_smtp_module –without-mail_imap_module

Create nginx init script:

sudo vim /etc/init.d/nginx

Enter the following content:

#! /bin/sh

### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the nginx web server
# Description:       starts nginx using start-stop-daemon
### END INIT INFO

PATH=/opt/nginx/sbin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/opt/nginx/sbin/nginx
NAME=nginx
DESC=nginx

test -x $DAEMON || exit 0

# Include nginx defaults if available
if [ -f /etc/default/nginx ] ; then
    . /etc/default/nginx
fi

set -e

. /lib/lsb/init-functions

test_nginx_config() {
  if nginx -t
  then
    return 0
  else
    return $?
  fi
}

case "$1" in
  start)
    echo -n "Starting $DESC: "
        test_nginx_config
    start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
        --exec $DAEMON -- $DAEMON_OPTS || true
    echo "$NAME."
    ;;
  stop)
    echo -n "Stopping $DESC: "
    start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
        --exec $DAEMON || true
    echo "$NAME."
    ;;
  restart|force-reload)
    echo -n "Restarting $DESC: "
    start-stop-daemon --stop --quiet --pidfile \
        /var/run/$NAME.pid --exec $DAEMON || true
    sleep 1
        test_nginx_config
    start-stop-daemon --start --quiet --pidfile \
        /var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS || true
    echo "$NAME."
    ;;
  reload)
        echo -n "Reloading $DESC configuration: "
        test_nginx_config
        start-stop-daemon --stop --signal HUP --quiet --pidfile /var/run/$NAME.pid \
            --exec $DAEMON || true
        echo "$NAME."
        ;;
  configtest)
        echo -n "Testing $DESC configuration: "
        if test_nginx_config
        then
          echo "$NAME."
        else
          exit $?
        fi
        ;;
  status)
    status_of_proc -p /var/run/$NAME.pid "$DAEMON" nginx && exit 0 || exit $?
    ;;
  *)
    echo "Usage: $NAME {start|stop|restart|reload|force-reload|status|configtest}" >&2
    exit 1
    ;;
esac

exit 0

Make it executable:

sudo chmod +x /etc/init.d/nginx

Add nginx to autostartup list:

sudo /usr/sbin/update-rc.d -f nginx defaults

Edit main nginx config

sudo vim /opt/nginx/conf/nginx.conf

Enter the following content:

user  www-data;
worker_processes  4;

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
    worker_connections  8192;
    use epoll;
}

http {
    passenger_root /opt/ruby-enterprise-1.8.7-2012.02/lib/ruby/gems/1.8/gems/passenger-3.0.11;
    passenger_ruby /usr/local/bin/ruby-with-env;

    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log;

    sendfile       on;
    tcp_nopush     on;
    tcp_nodelay        on;
    keepalive_timeout  65;

    # Passenger never sleeps!
    passenger_pool_idle_time 0;

    # Use more instances, because memory is enough
    passenger_max_pool_size 15;

    # Start application instantly
    passenger_pre_start http://127.0.0.1/;

    client_max_body_size 4m;

    include /opt/nginx/conf/sites-enabled/*;
}

Create site configuration and log directories:

sudo mkdir /opt/nginx/conf/sites-enabled 
sudo mkdir /opt/nginx/conf/sites-available 
sudo mkdir /var/log/nginx

Set up virtual hosts

Create a separate user for each virtual host, create a home directory, set password, create folder for logs and web application, set permissions

sudo useradd newuser -d /home/newuser 
sudo mkdir /home/newuser 
sudo passwd newuser password
sudo mkdir /home/username/hostname 
sudo mkdir /home/username/logs 
sudo mkdir /home/username/logs/hostname 
sudo chown -R username:www-data /home/username/ 
sudo chmod 750 /home/username/

Configure hosts:

sudo vim /opt/nginx/conf/sites-available/mysite.com
server {
        listen   80;
        server_name  .mysite.com;

        access_log  /home/user/logs/mysite.com/access.log;
        error_log  /home/user/logs/mysite.com/error.log;
        root   /home/user/mysite.com/current/public;
        # uncomment if traffic is coming from a frontend server/loadbalancer
        #set_real_ip_from   IP.ad.dre.ss;
        #real_ip_header     X-Real-IP;

        passenger_enabled on;
        passenger_min_instances 5;
}

Enable site:

sudo ln -s /opt/nginx/conf/sites-available/mysite.com /opt/nginx/conf/sites-enabled/mysite.com

Set up log rotation:

sudo ln -s /home/user/mysite.com/current/config/logrotate.conf /etc/logrotate.d/mysite

Install fastcgi (for PHP setups)

Install dependencies

sudo apt-get install libmcrypt-dev libxml2-dev libpng-dev autoconf2.13 libevent-dev libltdl-dev

Download latest stable PHP 5.2.13, Suhosin patch, PHP-FPM patch

cd ~/tmp wget http://pl2.php.net/get/php-5.2.13.tar.gz/from/pl.php.net/mirror
wget http://download.suhosin.org/suhosin-patch-5.2.13-0.9.7.patch.gz
wget http://php-fpm.org/downloads/php-5.2.13-fpm-0.5.13.diff.gz
tar xvzf php-5.2.13.tar.gz gunzip suhosin-patch-5.2.13-0.9.7.patch.gz 
gunzip php-5.2.13-fpm-0.5.13.diff.gz 
cd php-5.2.13 
patch -p 1 -i ../php-5.2.13-fpm-0.5.13.diff 
patch -p 1 -i ../suhosin-patch-5.2.13-0.9.7.patch 
./buildconf --force 
./configure --enable-fastcgi --enable-fpm --with-mcrypt --with-zlib --enable-mbstring --with-openssl --with-mysql --with-mysql-sock --with-gd --without-sqlite --disable-pdo 
make 
make test 
sudo make install

Alternatively download latest stable PHP 5.3.2, Suhosin patch, apply PHP-FPM patch

cd ~/tmp http://fi.php.net/get/php-5.3.2.tar.gz/from/this/mirror
wget http://download.suhosin.org/suhosin-patch-5.3.2-0.9.9.1.patch.gz
tar xvzf php-5.3.2.tar.gz gunzip suhosin-patch-5.3.2-0.9.9.1.patch.gz 
cd php-5.3.2 
patch -p 1 -i ../suhosin-patch-5.3.2-0.9.9.1.patch 
svn co http://svn.php.net/repository/php/php-src/trunk/sapi/fpm sapi/fpm 
./buildconf --force 
./configure --enable-fastcgi --enable-fpm --with-mcrypt --with-zlib --enable-mbstring --with-openssl --with-mysql --with-mysql-sock --with-gd --without-sqlite --disable-pdo --disable-reflection 
make 
make test 
sudo make install

Uninstall autoconf2.13 after compilation, since it is an old version and only required for PHP compilation

sudo apt-get uninstall autoconf2.13

Change user and group of php-fpm processes to your user dedicated to this application and www-data – lines 63 and 66 respectively

sudo vim /usr/local/etc/php-fpm.conf

Edit PHP settings

sudo vim /etc/php5/cgi/php.ini

Set:

max_execution_time = 30 
memory_limit = 64M 
error_reporting = E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR 
display_errors = Off 
log_errors = On 
error_log = /var/log/php.log 
register_globals = Off
Now restart nginx and you’re all set
sudo  /etc/init.d/nginx restart
Share

One Comment

  1. Mark wrote:

    Not so far I have found new cool tool to work with mySQL on ubuntu – Valentina Studio. Its free edition can do things more than many commercial tools!!
    I very recommend check it. http://www.valentina-db.com/en/valentina-studio-overview

    Friday, December 20, 2013 at 12:05 | Permalink

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*