LEMP Stack Tutorial: Ubuntu + Nginx + MySQL + PHP-FPM Servers Are Impressively Simple Yet Powerful

By    |  July 16, 2014

If you’re a web developer of any sort, you’ve probably been overwhelmed to the point of frustration at least once during the past few years as more and more options continue to appear in the realm of web hosting and server configuration.

It was only a few years ago that practically the entire world was using the same Apache web server (with cPanel installed), and things like CDNs and DDOS attacks were not commonly discussed. But as web technology continues to grow at an exponential rate, independent designers and bloggers face growing learning curves in the world of web servers, as performance and scalability become evermore crucial in the face of fierce online competition for users’ respect.

In this tutorial, I will be showing you how to install a LEMP server stack from scratch. Although Apache (LAMP) servers will probably never die out – at least not for a while – the platform continues to lose ground to other players. In particular, many corporate + enterprise networks continue to choose Microsoft servers, and Nginx continues to grow rapidly among high traffic content portals and other websites.

How To Setup & Configure A LEMP Server

1. Choose A Linux Flavor. The first letter in L.E.M.P. stands for “Linux” – so before we begin configuring anything, we first must choose and install a linux distro. As this tutorial does NOT install any control panels such as cPanel/WHM, I highly recommend Ubuntu* as it is arguably the easiest distro to manage via shell (command line). Ubuntu, maintained by London-based Canonical Ltd., is renowned for its exceptional repositories and on-time release schedule, making server software updates impressively easy.

*Always use an LTS (Long Term Support) release when installing Ubuntu to a server for added stability and security. The most recent Ubuntu LTS version is Ubuntu 14.04

2. Root Login & Password. After you’ve installed Ubuntu to your server, login to your machine’s IP address via SSH using the root username (Note: if you are using Windows, connect to your server using a free SSH client such as Putty):

ssh root@

If you are logging in for the very first time, you’ll see a security message something like the one below. Simply type “yes” and hit enter to continue logging in:

The authenticity of host ' ('
can't be established. ECDSA key fingerpring is
12:34:45:1a:aa:12:33:4c:87:65:43:21:bb:3c:fa:c0. Are you sure you want
to continue connecting (yes/no)?

Depending on how you installed Ubuntu – i.e. if you installed it through your web hosting company’s automated server “imaging” interface – be sure to reset the default root password to something unique and secure if you haven’t done so already:

sudo passwd

3. Add New User. After you’ve secured your root password, its time to create a new user with root privileges so that we no longer need to use the root user on our server. The prompt will ask you for things like “name” and “phone number” etc for your new user – simply skip through all of these fields unless you have a use for them:

sudo adduser example

Next, we need to add “sudo” privileges to our new user, otherwise known as root privileges – this will allow our new user to do anything that root can do, as long as we prefix commands with “sudo” and input our password. The reason we don’t want to use root directly is to avoid accidentally inputting destructive commands – plus, we will later disable root SSH logins, creating additional security for our server.

sudo visudo

Inputting the above command will let you edit the configuration file as below. After you’re done editing, hit CONTROL + X to exit and type “Y” to confirm saving the changes:

# User privilege specification
root    ALL=(ALL:ALL) ALL
example    ALL=(ALL:ALL) ALL

4. SSH Hardening. Now that you’ve setup a new user with root privileges, we can lock down SSH a bit which will greatly improve our server’s security:

sudo nano /etc/ssh/sshd_config

The above command will bring you to another configuration file. Change the default port number for SSH to something between 1025 and 65536 to avoid common SSH attacks aimed at port 22. Although not foolproof, changing the port number adds an additional security layer against would-be attackers. After you’ve changed it, make sure you write it down or remember it, otherwise you’ won’t be able to connect to your server:

Port 55555

Next, disable SSH logins from user root to further protect your server:

PermitRootLogin no

Lastly, specify which users are allowed to login via SSH (WARNING: Check your spelling carefully! If you make a mistake on this step, you may lock yourself out!):

AllowUsers example

Once again hit CONTROL + X to exit and type “Y” to save changes. Now, restart SSH:

service ssh restart

