LEMP step by step : The ultimate guide to build a secure webserver

Updated on , by Jacky Thierry, in the category #Security & Adminsys

LAMP & LEMP installation serveur

About the author

Jacky THIERRY

CTO, Project Manager, Startup owner

Working since 15 years in IT, i managed various web projects for world wide companies, web agencies or local associations. I am specialized in agile projects, with outsourced teams around the world.

  • Jacky Thierry linkedin
  • Jacky Thierry twitter
  • Jacky Thierry instagram
  • Jacky Thierry RSS feed
Jacky Thierry

Discover the LEMP stack

1 – What is LEMP

LEMP is one of the most popular stack for running your website with the best performances and modern features. This is a pack of tools including a web server, a database, and a programming language, all of them running with any Linux distribution. LEMP is an acronym who means :

  • L : Linux – your servers will work with a Linux distribution.
  • E : EngineX, mostly called Nginx – webserver who will receive your visitors requests and redirect them to the website directory.
  • M : MariaDB or Mysql – database who will stores all the data (contents, users, configurations, medias..) you wanna use on your website.
  • P : PHP – development language you can use to code the mechanisms of your website.

2 – Why you should self host your webserver

Hosting your own server is usually a good idea for companies, system administrators and web developers. It brings you some advantages compared to hosting solutions who always have some limitations:

  1. You can install any component you need without being restrained by a white list
  2. You can fully optimize your services and change the system configuration according to your needs
  3. You can control the versioning and the updating of your packets
  4. You don’t share performance and bandwidth with other customers

Of course, it requires some technical skills to install, configure and assure the maintenance, it’s more work and time to provide. It’s mainly recommended for people who are ready to invest time and who have or want to acquire system administration knowledges.

3 – Other stacks for your server

LEMP is not the only stack able to host a website. There are many different stacks, according to your operating system and your components. They all include a webserver, a database and a programming language. Here a non exhaustive list on existing stacks :

  • LEMP / WEMP / MEMP : Nginx, Mysql or MariaDB, PHP for Linux, Windows or Mac operating systems.
  • LAMP / WAMP / MAMP : Apache, Mysql or MariaDB, PHP, for Linux, Windows and Mac operating systems.
  • MEAN : MongoDB, ExpressJS, AngularJS, NodeJS
  • RAMP : Ruby on rails, Apache, Mysql, Passenger

For choosing the right one for you, the operating system and the programming language are often the 2 requirements you have to consider first.

4 – LEMP vs LAMP

LEMP is a variation of LAMP, so choosing LEMP over LAMP is mainly choosing Nginx over Apache. Both are good and massively used as webservers, so both solutions are pretty good for your use, the choice is mainly over your personal preferences.

4.1 – Nginx vs Apache

Like Lighttpd before it, Nginx is a light and modern webserver, offering better performances and usually implementing new features faster than Apache.

It has been created to solve some issues from Apache, with different approaches : it possesses an asynchronous and non-blocking approach instead of process / thread-oriented approach in apache. So Nginx doesn’t create new process for each request and has better performances (specifically under heavy load). It also includes modules during compilation and possesses a PHP module in its core.

You can see this excellent comparison between Nginx and Apache to know more about technical differences, but we can conclude :

  1. Nginx is newest and faster than apache in bulk version. It works better in stressed environments with heavy loads without any caching modules or optimizations.
  2. Nginx has less options than Apache, but is enough for most people and usage. It was built with the philosophy “do less but do better”.
  3. Due to its lightweight design, Nginx is easier to configure in a static webserver but more difficult in a complex environment.

From my personal opinion, Nginx should be a better choice due to its design and performances, except if you have deep knowledge of Apache or very specific needs.

4.2 – MariaDB vs Mysql

Both MariaDB and Mysql are used in LEMP and LAMP stack. MariaDB is a fork of Mysql, created by Michal Widenius, also creator of Mysql, when Oracle bought Mysql. The community at this time was very worried that Oracle could change the open licence of Mysql and created a fork to ensure the database software would still remains open and free. MariaDB is under the GNU General Public License 2.

MariaDB is fully compatible with Mysql, that means, you will use it the exact same way than Mysql, all commands and options are identical, except they are named mariadb instal of mysql.

