How to secure your LEMP stack

LEMP, it stands for Linux, (EngineX) NGINX, MariaDB (or MySQL) and PHP. Due to its flexibility and simplicity, NGINX slowly takes over the Internet.

In this tutorial, we will attempt, through examples of bad and good practices, to go through the steps of properly securing your Linux web server.

So what is the term Security? Often you can hear the IT Engineers saying “Our network is secure” or “Our servers are secure” however, those sentences although widely used are technically not correct, as in many books and publications you can find that Security as a term is not a static value, but rather a degree.

From Wikipedia the definition is:

“Security is the degree of resistance to, or protection from harm. It applies to any vulnerable and/or valuable asset, such as a person, dwelling, community, item, nation, or organization.”

In terms of servers or applications, one should always be aware that the more secure their server or application is, the less accessible it becomes (it is harder to be accessed). Of course, the best example for this is the proverb:

“The most secure server is the one that is switched off.”.

As ridiculous as it may sound, this is occasionally practiced by some organizations today, where their most secure servers are kept offline and/or in totally closed networks and are powered on only when they need to be.

Intended audience

Before going any further we must point out that this guide is aimed at intermediate users with some knowledge of installing and configuring the LEMP stack. We will assume that you (the reader) already have the relevant knowledge for handling a Linux operating system. This guide is not for beginners. Many other related topics are connected to this guide and things like installing the LEMP stack, installing SSL certificates, configuring public key authentication and many other are out of the scope of this tutorial. However, you can get a VPS from us and we’ll do all of this (and more!) for you, for free. So you don’t really need any Linux administrating skills if you get a Managed VPS from us.


  • We will use one of our CentOS 7 VPS, but the instructions are similar for other distros too, like Ubuntu;
  • Intermediate knowledge for administering Linux operating systems;
  • Basic networking knowledge. TCP/IP, Protocols, Ports;
  • Knowledge for creating basic NGINX configurations;
  • MySQL databases know-how;
  • PHP know-how;

Securing Linux itself

The Linux by nature is a very secure operating system. The viruses, for example, can not, or rather can rarely harm the OS.

Despite this, there are a number precautions that should be considered that will help for one to have an even more secure system.

Always have the latest software and security updates

This already assumed, however, we believe it has to be mentioned for the sake of completeness of this guide. No matter what you do, if your system is not up to date, the present and old vulnerabilities will be exploited and your server will eventually be compromised, not by people, but by automated bots that scan for these vulnerabilities. The hacker will not even realize that your server got hacked, his script will do that for him. You should always keep your system updated. This stands for any software and application that is on your server, and any website (CMS) running on it. If you have WordPress running as a website it must be updated as soon as a new version is released.

Securing the remote login

  1. The obvious – The root password should be a very long and strong combination of letters, numbers, and symbols.
  2. Change the default SSH port (22) to a random port.
  3. Remote root logins should be disallowed.
  4. A public key authentication mechanism instead of password logins should be used.
  5. While doing the above standard Linux user accounts should be used. These users should be given the ability to escalate root privileges.

For example:

We will set our root password to something like `BvP7mW#zX9rwK!PSA^jk`

If it is a basic Linux (CentOS) installation install sudo first.

# yum install sudo

Create a standard user:

# useradd -d /home/adminjohn adminjohn
# passwd adminjohn

Grant admin john the ability to escalate root privileges:

# visudo

At the end of the file find the part where it says:

## Allows people in group wheel to run all commands
%wheel  ALL=(ALL)   ALL

And make sure that the line is uncommented.

Now add our adminjohn as a member of the group wheel.

# usermod -a -G wheel adminjohn

Edit the /etc/ssh/sshd_config and set the following:

Note: While doing this be very careful as you may easily lock yourself out of your server. Always make sure that you have console access or two or more root sessions open so that you can test and revert back the changes.

# vim /etc/ssh/sshd_config

Port 29862
PermitRootLogin no

With these settings in place, remote root logins are forbidden, however, our user adminjohn will be able to log in via ssh and then he can switch to root using the commands sudo su -  if necessary.

