For my own record and maybe for your convenience I wrote down the steps to install a Debian/Ubuntu VPS as web server. This setup is optimized for low memory usage (128-512 MB). This means nginx instead of Apache, no DNS server and no e-mail server (only outgoing mail) and some MySQL, PHP and nginx tuning.
This setup guide has been tested for a VPS on OpenVZ, Xen and KVM and for Debian 6.0 (Squeeze), Ubuntu 10.04 (Lucid Lynx) and 11.10 (Oneiric Ocelot). My favorite combination so far is KVM and Debian 6.0 with the Dotdeb repository (fast virtualization, stable Linux and the latest server software).
This setup is handling > 60,000 page views per day (> 100,000 hits) for a dozen of sites on a dual core VPS (2 × 2.4 Ghz) with 512 MB memory with ease (little CPU usage, load average 0.1-0.2, and almost no swapping).
I use Hurricane Electric Free DNS Management, because I like the fast web interface, the possibility to set the TTL and because it is free (up to 50 domains), but be aware wildcard domains are not allowed (anymore). List of free DNS providers.
has been so kind to provide a VPS to test this guide.
Cheap, reliable VPS providers:
Index
- Setup VPS
- Setup security
- Setup time
- Setup servers
- Setup MySQL
- Setup PHP
- Setup nginx
- Setup WordPress
- Setup Piwik
- Setup E-mail
- Setup FTP
- Setup XMPP
- Backup
- Monitoring
- Tuning
- Tools
- Remote desktop
Setup VPS
- Configure the host name
- Configure rDNS and SPF (for reliable e-mail)
- Check: dig -x <IP>
- Check: dig TXT <domain>
- SPF wizard
- Point a domain name to the VPS
- Install a recent version of Debian or Ubuntu
Setup security
- Login to the VPS:
- ssh root@domain
- Set new root password:
- passwd
- Fix the hostname when needed:
- hostname <name>
- nano /etc/hosts
- apt-get update
- apt-get upgrade
- apt-get install nano sudo
- Sometimes needed:
- locale-gen en_US en_US.UTF-8
- dpkg-reconfigure locales
- When IPv6 doesn’t work: nano /etc/gai.conf
precedence ::ffff:0:0/96 100
- If you want more recent package versions, use Dotdeb
- Don’t change to Dotdeb afterwards, because you will run into dependency problems!
- Not every VPS-template is perfect and it may be necessary to prevent kernel/grub updates
- nano /etc/apt/preferences
Package: linux-base linux-image linux-headers firmware-linux-free Pin: version 2.6.32-30 Pin-Priority: 1001 Package: grub-common Pin: version 1.98+20100804-14 Pin-Priority: 1001
This can be fixed this way too.
- mkdir /root/.ssh
- chmod 700 /root/.ssh
- nano /root/.ssh/authorized_keys
-
- paste key from local computer
- cat ~/.ssh/id_dsa.pub
- ssh-keygen -t dsa
- nano /etc/ssh/sshd_config
Port 22022 PasswordAuthentication no ClientAliveInterval 120 ClientAliveCountMax 600 #Subsystem sftp /usr/lib/openssh/sftp-server Subsystem sftp internal-sftp Match Group users ChrootDirectory /home AllowTCPForwarding no X11Forwarding no ForceCommand internal-sftp
- service ssh restart
- chown root:root /home/*
- apt-get install iptables
- nano /etc/resolv.conf
nameserver 8.8.8.8 nameserver 8.8.4.4
- mkdir /etc/fw
- Download FirewallBuilder
- use web server template
- allow https in
- allow port 22022 in (ssh)
- allow port 587 out (ssmtp)
- allow http/https out (for updates)
- allow ntp out
- allow ftp out
- allow xmpp-server in/out
- allow xmpp-client in
- Install
- nano /etc/rc.local
/etc/fw/firewall.fw echo 1 >/proc/sys/net/ipv6/conf/eth0/disable_ipv6
- Prevent DoS attacks (to a certain extend):
iptables -I INPUT 1 -i eth0 -p tcp --syn --dport :1024 -m connlimit --connlimit-above 25 -j LOG --log-prefix "Conn limit: " iptables -I INPUT 2 -i eth0 -p tcp --syn --dport :1024 -m connlimit --connlimit-above 25 -j DROP
You can put these extra rules in Firewall Builder: double click on your machine name under Firewalls, then button Firewall Settings, tab Prolog/Epilog, second textarea.
- nano /etc/default/useradd
SHELL=/bin/false
Setup time
- apt-get install ntpdate
- nano /root/ntpdate.sh
#!/bin/sh /usr/sbin/ntpdate pool.ntp.org
- chmod 755 /root/ntpdate.sh
- crontab -e
0 9 * * * /root/ntpdate.sh >>/root/ntpdate.log 2>&1
Not needed/possible when shared Linux kernel (for example OpenVZ)
Setup servers
- apt-get purge sendmail* apache2* bind9 samba xinetd
- apt-get install nginx php5-fpm php5-cli php5-apc php5-curl php5-gd php5-suhosin php5-mcrypt php5-intl php5-mysql mysql-server
- Use Dotdeb repository (see before)
- apt-get install python-software-properties
- add-apt-repository ppa:nginx/stable
- add-apt-repository ppa:brianmercer/php
- “Obsolete and due to be closed in the near future.“
- apt-get update
Setup MySQL
- nano /etc/mysql/my.cnf
[mysqld] skip-innodb #skip-external-locking skip-networking query_cache_size = 64M key_buffer = 64M table_cache = 1024
- Debian:
default-storage-engine=MyISAM
- service mysql restart
Setup PHP
- nano /etc/php5/fpm/pool.d/www.conf
;pm = dynamic ;pm.max_children = 10 ;pm.start_servers = 4 pm = static pm.max_children = 3 pm.status_path = /fpm_status catch_workers_output = yes pm.max_requests = 1000 listen.backlog = -1 request_terminate_timeout = 60s request_slowlog_timeout = 30s slowlog = /var/log/php5-fpm-slow.log
- You could also use the dynamic process manager:
pm = dynamic pm.max_children = 7 pm.start_servers = 1 pm.min_spare_servers = 1 pm.max_spare_servers = 1
- or the new on demand process manager:
pm = ondemand pm.max_children = 3 pm.process_idle_timeout = 3s
- nano /etc/php5/fpm/php.ini
cgi.fix_pathinfo = 0; memory_limit = 80M; user_ini.filename = ".user.ini" upload_max_filesize = 4M open_basedir = /home:/tmp allow_url_fopen = Off mail.add_x_header = Off [PATH=/path/to/folder] open_basedir = suhosin.simulation = On
- Debian:
date.timezone = "Europe/Amsterdam"
- nano /etc/php5/fpm/conf.d/apc.ini
apc.enabled=1 apc.shm_size=96M apc.cache_by_default=0 ;apc.ttl=900 apc.stat=1
- Get maximum size: sysctl kernel.shmmax
- nano /etc/sysctl.conf
kernel.shmmax=83886080
- nano .user.ini
apc.cache_by_default=1
- nano /etc/php5/conf.d/suhosin.ini
suhosin.mail.protect=2 suhosin.memory_limit = 128M [PATH=/path/to/piwik] suhosin.memory_limit = 800M
- service php5-fpm restart
- Ubuntu 11.10: fuser forks and never reaps its children (serious unfixed bug)
Setup nginx
- nano /etc/nginx/nginx.conf
worker_processes 2; server_tokens off; client_max_body_size 4M;
- Enabled GZIP:
#gzip on; gzip_http_version 1.1; gzip_vary on; gzip_comp_level 1; gzip_min_length 1100; gzip_proxied any; gzip_types text/plain text/css application/json application/x-javascript text/x$ gzip_buffers 16 8k; gzip_disable "MSIE [1-6].(?!.*SV1)";
(you might want to use extra config files in /etc/nginx/conf.d)
- service nginx restart
If you want to migrate from one server to another, you can do this:
server {
listen 80;
location / {
proxy_pass http://aaa.bbb.ccc.ddd;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
}
Setup WordPress
- Segregate users:
- useradd <username>
- passwd <username>
- usermod -G users <username>
- File permissions
find /home -type d -name 'wp-content' -exec mkdir -p {}/../assets \;
find /home -type d -name 'wp-content' -exec mkdir -p {}/gallery \;
find /home -type d -name 'wp-content' -exec mkdir -p {}/uploads \;
find /home -type d -name 'wp-content' -exec mkdir -p {}/upgrade \;
chown root:root /home
chown -R <username>:www-data <username>
find /home -type d -exec chmod 2750 {} \;
chmod 755 /home
find /home -type d -name '.ssh' -exec chmod -R 2700 {} \;
find /home -type d -name 'wp-content' -exec chmod -R 2770 {}/gallery \;
find /home -type d -name 'wp-content' -exec chmod -R 2770 {}/uploads \;
find /home -type d -name 'wp-content' -exec chmod -R 2770 {}/upgrade \;
find /home -type d -name 'wp-content' -exec chmod -R 2770 {}/upgrade \;
find /home/ -type f -exec chmod 0640 {} \;
Setup Piwik
- Installing Piwik
- Archiving
- crontab -e
9 * * * * su www-data -c "/usr/bin/php5 .../misc/cron/archive.php --url=http://example.org/" >>.../piwik-archive.log 2>&1
Setup E-Mail
- Workaround for bug
- apt-get install ssmtp mutt
- nano /etc/ssmtp/ssmtp.conf
Root=user@example.org MailHub=smtp.gmail.com:587 RewriteDomain=example.org Hostname=example.org FromLineOverride=YES UseTLS=YES UseSTARTTLS=YES AuthUser=user@example.org AuthPass=... AuthMethod=LOGIN
- nano /etc/ssmtp/revaliases
root:user@example.org:smtp.gmail.com:587 www-data:user@example.org:smtp.gmail.com:587
For Google mail send limits, see here.
Don’t forget to setup rDNS and SPF.
Possible alternative: Msmtp (not tested)
Setup FTP
- apt-get install proftpd
- standalone
- nano /etc/proftpd/proftpd.conf
RequireValidShell off
- service proftpd restart
(needed for WordPress updates)
Setup XMPP
- Setup prosody repository
- apt-get install liblua5.1-sec1 lua-zlib prosody
Backup
- Backup databases:
fn="/tmp/backup_mysql.sql.gz" mysqldump -u... -p... --all-databases | gzip -9 >$fn scp -q -P 22022 $fn user@domain:/path/to/folder/
- Restore databases:
gunzip <test.gz >/tmp/dump.sql mysql -u... -p... </tmp/dump.sql mysqladmin -u... -p... flush-privileges
- You might need to restore the debian-sys-maint password:
- nano /etc/mysql/debian.cnf
- Backup files with rsync (apt-get install rsync)
rsync -avz -e 'ssh -p 22022' /etc/ user@domain:/path/to/folder/ rsync -avz -e 'ssh -p 22022' /var/lib/ user@domain:/path/to/folder/ rsync -avz -e 'ssh -p 22022' --exclude some/folder /home/ user@domain:/path/to/folder/
- crontab -e
0 1 * * * /root/backup.sh >>/root/backup_`date +\%F`.log 2>&1
- Even better: use duplicity or rdiff-backup
duplicity --exclude-filelist=duplicity.exclude --full-if-older-than 1W --allow-source-mismatch / rsync://user@host:22022//path/to/backup/folder duplicity remove-older-than 1M --force rsync://user@host:22022//path/to/backup/folder
Some typical excludes for duplicity:
- /sys - /dev - /proc - /tmp - /mnt - /var/lib/mysql - /var/lib/mongodb
You might need a backport of duplicity on Debian.
Monitoring
- apt-get install munin munin-node sysstat libwww-perl libipc-sharelite-perl libcache-cache-perl
- perl -MCPAN -eshell
- install IPC::ShareLite
- nano /etc/munin/plugin-conf.d/munin-node
[nginx_status] env.url http://host/nginx_status [nginx_request] env.url http://host/nginx_status
- ln -s /usr/share/munin/plugins/nginx_* /etc/munin/plugins
- ln -s /usr/share/munin/plugins/mysql_* /etc/munin/plugins
- rm /etc/munin/plugins/mysql_innodb
- rm /etc/munin/plugins/iostats_ios
- service munin-node restart
- munin-node-configure –suggest
- PHP5-FPM
- Other plugins
Tuning
- nano /etc/sysctl.conf
vm.swappiness=10
Tools
Be sure to lock these down behind a firewall / password.
Remote desktop
- apt-get install lxde-core lxtask lxde-icon-theme xfonts-base xarchiver tightvncserver
- tightvncserver :1
- tightvncserver -kill :1
- nano ~/.vnc/xstartup
lxterminal & /usr/bin/lxsession -s LXDE &
- tightvncserver :1
- If you want a browser:
- apt-get install epiphany-browser
- xtightvncviewer -via “user@domain -p 22022″ localhost:1
- If you want VNC in your web browser: noVNC
Please let me know if you have any remarks or suggestions.