5 – LEMP market share

Here some facts to show the use and progression of the LEMP stack :

  1. Nginx now holds more than 35% of the webserver market share.
  2. Apache holds less than 50% of the production websites.
  3. During the last 5 years, the servers in production using Nginx increase by more than 20%. Apache is used by 20% less servers than 5 years ago.
  4. PHP runs 83% of the world wide websites, growing by 6% since 5 years.
  5. PHP7 has been released in december 2015, and is now used by 10% of webservers running PHP

Sources : Nginx market share evolution, usage of PHP as backend language, usage of PHP7 vs PHP5.

Install LEMP

Before starting your installations, you need to update your server :
apt update && apt dist-upgrade

1 – Install Nginx

We will take Nginx from the official repository, and then add these values in the http declaration in the main configuration file.

apt install nginx

vi /etc/nginx/nginx.conf

client_body_buffer_size 10K;
client_header_buffer_size 1k;
client_max_body_size 8m;
large_client_header_buffers 4 16k;
fastcgi_buffers 16 16k; 
fastcgi_buffer_size 32k;

We can now restart the service to apply our changes :

service nginx restart

2 – Install PHP7

PHP7.0 is the default version in the debian repository. If you want a more updated version of PHP (PHP7.2 at the time i’m writing this article), you can use the repository from Ondrej Sury, with the last version of PHP for debian and ubuntu.

apt install php7.0-fpm php7.0-mysql php7.0-common php7.0-gd php7.0-json php7.0-cli php7.0-curl php7.0-xml php7.0-zip php7.0-mbstring

Once installed, you can configure theses values to optimize your installation :

vi /etc/php/7.0/fpm/pool.d/www.conf

pm.max_children = 10
pm.max_requests = 200

vi /etc/php/7.0/fpm/php.ini

date.timezone = America/Cayenne
upload_max_filesize = 8M
max_execution_time = 60
max_input_vars = 5000

You can now restart the service to apply theses changes :

service php7.0-fpm restart

3 – Install MariaDB

Now, we can install MariaDB. I choose here to use MariaDB instead of Mysql due to his licence type, but MariaDB being a fork of Mysql, the both works the same way, you just have to change “mariadb-server” by “mysql-server” during the installation to have the second one.

apt install mariadb-server

By default, MariaDB isn’t secured and contains a base with testing data. We can change that with the following script, installed with the principal package. Be sure to always run this script after any mariaDB installation.

mysql_secure_installation

NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
SERVERS IN PRODUCTION USE! PLEASE READ EACH STEP CAREFULLY!
In order to log into MariaDB to secure it, we'll need the current
password for the root user. If you've just installed MariaDB, and
you haven't set the root password yet, the password will be blank,
so you should just press enter here.
Enter current password for root (enter for none): Type enter
OK, successfully used password, moving on...
Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.
Set root password? [Y/n] y
New password: Your password (at least 12 caracteres with letters and special caracteres)
Re-enter new password: Your password (at least 12 caracteres with letters and special caracteres)
Password updated successfully!
Reloading privilege tables..
... Success!
By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them. This is intended only for testing, and to make the installation
go a bit smoother. You should remove them before moving into a
production environment.
Remove anonymous users? [Y/n] Y
... Success!
Normally, root should only be allowed to connect from 'localhost'. This
ensures that someone cannot guess at the root password from the network.
Disallow root login remotely? [Y/n] Y
... Success!
By default, MariaDB comes with a database named 'test' that anyone can
access. This is also intended only for testing, and should be removed
before moving into a production environment.
Remove test database and access to it? [Y/n] Y
- Dropping test database...
... Success!
- Removing privileges on test database...
... Success!
Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.
Reload privilege tables now? [Y/n] Y
... Success!
Cleaning up...
All done! If you've completed all of the above steps, your MariaDB
installation should now be secure.
Thanks for using MariaDB!

You can add theses values to the default configuration, for increasing the cache and the size of InnoDB

vi /etc/mysql/mariadb.conf.d/50-server.cnf