This system can be further improved by implementing a public key authentication. This, however, is more complex and it is out of the scope of the guide.

Linux firewall

Most Linux systems today make use of either IPTables or Firewalld as their firewall. The key concept here to understand is that as a rule of thumb, if we want our firewall to be effective then we must implement a so-called deny all with exceptions policy.

For example: If your server serves only a WordPress website, then any firewall in place should allow only access to ports 80 (HTTP), 443 (HTTPS) and 22 (SSH) or whatever other port you decide to be. In our example, we should allow access to 80, 443 and 29862. We can, however, further extend our use and also open up the port 25 if we want to send emails from our site. If we are not sure, we can always make use of the command netstat -tulnp in order to check all of the active ports on our server and decide what should and shouldn’t be used.

Disabling unused services

To go one step further, on any Linux server one must always disable any unused services/applications as this will greatly limit the attack surface of the server. The more services you add, the more you increase the attack surface of the server, and the more susceptible to attack it becomes. Modern Linux implementations like CentOS 7 and Ubuntu 16 use systemd as their init daemon. Services that need access to the network can easily be identified with the netstat -tulnp command.

In the example below, we can see that our system (CentOS 7) runs dovecot, postfix (master), sshd, vsftpd and nginx.

# netstat -tulnp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0* LISTEN 444/dovecot
tcp 0 0* LISTEN 444/dovecot
tcp 0 0* LISTEN 442/master
tcp 0 0* LISTEN 30430/sshd
tcp 0 0* LISTEN 444/dovecot
tcp 0 0* LISTEN 444/dovecot
tcp 0 0* LISTEN 241/nginx: master p
tcp 0 0* LISTEN 119/vsftpd
tcp 0 0* LISTEN 442/master

If we decide that we don’t need dovecot it can be disabled using:

## Be carefull not to disable anything essential
systemctl stop dovecot
systemctl disable dovecot

The latter disables dovecot from starting the next time our system is rebooted.

Note: Older systems use older init daemons, thus on these systems the service management commands are different. Ubuntu 14, for example, uses upstart, and CentOS 6 uses sysvinit. If you wish to learn how you can disable/enable services on these systems, you should consult their documentation.

Securing NGINX

NGINX should run as a non-root user

The default NGINX installation sets its process to be run by either nginx or www-data as its user. If by any chance the server runs as root then it can be changed by setting the appropriate directive in the /etc/nginx/nginx.conf file.

user nginx;

Keep in mind that if the user is set as root and you change it, that might break your website, as it may require to further adjust the web root permission.

Normal web root permissions are 755 for the directories and 644 for the files.

Hide Nginx version number

Version numbers are often used by the hackers to exploit already present vulnerabilities.

Put this in the http block in /etc/nginx/nginx.conf:

# vim /etc/nginx/nginx.conf
server_tokens off;

Force using HTTPS

If you are aiming for a highly secure site you should always standardize URL names and force your users to use HTTPS. Usually the developers will put headers in their WordPress site, however, the right way to do that is from the NGNIX itself.

In the example below, we have two server blocks. The first one’s only purpose is to capture standard HTTP requests and redirects them to HTTPS requests.

