Set up Ubuntu + PHP7 + Nginx + MySQL Stack
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 ntp.ubuntu.com
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
GPG Error: NO_PUBKEY?
This GPG error might happen if you are upgrading to Ubuntu 14.04 LTS:
W:GPG error:http://archive.ubuntu.com 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 keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
sudo apt-key adv --keyserver keyserver.ubuntu.com --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 http://wiki.nginx.org/Install
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 serveraccess 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.
cgi.fix_pathinfo=0
ufw
ufw is the “firewall” shipped by Ubuntu. Here is a comprehensive tutorial: http://www.thefanclub.co.za/how-to/how-secure-ubuntu-1204-lts-server-part-1-basics
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.
ModSecurity
To install ModSecurity on Nginx one needs to start from compiling the code as standalone module. The guides are available at https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#Installation_for_NGINX.
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;
# YOUR ORIGINAL CONTENT
# FOLLOWS
}
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 http://nginx.org/en/docs/http/ngx_http_limit_req_module.html.
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.
Housekeeping
Finally there is some housekeeping work.
sudo apt-get autoremove
sudo apt-get autoclean
That’s it!