[mysqld]
query_cache_limit       = 2M
query_cache_size        = 32M
innodb_buffer_pool_instances = 1
innodb_buffer_pool_size = 79M
[mariadb]
aria_pagecache_buffer_size = 2M

And now, as usual after any change, restart the service :

service mariadb restart

Configure your website in LEMP

1 – Create your log directory

Finally, you just have to create the folder who will contain the logs and attribute it to the user www-data :

mkdir /var/log/nginx/website/

chown -R www-data /var/log/nginx/website/

2 – Copy your website on your server

If you already have a website to host, a CMS to install or a framework to use, you can put it in the folder /var/www/website/

Otherwise, we’ll create in this article a single file who will show you all the configurations and options activated on your LEMP server :

mkdir /var/www/website

vi /var/www/website/index.php

<?php phpinfo(); ?>

chown www-data -R /var/www/website/

Don’t forget to delete this file after your test, it gives many usefull and private informations on your system and should never be accessible from internet for obvious security reasons.

3 – Create a vhost

If you choose to run a CMS on your new LEMP stack, you can see this article to easily install WordPress in 3 steps.

First, you need to deactivate the default website in Nginx :

unlink /etc/nginx/sites-enabled/default

Then, you can create a simple vhost in Nginx to display your website :

vi /etc/nginx/sites-available/www.website.com.conf

server {
server_name www.website.com;
listen 80;
port_in_redirect off;
access_log /var/log/nginx/website/access.log;
error_log /var/log/nginx/website/error.log error;

root /var/www/website/;
index index.php;

location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}

Finally, activate this virtualhost and restart Nginx :

ln -s /etc/nginx/sites-available/www.website.com.conf /etc/nginx/sites-enabled/www.website.com.conf

service nginx restart

4 – Test your website

Your website is now available, you can type the adresse in your browser, to see the result of the command phpinfo you put in your index.php, with all your servers informations.

Do not forget to remove this file after your test !

Add SSL to Nginx

1 – What is SSL

SSL is a way to secure the connexions from your customer’s computer to your webserver. it’s composed of a public certificate and a private key.

You can learn everything to know about SSL certificates in my beginner’s guide.

2 – Create certificates

We will use Let’s encrypt in this article. If you don’t know how to use it, i advice you to read my ultimate guide about Let’s Encrypt.

apt install git

git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt --depth=1

cd /opt/letsencrypt/

service nginx stop

/opt/letsencrypt/letsencrypt-auto certonly --rsa-key-size 4096 --standalone -d www.website.com

service nginx start

3 – Strenghten SSL with Diffie-Hellman

Diffie-Hellman is a tool allowing you to strengthen your private key. The goal is to harden the decypher of your SSL transactions in the case an attacker put his hands on your private key. We’ll add a 4096 bits encryption. Be patient, it will take few minutes.

openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

4 – Modify your vhost

Now you generated your SSL files, it’s time to implement them in your Nginx virtualhost.

4.1 Add SSL certificates

vi /etc/nginx/sites-available/www.website.com.conf

ssl_certificate /etc/letsencrypt/live/www.website.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.website.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/www.website.com/chain.pem;

4.2 Add Diffie-Hellman key

vi /etc/nginx/sites-available/www.website.com.conf

ssl_dhparam /etc/ssl/certs/dhparam.pem;

4.3 Specify your protocols and cyphers

I advice you to use only the last version of TLS, and strong ciphers :

vi /etc/nginx/sites-available/www.website.com.conf

ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers On;

4.4 OCSP Stapling

OCSP Stapling allows to put in cache the validity of a certificate. Usually, for each request, a connexion is made to an authority to ensure the certificate is still valid in time. Theses are 2 majors issues, it burdens the load of authorities servers and more important, it informs the authority on which website you visit. OCSP stapling allows to put the validity in cache during the SSL transactions, so no checking to an authority is needed.

vi /etc/nginx/sites-available/www.website.com.conf

ssl_stapling on;
ssl_stapling_verify on;

4.5 Redirect your traffic to https with 301 redirections

Now you have a ssl website, you want to make sure everyone use this version. Redirect all traffic from http to htpps :

vi /etc/nginx/sites-available/www.website.com.conf

server {
 listen 80;
 server_name www.website.com;
 return 301 https://www.website.com$request_uri;
}