5. Install Nginx Server. Here’s where you start feeling happy that you chose Ubuntu, because all the software we will be installing is conveniently available in Ubuntu’s default repositories. So, we can use the Ubuntu “shortcut” command called apt:

sudo apt-get update
sudo apt-get install nginx

On all new 14.04+ versions of Ubuntu, Nginx will automatically run after installation is complete. You are literally done installing Nginx with a single command.

6. MySQL Installation. You are probably aware that MySQL is the most popular database software in the world. In recent years, other software such as MongoDB has become popular among developers because of its speed and simplicity. However, the newest 5.6 version of MySQL has drastically improved performance compared with the older version 5.5 (its also important to note that newest MySQL version (5.6+) defaults to innoDB tables, which provides automatic table repair and optimization… make sure your database is compatible). Unless you are an advanced developer, stick with MySQL:

sudo apt-get install mysql-server-5.6

During setup, you will need to set a “root” MySQL password. Don’t be confused; this is not related to the SSH password for user root but rather like a “superadmin” password for MySQL that uses the username root to connect to MySQL.

Note: In pre-5.6 versions of MySQL the sudo mysql_install_db command is required to properly finish setting up MySQL. However after version 5.6 the command is no longer needed and should be skipped to avoid creating server errors.

Next, run the below security script to remove insecure MySQL permissions:

sudo mysql_secure_installation

Type “Y” for all of the options, which will greatly improve MySQL security:

Remove anonymous users? [Y/n] Y
Disallow root login remotely? [Y/n] Y
Remove test database and access to it? [Y/n] Y
Reload privilege tables now? [Y/n] Y

7. PHP-FPM Installation. So now we’ve got Nginx to serve our website files, and MySQL to store all of our data. But we need something to connect the dots and to generate dynamic content on the fly. Enter the magical world of PHP – specifically, PHP-FPM (fastCGI process manager) which allows Nginx to process PHP requests by passing them through FCGI. Lucky for us, this has added loading speed benefits:

sudo apt-get install php5-fpm php5-mysql

8. PHP Hardening. There’s a default setting when PHP is first installed that we must change in order to secure our server. First, enter the command below:

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

