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/0x6770 on patchlevel 358) [x86_64-linux], MBARI 0x6770, 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
sudo /etc/init.d/nginx restart
Leave a Reply