4.6 Complete virtualhost configuration for SSL

vi /etc/nginx/sites-available/www.website.com.conf

server {
  server_name www.website.com;
  listen 443 ssl;
  ssl_protocols TLSv1.2;
  ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
  ssl_prefer_server_ciphers On;
  ssl_certificate /etc/letsencrypt/live/www.website.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.website.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/www.website.com/chain.pem;
  ssl_session_cache shared:SSL:128m;
  ssl_dhparam /etc/ssl/certs/dhparam.pem;
  ssl_stapling on;
  ssl_stapling_verify on;
  port_in_redirect off;

  access_log /var/log/nginx/website/access.log;
  error_log /var/log/nginx/website/error.log error;

  root /var/www/website/;
  index index.php;

  location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    fastcgi_split_path_info ^(.+\.php)(.*)$;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
 }

server {
 listen 80;
 server_name www.website.com;
 return 301 https://www.website.com$request_uri;
}

Improve security

1 – Create new MariaDB users

A best practice in using databases is to make a new user for each application who will connect to a DB, with permissions restrained to the base according to the needs. You should never use an administrator account in your aplications, for obvious security reasons. If your application is compromised, all your databases would be to.

  1. Create a new user
  2. Create a new database
  3. Grant permissions

mysql -u root -p

CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
CREATE DATABASE newbase;
GRANT ALL PRIVILEGES ON newbase.* TO 'newuser'@'localhost';
FLUSH PRIVILEGES;
QUIT;

2 – Define files and folders permissions

You should create a specific user in your linux system to upload or edit your files. A best practice is to create a new user for each website and give him permissions only on the website folder, so even if this user is compromised, an attacker could not see your system or other websites.

This user can have full access to your folder, and the user used by Nginx (www-data) should have only read and access permissions :

adduser sysuser

chown sysuser:www-data -R /var/www/website/

chmod -R 750 -R /var/www/website/

3 – Change Nginx server signature

The server signature of Nginx is a header indicating the version of the webserver. It is configured by default so, anyone can read the header and knows you’re running Nginx, with the exact version. It is of course an information you don’t want to to share.

To use the option “more_set_headers” and hide Nginx from the response, you will need the nginx-extras version, instead of the nginx-full version who was installed earlier :

apt install -y nginx-extras

vi /etc/nginx/nginx.conf

more_set_headers "Server: Webserver";
server_tokens off;

4 – Add security headers in Nginx

4.1 Strict-Transport-Security

HSTS (HTTP Strict Transport Security) is a mechanism that indicates to all browsers to use HTTPS for their connexions to your server. It also indicates a time period in which all request who will not use HTTPS will be refused.

This header prevents protocol downgrade attacks and cookie hijacking.

vi /etc/nginx/sites-available/www.website.com.conf

add_header Strict-Transport-Security "max-age=31557600; includeSubDomains";

4.2 Content-Security-Policy

CSP (Content Security Policy) allows you to define the origin of the resources to load of your website.

This header prevents cross-site-scripting (XSS), clickjacking and some code injection attacks.

vi /etc/nginx/sites-available/www.website.com.conf

add_header Content-Security-Policy "default-src 'self' https: data: 'unsafe-inline';";

4.3 X-XSS-Protection

XSS (Cross Site Scripting) filter is useful only for IE8. It forces the XSS protection feature of the browser.

This header prevents cross-site-scripting (XSS).

vi /etc/nginx/sites-available/www.website.com.conf

add_header X-Xss-Protection "1";

4.4 X-Frame-Options

This header indicates if you allow browsers to render your pages in a frame, iframe or object html tag.

This header prevents clickjacking, and embedding your content in other websites.

vi /etc/nginx/sites-available/www.website.com.conf

add_header X-Frame-Options "SAMEORIGIN" always;

4.5 X-Content-Type-Options

Forces browsers to use the MIME type of your resources without interpreting it.

This header prevents MIME type sniffing.

vi /etc/nginx/sites-available/www.website.com.conf

add_header X-Content-Type-Options "nosniff" always;

4.6 Referrer-Policy