Uncomment (remove the # symbol) and change the following line as below:

post_max_size = 64M
upload_max_filesize = 64M

Performing this step will keep PHP from attempting to execute “nearest match” files when a path or file is missing. This prevents hackers from crafting malicious PHP requests in order to execute files they shouldn’t be allowed to access. Now, restart PHP:

sudo service php5-fpm restart

9. Configure Nginx Server Block. All of our server components are installed by now – however, Nginx still hasn’t been told to “handle” all of our dynamic content via PHP-FPM. For that, we use a “server block” which is similar to Apache virtual hosts:

sudo nano /etc/nginx/sites-available/default

I won’t spend time explaining all the various options and settings for Nginx server blocks. The below example is optimized for websites using WordPress as their CMS, and it provides both security and speed benefits:

Below server block code last updated 12 March, 2015

server {
        listen 80 default_server;
        # keep this commented unless ipv6 is being used
        # listen [::]:80 default_server ipv6only=on;
        root /home/example/www;
        index index.php index.html index.htm;
        server_name example.com www.example.com;
        # first try files, then directories, otherwise query index
        location / {
		try_files $uri $uri/ /index.php?$args;
        # make sure PHP loads via FCGI for better performance
        location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass unix:/var/run/php5-fpm.sock;
                fastcgi_index index.php;
                include fastcgi_params;
        # no need to log any access requests for favicon
        location = /favicon.ico {
                log_not_found off;
                access_log off;
        # don't log robots file and allow any requests
        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        # block any attempted access to dot files
        location ~ /\. {
                deny all;
                log_not_found off;
                access_log off;
	# comment this until WP is properly setup (blocks access)
	# location ~* wp-config.php {
	#	deny all;
	# }
        # block any attempted manipulation of uploads
        location ~* /(?:uploads|files)/.*\.php$ {
                deny all;
        # avoid any font problems in Firefox and IE
	location ~ \.(eot|ttf|woff|svg|css)$ {
	    add_header Access-Control-Allow-Origin "*";
        # set maximum expiry times for static files
 	location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
                expires max;
                log_not_found off;
                access_log off;
        # define error pages in the web directory
        error_page 500 502 503 504 /50x.html;
                location = /50x.html {
                        root /usr/share/nginx/html;

The server block example above also specifies expires max on static files for improved cache control. It also properly configures @font-face directives to avoid display errors with Firefox/IE browsers. In the above example, /var/www is the default “web root” directory which is recommended for most websites. Now, restart Nginx one more time:

sudo service nginx restart

10. Setup MySQL Database. Even if you are migrating from another server, its usually easiest to simply setup a new MySQL database and later import your data into it:

sudo mysql -u root -p
CREATE DATABASE newdatabase;
CREATE USER newuser@localhost IDENTIFIED BY 'newpassword';
GRANT ALL PRIVILEGES ON newdatabase.* TO newuser@localhost;

11. Migrate Server Data. If you are migrating your data from another server, simply use mysqldump to dump your old database into a single .sql file:

sudo mysqldump -u root -p [database name] > dump.sql

And use the powerful tar command to bundle all files in your old web directory:

sudo tar -czf migrate.tar.gz *

Use encrypted SCP to securely send your database and tarball to your new server:

sudo scp dump.sql user@
sudo scp migrate.tar.gz user@

On your new server, go to your web root i.e. /var/www and unzip the tarball:

sudo tar -xzf migrate.tar.gz

Now, restore your innoDB MySQL database to your freshly installed MySQL 5.6:

sudo mysql -u root -p newdatabase < /path/to/dump.sql

12. Cleanup & Finish. We are nearly finished, but there are a few things we need to double check. First of all, make sure to configure your wp-config.php or other database configuration file depending on the CMS you are using. Also, let's make sure that Nginx owns the web root so that your CMS can update and edit files properly, and lastly, fix up any potential file + directory permission errors:

chown root:root /home/example
chmod 755 /home/example
sudo mkdir /home/example/www
sudo addgroup wordpress
sudo adduser example wordpress
sudo adduser www-data wordpress
sudo chown -R example:wordpress /home/example/www
sudo find /home/example/www/ -type d -exec chmod 775 {} \;
sudo find /home/example/www/ -type f -exec chmod 664 {} \;
sudo chmod 660 /home/example/www/wp-config.php
sudo chmod -R g+s /home/example/www/
sudo service nginx restart

If you want to lock down your SFTP users and block SSH, implement this under /etc/ssh/sshd_config at the very bottom of the file (seriously, the very bottom)

# Subsystem sftp /usr/lib/openssh/sftp-server
Subsystem sftp internal-sftp
Match User stupid
ChrootDirectory /home/stupid
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no

For good measure, add this to wp-config.php:

/* make sure WordPress has enough memory */
define('WP_MEMORY_LIMIT', '64M');
/* make sure WordPress admin has enough memory */
define( 'WP_MAX_MEMORY_LIMIT', '64M' );
/* avoid annoying FTP prompt screens on updates/installs */
define( 'FS_METHOD', 'direct' );

Finally, enable all the gzip options on Nginx to compress your files and to speed up rendering times. On the newest versions of Nginx, the default options are usually fine:

sudo nano /etc/nginx/nginx.conf

Simply uncomment all of the default gzip options in order to activate them (below). And, like always, hit CONTROL + X to exit the nano editor and type "Y" to save changes:

# Basic Settings

# allows file uploads up to 500 megabytes
client_max_body_size 500M;

# Gzip Settings

gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

CONGRATULATIONS! We're done. From here, you can install your favorites firewall(s) if you wish, such as fail2ban etc. Whenever possible, avoid installing an FTP server, email server, name server, or control panel to greatly speed up your server and avoid quite a few inherent security risks that accompany these unnecessary applications.

sudo service nginx restart

Questions? Comments? Please leave your thoughtful feedback down below or consider following Jesse Nickles on Google+ to stay updated or to get in touch!

Possibly Related Stories:
Page ID #55112  -  Last updated on

Leave A Comment, Free Speech Is Welcome!

One Comment on “LEMP Stack Tutorial: Ubuntu + Nginx + MySQL + PHP-FPM Servers Are Impressively Simple Yet Powerful”  (RSS)

Please scroll up to leave a comment.