Another Dimension

Xiangyu's personal blog.

Set up Ubuntu + PHP7 + Nginx + MySQL Stack

2014-02-09 tutorial xbu

This tutorial introduces how to set up a LEMP (Linux + Nginx + MySQL + PHP-FPM) stack on Ubuntu Server from scratch.

Update on Dec 24, 2015: added PHP7.0-FPM instructions.

Upgrade to Latest Ubuntu Distro (Optional)

If your server runs an older version of Ubuntu Server and you want to upgrade to the latest version, you will need to do the following with root permission:

# get root permission
sudo -s

# update package lists
apt-get update
apt-get dist-upgrade

# install update manager command-line tool
apt-get install update-manager-core

# start upgrade
do-release-upgrade -d

Check Basic Settings

Here are some basic configurations you need to do at the beginning.

Time and Timezone

# set up timezone
sudo dpkg-reconfigure tzdata

# synchronize time (requires ntpdate package)
sudo apt-get install -y ntpdate
sudo ntpdate

System Locale

If you don’t have Unicode as default locale, something could go wrong when like, you add PPAs / repositories or use some Python scripts. There are workarounds for that, but why not Unicode? So check the locale first. You may want to adjust the locale string to your owns.

# print locale information
locale -a

# generate English UTF-8 locale 
sudo locale-gen en_US.UTF-8
export LANG=en_US.UTF-8

# if other UTF-8 locales are available, simply switch to them.
# For example,
# export LANG=C.UTF-8

# set default locale to English (US)
touch /etc/default/locale
echo LANG="en_US.UTF-8" > /etc/default/locale
echo LANGUAGE="en_US:en" >> /etc/default/locale

This will fix errors like

UnicodeDecodeError: 'ascii' codec can't decode byte 0x* in position *: ordinal not in range(128)

Some Necessary Packages

Before proceeding, do a sudo apt-get update && sudo apt-get upgrade to upgrade all current packages to latest.

Then install the following packages so that you can add apt repositories (i.e., use add-apt-repository command later).

sudo apt-get install python-software-properties software-properties-common


This GPG error might happen if you are upgrading to Ubuntu 14.04 LTS:

W:GPG error: trusty-updates Release: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 40976EAF437D05B5 NO_PUBKEY 3B4FE6ACC0B21F32

To solve this simply import the public keys listed in the error prompt.

sudo apt-key adv --keyserver --recv-keys 40976EAF437D05B5
sudo apt-key adv --keyserver --recv-keys 3B4FE6ACC0B21F32

After importing the keys, rerun the apt-get command. If aptitude says it requires installation of “untrusted” packages, clean the cache as this topic says.

Setup LEMP Stack

Note that this section only addresses installation, not configuration. Here is a more comprehensive tutorial about setting up LEMP stack.

Install Latest Nginx

Ubuntu by default seldom ships the latest version of Nginx. To use the latest stable version, according to

sudo -s # Use root permission
add-apt-repository ppa:nginx/stable
apt-get update
apt-get install nginx-extras

Install Latest MySQL

The default version of MySQL on Ubuntu 14.04 LTS is 5.5, but MySQL 5.6 is actually available through universe channel:

sudo apt-get install mysql-client-5.6 mysql-server-5.6

It will prompt you to enter MySQL root password after installation.

Install Latest PHP-FPM

Note that add-apt-repository is broken on non-UTF-8 locales. For workaround, refer here.

If you want PHP 5 (5.6) only,

sudo add-apt-repository ppa:ondrej/php5-5.6
sudo apt-get install php5-common php5-fpm php5-dev php5-mysql php5-curl php5-geoip php5-gd php5-intl php-pear php5-imagick php5-imap php5-mcrypt php5-ming php5-ps php5-pspell php5-recode php5-snmp php5-sqlite php5-tidy php5-xmlrpc php5-xsl php5-apcu

If you want PHP 7 only,

sudo add-apt-repository ppa:ondrej/php-7.0
sudo apt-get install php7.0-fpm php7.0-mysql php7.0-json
# and more you may need

If you need PHP 5 and PHP 7 coexist,

sudo add-apt-repository ppa:ondrej/php
# and install packages

And for a performance boost, don’t forget to enable Zend OPcache and APCu in php.ini, which can be checked by inspecting the output of command php -v, and calibrate the time on the server. To enable OPcache and APCu, refer to >this article<. If higher performance is desired, one may prefer Facebook HipHop VM to official Zend PHP Engine.

