How to Configure a Linode VPS Server to Run Ubuntu 12.04 LTS with a LEMP Stack

Published on February 21, 2013.

My command line usage skyrocketed in 2012. Being a slave to shared hosting is not fun, and I finally built the confidence (and cash flow) necessary to upgrade to a VPS. The problem was that I had no idea what to do after purchasing the VPS, let alone which company to go with.

After much deliberation, I settled on Linode. The plans are affordable and they receive rave reviews from developers who are way smarter than me. Also, they have an incredible knowledgebase to help us sysadmin noobs get started.

  1. Getting started
  2. Adding DNS Records
  3. Connect to your VPS
  4. Basic server stability
  5. Basic server security
  6. Postfix + Gmail SMTP server relay
  7. More security
  8. Install a barebones LEMP stack
  9. Configure Nginx
  10. Configure php5-fpm
  11. Configure Nginx server blocks (virtual hosts)
  12. Installing Git and using GitHub
  13. Conclusion

Getting started

  1. March on over to Linode and pick a plan. I signed up for their entry-level “Linode 512” plan. You can always upgrade later.
  2. Log in to Linode Manager and select a data center. I chose Newark, NJ.
  3. Deploy an Ubuntu 12.04 LTS linux distribution, leaving default values for “Disk Size” and “Swap Disk”.
  4. Boot up the VPS for the first time by selecting it in the Linode Manager and clicking “Boot”.

Adding DNS records

Now is a good time to map your domain name to the VPS. Be aware that this process can take a while sometimes, so allow up to 24 hours for DNS changes to be reflected across the web.

  1. Add a master DNS zone for [] in the Linode Manager.
  2. Navigate to your domain’s registrar (I use Namecheap) and set the following name servers:

Set up reverse DNS

Though not typically required, reverse DNS is helpful if you ever have to do any network troubleshooting (such as traceroute, ping, etc) or are concerned with spam emails. It only takes a moment to set up, so why not?

  1. From the Linode Manager, click the “Remote Access” tab
  2. Click “Reverse DNS”
  3. Enter [] as the domain
  4. Click “Lookup”
  5. After your domain is found, confirm by clicking “Yes”

Connect to your VPS

After you’ve got the VPS booted and running, it’s time for the fun to begin. Right off the bat you should “shell in” to the VPS using the ssh command from your terminal, set the hostname, edit your hosts file, and set the default language.

Since I am a Mac OSX user, all command line instructions are written as such. This can all be done from any operating system — simply use the equivalent syntax for your operating system’s command prompt.

Open Terminal on your Mac, or if you are on Windows download and open putty, since Windows doesn’t ship with a very good command prompt.

Connect to the VPS:

ssh root@<your IP address>

Set your hostname, verify it, and reconnect to the VPS:

echo "your hostname" > /etc/hostname
hostname -F /etc/hostname

Set the fully qualified domain name (FQDN):

nano /etc/hosts

Add the following line and save the file (Ctrl + X saves the file in nano): <your hostname>.<> <yourhostname>

Set the language and time zone:

locale-gen en_US.UTF-8
update-locale LANG=en_US.UTF-8
dpkg-reconfigure tzdata

Google Apps

There are several different ways you can set up an email server on a VPS. Since I already use Google Apps with this domain, it made sense for me to link it up to my VPS as well. This setup is probably not optimal for everyone, but it works for me.

These directions assume that you’ve already signed up and configured a Google Apps account for your domain.

Create a new API key by clicking on “my profile” in the Linode Manager, confirming your password, and clicking the “Generate a new API key” button.

Download the gapps-linode-dns script onto the VPS:

sudo wget ""
  1. Make the script executable and run it:
chmod +x

Paste in your API key, domain name, and answer “y” to the rest of the configuration questions. Wait a few minutes, and ask a friend to send some test emails to ensure it’s working properly.

Basic server stability

In the event of a traffic spike, the VPS may run out of memory and your site could be down for hours, well after the spike has ended. To avoid the extended downtime, you’ll want the VPS to automatically reboot instead. It’s one less thing for you to worry about and provides a bit more stability (a few minutes of downtime as opposed to hours).

Set the server to automatically reboot when it runs out of memory:

sudo nano /etc/sysctl.conf

Add the following lines and save:


Basic server security

This part is extensive, and for good reason. Security is the most important part of configuring a web server. Below are several measures to make sure you’ve got a good foundation in place to safeguard your server.

Create a new user:

adduser <your username>

Add the following line and save:

<your username> ALL=(ALL:ALL) ALL

Configure SSH:

nano /etc/ssh/sshd_config

Edit the following lines and save:

Port <your port number>
Protocol 2
PermitRootLogin no
PermitEmptyPasswords no
UseDNS no
AllowUsers yourusername

You should choose a port number that is less than 1024, and not 22. Here’s why.

Restart SSH:

service ssh restart

Login as new user:

ssh -p <your port number> <your username>@<>

Update Ubuntu:

sudo apt-get update && sudo apt-get upgrade --show-upgraded

If you get the error “resolvconf: Error: /etc/resolv.conf isn’t a symlink, not doing anything” while updating, run the command below, answering “yes” to all the prompts, and reboot the VPS from the Linode Manager dashboard:

sudo dpkg-reconfigure resolvconf

Enable automatic security updates:

sudo apt-get install unattended-upgrades
sudo nano /etc/apt/apt.conf.d/10periodic

Edit the following lines and save:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Edit the following lines and save:

Unattended-Upgrade::Allowed-Origins {
	"Ubuntu lucid-security";
	//"Ubuntu lucid-updates";

Generate SSH keys on your local machine for passwordless login:

ssh-keygen -t rsa -C "Your comment"

Secure copy the public key from your local machine to the VPS:

On the VPS, run:

sudo mkdir -p ~/.ssh/authorized_keys
sudo chown -R <your username>:<your username> .ssh

On your local machine, run:

scp -P <your port number> ~/.ssh/ <your username>@<>:

On the VPS, run:

cat ~/ >> ~/.ssh/authorized_keys
sudo chmod 700 .ssh
sudo chmod 600 .ssh/authorized_keys
sudo rm ~/
ssh -p <your port number> <your username>@<>

Configure firewall with ufw:

sudo apt-get install nmap
nmap -v -sT localhost
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw logging on
sudo ufw allow ssh/tcp
sudo ufw allow http/tcp
sudo ufw allow 443
sudo ufw allow <your port number>
sudo ufw enable
sudo ufw status

Secure shared memory:

sudo nano /etc/fstab

Add the following line and save:

tmpfs /dev/shm tmpfs defaults,noexec,nosuid 0 0

Mount that shit:

sudo mount -a

Install Fail2Ban to prevent repeated login attempts:

sudo apt-get install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Edit the following lines and save:

destemail = <>
action = %(action_mwl)s


enabled  = true
port     = <your port number>
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 6


enabled  = true
port     = <your port number>
filter   = sshd-ddos
logpath  = /var/log/auth.log
maxretry = 6

Restart Fail2Ban:

sudo service fail2ban restart

Harden network with sysctl settings:

sudo nano /etc/sysctl.conf

Edit the file and save:

# IP Spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignore ICMP broadcast requests
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0

# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Block SYN attacks
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5

# Log Martians
net.ipv4.conf.all.log_martians = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0

# Ignore Directed pings
net.ipv4.icmp_echo_ignore_all = 1

Apply new settings:

sudo sysctl -p

Prevent IP Spoofing:

sudo nano /etc/host.conf

Add the following lines and save:

order bind,hosts
nospoof on

Postfix + Gmail SMTP server relay

If you ever need to set up cron jobs, or would like error and log reports emailed to you, then pay attention to this section. The below instructions will show you how to configure Postfix to send important server emails to your Google Apps email account.

Install Postfix and its dependencies:

sudo apt-get install postfix libsasl2-2 ca-certificates libsasl2-modules


sudo nano /etc/postfix/

Add the following lines and save:

# sets gmail as relay
relayhost = []:587

# use tls

# use sasl when authenticating to foreign SMTP servers
smtp_sasl_auth_enable = yes

# path to password map file
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd

# list of CAs to trust when verifying server certificate
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt

# eliminates default security options which are imcompatible with gmail
smtp_sasl_security_options =

Edit sasl_passwd:

sudo nano /etc/postfix/sasl_passwd

Add the following line and save:

[]:587  <your gmail username>:<your gmail password>

Chown sasl_passwd:

sudo postmap /etc/postfix/sasl_passwd
sudo chown postfix /etc/postfix/sasl_passwd*

Reload Postfix and send a test email:

sudo service postfix reload
sendmail <test email address> This email worked! .

More server security

There is no such thing as “enough” security. You can always do more to make sure your web server is safe and secure, so that’s what you’ll do next.

Check for rootkits with RKHunter and CHKRootKit:

sudo apt-get install rkhunter chkrootkit
sudo chkrootkit
sudo rkhunter --update
sudo rkhunter --propupd
sudo rkhunter --check

Analyze system log files with LogWatch:

sudo apt-get install logwatch libdate-manip-perl
sudo logwatch | less
sudo logwatch --mailto <your email address> --output mail --format html --range 'between -7 days and today'

Audit system security with Tiger:

sudo apt-get install tiger
sudo tiger
sudo less /var/log/tiger/*

Install a barebones LEMP stack

Instead of a traditional LAMP stack, I decided to give nginx a whirl instead of Apache. Nginx excels at serving small static sites, which is what I have here. If you’d like to use a similar stack on your server, then follow the guidelines below.

Add PPAs for Nginx and PHP, as Ubuntu’s defaults are quite outdated:

sudo apt-get install python-software-properties
sudo add-apt-repository ppa:nginx/stable
sudo add-apt-repository ppa:ondrej/php5
sudo apt-get update

Install Nginx, MySQL, PHP, and all dependencies:

sudo apt-get install mysql-server nginx php5 php5-fpm php5-cli php5-mysql php5-curl php5-gd php5-intl php-pear php5-imagick php5-imap php5-mcrypt php5-memcache php5-ming php5-ps php5-pspell php5-recode php5-snmp php5-sqlite php5-tidy php5-xmlrpc php5-xsl php5-xcache
  1. Start Nginx, php5-fpm, and MySQL:
sudo apt-cache search php5 | grep php5
sudo service nginx start
sudo service php5-fpm start
sudo start mysql
ifconfig eth0 | grep inet | awk '{ print $2 }'

Configure Nginx

Below is a set of useful defaults for a typical Nginx setup. I find this is a good starting point, but don’t be afraid to do your own tests, tweaks, and refinements.

Modify the nginx.conf file:

sudo nano /etc/nginx/nginx.conf

Edit the following lines and save:

worker_processes 4;
keepalive_timeout 2;

Make a backup copy of Nginx’s “default” file and modify the original:

sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak
sudo nano /etc/nginx/sites-available/default

Edit the following lines and save:

server {
	listen   80; ## listen for ipv4; this line is default and implied
	listen   [::]:80 default ipv6only=on; ## listen for ipv6

	root /usr/share/nginx/html;
	index index.html index.htm index.php;

	# Make site accessible from http://localhost/
	server_name _;
	location / {
		try_files $uri $uri/ /index.html;

	location /doc/ {
		alias /usr/share/doc/;
		autoindex on;
		deny all;

	# Redirect server error pages to the static page /50x.html
	error_page 500 502 503 504 /50x.html;
	location = /50x.html {
		root /usr/share/nginx/html;

	# Pass the PHP scripts to FastCGI server listening on
	location ~ \.php$ {
		fastcgi_split_path_info ^(.+\.php)(/.+)$;
		fastcgi_pass unix:/tmp/php5-fpm.sock;
		fastcgi_index index.php;
		include fastcgi_params;

	# Deny access to .htaccess files, if Apache's document root concurs
	# with Nginx’s one
	location ~ /\.ht {
		deny all;

Configure php5-fpm

Once Nginx is configured, we have to make it place nice with PHP. That’s where php5-fpm comes in.

Modify the php.ini file:

sudo nano /etc/php5/fpm/php.ini

Edit the following line and save:

cgi.fix_pathinfo = 0

Modify the www.conf file:

sudo nano /etc/php5/fpm/pool.d/www.conf

Edit the following line and save:

listen = /tmp/php5-fpm.sock

Reload php5-fpm and nginx:

sudo service php5-fpm reload
sudo service nginx reload

Create info.php:

sudo nano /usr/share/nginx/html/info.php

Add the following line and save:

<?php phpinfo(); ?>

Visit http://youripaddress/info.php in a web browser. You should see a bunch of PHP information on this page.

Configure Nginx server blocks (aka virtual hosts)

In Nginx, virtual hosts are called “server blocks”. Below you will set up a virtual host for your domain so that it can be viewed at

Create a directory for your domain:

sudo mkdir -p /var/www/[]/{public_html,logs}
sudo chown -R [your username]:www-data /var/www/[]/public_html
sudo chmod -R 755 /var/www

Create a “Hello world” index page:

sudo nano /var/www/<>/public_html/index.php

Add the following lines and save:

		<title>Hello world</title>
		<h1>Hello world!</h1>

Create an Nginx config file for your domain:

sudo nano /etc/nginx/sites-available/<>

Add the following lines and save:

server {
	server_name <>;

	# Rewrite www to non-www
	rewrite ^(.*) http://<>$1 permanent;

server {
	# Listening ports
	listen 80;      ## listen for ipv4; this line is default and implied
	listen [::]:80; ## listen for ipv6

	# Make site accessible from domain
	server_name <>;

	# Root directory
	root /var/www/<>/public_html;
	index index.html index.htm index.php;

	# Logs
	access_log /var/www/<>/logs/access.log;
	error_log /var/www/<>/logs/error.log;

	# Includes
	include global/restrictions.conf;

Create global/restrictions.conf:

sudo mkdir /etc/nginx/global
sudo nano /etc/nginx/global/restrictions.conf

Add the following lines and save:

# Global restrictions configuration file.
# Designed to be included in any server {} block.&lt;/p&gt;
location = /favicon.ico {
	log_not_found off;
	access_log off;

location = /robots.txt {
	allow all;
	log_not_found off;
	access_log off;

# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~ /\. {
	deny all;

# Deny access to any files with a .php extension in the uploads directory
# Works in sub-directory installs and also in multisite network
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~* /(?:uploads|files)/.*\.php$ {
	deny all;

Create a symbolic link for your domain’s config file from “/sites-available” to “/sites-enabled”:

sudo ln -s /etc/nginx/sites-available/<> /etc/nginx/sites-enabled/<>

Reload php5-fpm and nginx:

sudo service php5-fpm reload
sudo service nginx reload

Installing Git and using GitHub

I had a hard time wrapping my head around Git for quite a while, but it eventually just “clicked” for me and now I am addicted. If you want to use Git on your server and host your domain’s code on GitHub, then follow these steps. This is a very basic workflow, but it satisfies my needs for the moment.

On your local machine(s)

First, download & install Git on your local machine(s). Then, sign up for GitHub, and create a new repository called “” with a default file included.

Set your default name and email:

git config --global "your github username"
git config --global ""

If you’re on OSX, cache your password so you don’t have to type it each time:

git config --global credential.helper osxkeychain

Create a folder on your local machine called, and clone your new GitHub repository into the empty folder you just created:

cd /path/to/
git clone<your github username>/<>.git .

Make some changes to the file, commit, and push to your GitHub repository to ensure everything is working properly:

git commit -am "My first commit!"
git remote add origin<your_username>/<>.git
git push origin master

View your repository on GitHub to make sure the changes were successfully committed and pushed from your local machine.

On the VPS

Download & install Git on your server:

sudo apt-get install git-core

Clone your repository from GitHub into your domain’s “public_html” directory (make sure this directory is empty beforehand — you may need to delete the index.php file from earlier if you created one), and pull in the most recent changes:

cd /var/www/<>/public_html/
git clone<your_username>/<>.git .
git pull

Your domain’s “public_html” directory on the VPS should now contain all the files from your GitHub repository. Whenever you want to update your live site with whatever changes you’ve pushed to your GitHub repository from your local machine, just do a git pull from the “public_html” directory for your domain on the VPS.


Phew. Exhausted yet? If you followed along, you should have a basic server ready to host your websites. Documenting this whole process helped me learn a alot about basic server configuration. Please post any questions and comments below.

Leave a Comment