This header indicates the policy you want to set concerning your website as a referrer. It allows you to specify if you want the websites you are sending your visitor to with external links to know if you’re the website of origin.

vi /etc/nginx/sites-available/www.website.com.conf

add_header Referrer-Policy strict-origin-when-cross-origin;

5 – Phpmyadmin and DB web administrators

I strongly advise you to never install phpmyadmin on your server, as it creates a direct access to your databases, who could be used for malicious attempts to break your server.

However, if you need a graphic administration tool for your DB, follow this steps :

  1. Prefer to use a server-less software like Adminer. You will have more features available and it doesn’t require to be on your server to work (you can launch it from your client computer).
  2. In case you absolutely needs phpmyadmin, install it on another server with another domain name.
  3. Change the url to access it, like /pma/ instad of the well know /phpmyadmin/

Improve performance

1 – Configure HTTP2

1.1 What is HTTP2

HTTP2 is the evolution of the famous network protocol HTTP (HyperText Transfert Protocol). It was released in 2015, 18 years after the last update (HTTP 1.1 in 1997) by the organization IETF (Internet Engineering Task Force). It is a derived version of the protocol SPDY, who was developped by Google fews years before.

It brings new features, for improving performance and security, such as :

  1. Compression of HTTP headers
  2. Pipeling of request
  3. Multiplexing request over a single connexion
  4. Push server
  5. Requirement of TLS 1.2 (the protocol itself doesn’t require TLS, but most web browsers only support HTPP2 over TLS).

1.2 How to install HTTP2 in Nginx

  1. First, you have to ensure to activate SSL with protocol TLS 1.2 on your website (see the previous chapter)
  2. Then, you can edit your virtualhost and change the listening argument, by adding http2
  3. Finally, restart your webserver

vi /etc/nginx/sites-available/www.website.com.conf

listen 443 ssl http2;

2 – Activate Gzip compression

2.1 What is GZIP compression

GZIP is a module who will compress the data sent by the webserver to the web browser. Instead of sending plain text, which could have big size, it compresses the file to reduce the size, and decrease the transfert time. The web browser will receive the file and uncompress it, to make the whole process of transfert faster.

2.2 How to configure Nginx for GZIP compression

vi /etc/nginx/sites-available/www.website.com.conf

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_min_length 265;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

3 – Add caching to your resources

3.1 What is resources caching

Caching your resources allow the web browser to not request for elements he already has in memory. All pages of your website have some elements in commun (like your logo, your fonts, your css/js…). With this option, you indicate to the browser to not try to download theses resources for each page, but to use the ones he already download from a previous loading. It can considerably decrease the rendering time of your pages.

3.2 How to cache your resources in Nginx

vi /etc/nginx/sites-available/www.website.com.conf

location ~*.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|cur)$ {
  expires max;
  log_not_found off;
  access_log off;
}

4 – Complete virtualhost

vi /etc/nginx/sites-availables/website

server {
  server_name www.website.com;
  listen 443 ssl http2;
  ssl_protocols TLSv1.2;
  ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-$
  ssl_prefer_server_ciphers On;
  ssl_certificate /etc/letsencrypt/live/www.website.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.website.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/www.website.com/chain.pem;
  ssl_session_cache shared:SSL:128m;
  ssl_dhparam /etc/ssl/certs/dhparam.pem;
  ssl_stapling on;
  ssl_stapling_verify on;
  port_in_redirect off;

  add_header Strict-Transport-Security "max-age=31557600; includeSubDomains";
  add_header Content-Security-Policy "default-src 'self' https: data: 'unsafe-inline';";
  add_header X-Xss-Protection "1";
  add_header X-Frame-Options "SAMEORIGIN" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header Referrer-Policy strict-origin-when-cross-origin;

  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_min_length 265;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  access_log /var/log/nginx/website/access.log;
  error_log /var/log/nginx/website/error.log error;

  root /var/www/website/;
  index index.php;

  location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    fastcgi_split_path_info ^(.+\.php)(.*)$;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }

  location ~*.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|cur)$ {
    expires max;
    log_not_found off;
    access_log off;
  }

}

server {
 listen 80;
 server_name www.website.com;
 return 301 https://www.website.com$request_uri;
}