Other Packages

Other packages that may be useful including

  • smtp server packages like ssmtp, if you need smtp on your server

  • access control package like acl, if you need more concrete permission control

Secure the Server

Secure PHP

Inside the effective php.ini on the host (For PHP5-FPM, normally at “/etc/php5/fpm/php.ini”), find the param cgi.fix_pathinfo and change its value to 0. This post discusses how an attacker can initiate an attack by exploiting this param.



ufw is the “firewall” shipped by Ubuntu. Here is a comprehensive tutorial:

Depending on the jobs of your server, you need to grant in / out access to different ports. Besides, be careful if your connection to the server depends on a port. Making changes effective before allowing that port connection may result in termination of your existing connection. In particular, if you use a non-default SSH port, be 100% sure to enable it before making ufw changes effective. (For how to change the SSH port, check the related section near the end of this post.)

For example, for a web server that also sends emails and accepts ssh connection, the following ports should be open:

# If you haven't installed ufw
sudo apt-get install ufw

# YOUR_CUSTOM_SSH_PORT is either "ssh" (alias for 22) or the port number.
sudo ufw allow YOUR_CUSTOM_SSH_PORT
sudo ufw allow http
sudo ufw allow https
sudo ufw allow smtp
sudo ufw allow out 53

Pay attention that ufw may disable outgoing ports which results in failures of commands like apt-get or git because they cannot connect to DNS server. Enabling outgoing traffic on port 53 should solve this.

Another issue is that ufw disables most outgoing traffic, including traffic between different ports of the same machine, making Wordpress plugins like Akismet and JetPack behave incorrectly.

To enable outgoing traffic,

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

To make sure ufw not disable the server itself:

sudo ufw allow in from YOUR_SERVER_IP_ADDR

Some OpenVZ-contained ipv6 hosts may have issues with ufw, here is an article discussing the solution.


To install ModSecurity on Nginx one needs to start from compiling the code as standalone module. The guides are available at

Before you start, beware that the compilation process may complain “configure: error: couldn’t find APXS” because it relies on this Apache module. I don’t want Apache at all, so I personally skip this step…

ModSecurity requires the following packages to compile:

sudo apt-get install libxml2 libxml2-dev libxml2-utils libaprutil1 libaprutil1-dev
sudo apt-get install libpcre-ocaml-dev autoconf make automake libtool
sudo apt-get install build-essential git libpcre3 libpcre3-dev libpcrecpp0 libssl-dev zlib1g-dev
# should be comprehensive enough

Nginx: HttpLimitReqModule

ngx_http_limit_req_module (HttpLimitReqModule) is a Nginx module that works similar to Apache’s ModEvasive. You can use it to limit the number of requests for a given session (per a defined key), thereby reducing the possibility of server overload or DDoS attacks. When the number of session requests exceeds the ceiling, a HTTP 503 error will be returned.

Step 1: Create a Limit zone

sudo vim /etc/nginx/nginx.conf

Inside the http container, add

limit_req_zone  $binary_remote_addr  zone=one:10m   rate=2r/s;

For example,

http {
    limit_req_zone  $binary_remote_addr  zone=one:10m   rate=2r/s;

where one is the name of the zone, and 10m means that the storage is 10MB in size. Each state marked by $binary_remote_addr takes 64B, so 10MB should be plenty for guests. The rate is set to be 2 requests per second; other units include r/m, etc.

Step 2: Make the Limit zone effective

Add the limit zone to the http {}, server {}, location {} containers where you want it to be effective:

limit_req zone=one burst=6;

In each second, the third (2+1, where 2 is the max number of requests in the specified time span, which in our example, is rate=2r/s) to sixth (because burst=6) requests, if any, will be queued, and if there are more than six requests, a HTTP 503 will be returned.

More details can be found at Nginx’s document page

Use a Non-Default Port of SSH Connection

To change the port number used by sshd,

sudo vim /etc/ssh/sshd_config

Find the line Port 22 and change the “22” to the port number you want to use.

You can also disable root login there.

Add Users and Set File Access Control Permissions

There is no universal command for this step, but the target is to isolate tasks to reduce the possibility of compromising.


Finally there is some housekeeping work.

sudo apt-get autoremove
sudo apt-get autoclean

That’s it!

More Readings

comments powered by Disqus
Theme by Lednerb