server {
    listen 80;
    return 301$request_uri;
server {
    listen 443;
    ssl_protocols TLSv1.1 TLSv1.2;
    # [...]


Restrict HTTPS to TLSv1.1 and TLSv1.2

Refer to the example above. In the SSL server block, the ssl_protocls directive clearly defines the protocols that should be allowed. A more liberal approach would be to include the TLSv1 as well:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

Forwarding uncontrolled requests to PHP

Many online guides about setting up NGINX with PHP contain sections like this:

location ~* \.php$ {
    fastcgi_pass backend;
    # [...]

In the above example, every request ending in .php will be passed to the FastCGI backend. The default PHP configuration tries to guess which file you want to execute if the specified location does not lead to an actual php file.

For instance, if a request is made for / which does not exist but if / does, the PHP interpreter will process // instead. If this contains embedded PHP code, this code will be executed accordingly. Do you know what this means? If you have a WordPress site that allows a user to upload images, an attacker could easily upload a malicious file disguised as an image and compromise your site.

There are a couple of solutions for this both in the NGINX configuration and in the PHP itself (See below at Securing PHP).

One approach is to ensure that you give NGINX specific files for execution. This is the most secure method but it will also limit the files allowed to be executed and may give you problems later on.

In the example below our server will process only the files named index.php, site.php and cms.php.

location ~* (index.php|site.php|cms.php)\.php$ {
    fastcgi_pass backend;
    # [...]

Another approach is to use the try_files directive. In the below example, if the provided file does not exist, NGINX will throw a 404 error.

location ~* \.php$ {
    try_files $uri =404;
    fastcgi_pass backend;
    # [...]

Last but not least, disable script processing for any folders not meant for such purpose. To be more precise such folders are usually folders containing uploads of any kind.

location /uploads {
    location ~ \.php$ {return 403;}
    # [...]
Stuck somewhere? Get a fully managed VPS from us and we’ll completely and properly secure your server.

Securing MySQL / MariaDB

There is not much on the topic of securing your MySQL database and in fact, the best way to secure it is to make sure that it is not accessible from anywhere except the host itself. After installing MySQL, the package comes with a great deployment script which can be invoked using mysql_secure_installation on your server. The script is great because it will make sure that there are no loose ends. It will prompt you to setup a MySQL root password as well as it will disable the remote logins and will remove the test database.

For those of you that already have functioning servers and do not wish to start the script, the best place to check is /etc/my.cnf and make sure that you have the bind-address = value set and it is not commented out.

Another also highly secure mechanism is to use one (read at least one) user/database per site. A lot of websites out there make use of the root mysql user for connecting the website with the database. The root user by default has access to every other database. This means any discovered vulnerability on one site, can potentially be exploited for an attacker to gain access to all of your databases.

Bottom line is, if you have three websites on your server, then there should be at least three database users accessing their own database.

Securing PHP

PHP itself provides a relatively large attack surface, especially because most PHP implementations by default have fairly liberal configurations so that the developers can write their code without too much hassle. The subtopics mentioned here, are general considerations for sysadmins. These, however, have to be additionally tweaked as per the developer’s needs. Securing the PHP interpreter is always a place of argument and debate between the sysadmins and the developers, and can only be done properly if they all work together.

Always a place of argument and debate between the sysadmins and the developers, thus can only be done properly if both work together.

Types of PHP Attacks

SQL Injection

This is a vulnerability of the application itself, usually poorly coded PHP apps almost always have this vulnerability. The more popular CMS systems are always secured against this type of attack. There is not much to be said about this type of attack except that the best way to prevent it is with good education for the developers.

XSS – Cross Site Scripting

These types of vulnerabilities are difficult to defend against. Again, the developers of the commonly used CMS platforms like WordPress, Joomla, Drupal etc. are very cautious and make sure that their code is properly written and regularly patched. Most of the security measures against these and any other types of attacks can be implemented via the main PHP configuration file. More on this later.

Cross-site request forgeries – CSRF

Type of attack which forces the end user to execute unwanted actions on the web application where he is currently authenticated. If the user is an administrator account, the entire website could be compromised. Banking and e-commerce sites are particularly targeted with these types of attacks especially because the attackers are interested in stealing their account information to gain access to their funds.

PHP.INI Tweaks

Stop PHP processing if the file is not found

Make sure that cgi.fix_pathinfo=0  is in fact set to 0 in php.ini. This causes the PHP interpreter to only try the literal path given and to stop processing if the file is not found. Do you recall the examples from Forwarding uncontrolled requests to PHP of the Securing NGINX part? This is the final piece of the NGINX/PHP-FPM puzzle.

Disabling PHP Dangerous functions

The flexibility of PHP includes many functions by default which can be used or misused depending on what one wishes to accomplish. For example, for more advanced configurations, PHP allows for remote file executions where the files can be remotely executed from another server. Although this may sound fun, it is also a security vulnerability where an attacker can open and execute any file on the remote server. This may allow them to also upload malicious files. Remote file execution should be disabled from the PHP configuration file.

disable_functions =exec,eval,phpinfo,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

Restricting file uploads

We’ve seen that through the NGINX configuration we can restrict the file uploads only to certain directories on our site. However, if your site doesn’t use file uploading features at all, they can and should be completely disabled.


Of course, if your application is using the file uploads feature, then it can be reasonably enabled by restricting the upload size limit. In the below example, one can upload files up to 1 megabyte.


Set the POST size to a reasonable value

The POST method is used whenever a user needs to send some data to your server/web application, and as such can potentially be exploited for malicious use, like DoS attack for example. This also includes sending large files to your server. However this method is also an integral part of any web application, so the best way to protect yourself is to limit its value and set it to something more reasonable. If you don’t use file uploads, then its value can be set to something like 4 KB or less.


If you do use the uploading files feature, then it should be set to a value that is larger than the upload_max_filesize value on your server.

Restrict PHP Information Leakage

By setting the  expose_php = Off, one can simply hide the information that PHP is installed on the server. This will, in fact, hide the signature that PHP is leaving on the Web server header. The PHP documentation states that leaving this value to ON is not a vulnerability. However, no one can deny the fact that the less information you present to the external world, the more secure your server will be.

Restricting the PHP script maximum execution time

Set the following values in your php.ini file. They can later be tailored according to a more specific needs.

# set in seconds
max_execution_time = 30
max_input_time = 30
memory_limit = 40M

Not only that setting these limits are a great DoS attack prevention, but they can also protect your server by careless programming and infinite loop cycles. What this basically does, is the following:

  1. Sets the maximum time to 30 seconds that a script is allowed to run before it is terminated by the parser (PHP).
  2. Sets the maximum time to 30 seconds that a script is allowed to parse input data, like POST and GET.
  3. Sets the maximum amount of memory to 40 MB, a value that a script is allowed to allocate.

In many PHP configurations, the memory_limit value is set to -1, which is fine in development, but otherwise a bad choice.

Disable unused PHP modules

PHP modules are great since they allow and enable certain functionalities to your web application. The most common example for this is the PHP pdo_mysql module which allows PHP to access MySQL databases. All of these modules, however, can potentially bring their own flaws and vulnerabilities with them and if they are not used, they can be disabled or removed. By using the command php -m one can see all of the installed PHP modules. Disabling unused modules lowers the PHP attack surface. Ideally, PHP should be reinstalled and recompiled using only the needed modules. However, if you feel that redoing all of that is too much, then you can simply disable these by just uncommenting their line in php.ini or renaming their configuration file in /etc/php.d/module_name.ini to something like /etc/php.d/module_name.ini.disabled.


The Linux server itself is by nature a very secure operating system. This does not count for any negligence, so standard security measures should always be practiced. Direct root logins are never recommended, and public key authentication is always advised.

Proper LEMP stack implementation is essential for any secure web server setup. However one should always keep in mind that this is just one part of the security. Securing your servers and web applications should be in fact a joined effort between the developers and the system administrators. That is the other part is in the application code itself. For example, even if all of the above is carefully planned and implemented, but the developers do not secure the MySQL queries properly, your application will be vulnerable to the MySQL Injection attack which will lead to data leakage.

Finally, regularly installing the application upgrades and the security patches are always one of the best ways to protect against any known vulnerabilities. Your server and application if left unattended will eventually get compromised.

Of course, you don’t have to do any of this if you use one of our Linux SSD VPS solutions, in which case you can simply ask their expert Linux admins to setup everything on your Linux server. They are available 24×7 and will take care of your request immediately.

PS. If you liked this post please share it with your friends on the social networks using the buttons below or simply leave a reply. Thanks.

Install Croogo on an Ubuntu VPS with Nginx and MariaDB
How to install Cody CMS on Ubuntu 14.04
How to install Exponent CMS on a Linux Virtual Server