5/5 - (1 vote)

This material is a series of articles, you can read the rest of the parts at the following links:
https://systemadminspro.com/setting-up-a-lemp-server-for-simple-projects-instructions-for-the-little-ones-part-one/ -first part

https://systemadminspro.com/setting-up-a-lemp-server-for-simple-projects-instructions-for-the-little-ones-part-three/ – third part

The article is a training material for novice administrators, as well as for developers who would like to get acquainted with the world of project administration. If you are an experienced administrator, you can safely skip this material.

The purpose of the series of articles is to describe how to prepare a server with a LEMP stack (Linux, Nginx, MySQL, PHP), I note that Apache2 is used as a PHP interpreter here , and not PHP-FPM , as practice shows many developers still need the .htaccess file , which PHP-FPM does not support out of the box.

For our part, we transfer the rules from the .htaccess file to Nginx if you need to install PHP-FPM, but this also takes some time, it is often easier and faster for developers to add the necessary rules for current sites to the .htaccess file. The articles also describe the deployment of the stack and the raising of working sites on it. The instruction is suitable for small Bitrix projects, as well as for projects deployed under any popular CMS .

Despite the fact that the topic is already covered in sufficient detail on the web, we decided to describe in detail the general standards of administration from scratch, since we regularly receive a large number of basic questions from people who are somehow related to our field.

The purpose of the articles is not to show how to deploy an ideal environment, but only to point out the nuances in operation and protect beginners from basic configuration mistakes.

The second part of the article will cover topics such as:

Basic Nginx
setup Apache2
basic setup MySQL basic setup

Basic Nginx Setup

After the basic configuration of the main components of the server, it’s time to start configuring the web servers, let’s start with Nginx . The package is already in the standard Debian repositories , so we perform a simple installation:

apt update
apt install nginx

Nginx in our configuration will be the entry point of http / https traffic and process static data. After installation, we begin to configure the web server, for this we go to the /etc/nginx/nginx.conf file  and bring it to the following form:

user www-data;
worker_processes auto;
worker_priority -15;
include /etc/nginx/modules-enabled/*.conf;
error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;
events {
        worker_connections  1024;
http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;
        log_format nixys '$remote_addr\t"$host"\t[$time_local]\t$status\t"$request"\t$request_time ($upstream_response_time)\t$bytes_sent\t"$http_referer"\t"$http_user_agent"';
        log_format nixys-debug '$remote_addr\t"$host"\t[$time_local]\t$status\t"$request"\t"req_time: $request_time"\t"bytes_sent: $bytes_sent"\n'
                            	'\t\t\t\t\t\t\t\t"req_file: $request_filename"\t"$http_user_agent"\t"$http_referer"\n'
                                '\t\t\t\t\t\t\t\t"Request completed: $request_completion"\n'
                                '\t\t\t\t\t\t\t\t"Body request: $request_body"\n';
    	access_log  /var/log/nginx/access.log nixys;
        sendfile	on;
        tcp_nodelay on;
    	gzip on;
        gzip_proxied any;
        gzip_comp_level 4;
        gzip_vary on;
        gzip_types text/css text/plain application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
    	server_tokens off;
    	server_names_hash_bucket_size 33;
        reset_timedout_connection on;
        client_header_timeout 15;
        client_body_timeout   15;
        send_timeout       	5;
        keepalive_timeout  30 15;
    	include /etc/nginx/conf.d/*.conf;
    	include /etc/nginx/sites-enabled/*;

Let’s go over the main changes we’re adding to the default configuration:

worker_priority -15;

This line raises the priority of Nginx processes in the system. This is necessary in order to give processing priority to Nginx processes during high load , which will allow displaying static site data.

worker_connections  1024;

We increase the number of connections of one worker process to increase the number of requests in processing. This parameter can not be changed if you have a weak server.

Be sure to enable data compression:

gzip on;
gzip_proxied any;
gzip_comp_level 4;
gzip_vary on; gzip_types text/css text/plain application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;

I would also like to note the following log format:

    	log_format nixys '$remote_addr\t"$host"\t[$time_local]\t$status\t"$request"\t$request_time ($upstream_response_time)\t$bytes_sent\t"$http_referer"\t"$http_user_agent"';
    	log_format nixys-debug '$remote_addr\t"$host"\t[$time_local]\t$status\t"$request"\t"req_time: $request_time"\t"bytes_sent: $bytes_sent"\n'
                           	'\t\t\t\t\t\t\t\t"req_file: $request_filename"\t"$http_user_agent"\t"$http_referer"\n'
                                '\t\t\t\t\t\t\t\t"Request completed: $request_completion"\n'
                                '\t\t\t\t\t\t\t\t"Body request: $request_body"\n';

Here we set two log formats for Nginx . The Nixys log format will be the default, and will be used for all virtual hosts. The logs connected with it look like this: "site.ru"   [18/Dec/2021:06:26:50 +0500]	200 	"GET / HTTP/1.1"    	0.028 (0.028)   13069   "-" 	"Mozilla/5.0 (compatible; InterfaxBot/1.0; email:[email protected])"

Representing logs in this way is extremely convenient for analysis, let’s expand the log line for understanding: – client IP address

site.ru   – virtual host name

18/Dec/2021:06:26:50 +0500 – request receipt time

200 – response code received as a result of the request

0.028 – Nginx request processing time

(0.028)   – request processing time from the side to which Nginx passed the request (if necessary). In our case, this will reflect the time it took for Apache2 to process the connection.

13069   is the size of the transferred data in bytes

“Mozilla/5.0 (compatible; InterfaxBot/1.0; email:[email protected])” – user-agent passed by the client.

For our part, we consider this logging format the most successful, as it allows you to analyze the situation as much as possible in case of problems in the operation of the site.

After making these changes, you need to reread the web server files. During any reload or restart operation of the Nginx service , it is necessary to test the configuration for errors using the nginx -t command , many administrators forget about this at the beginning of work, which can lead to downtime in the project:

nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If everything is OK, then reread the web server configuration:

service nginx reload

And be sure to check the status:

service nginx status
● nginx.service - A high performance web server and a reverse proxy server

   Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled
   Active: active (running) since Sun 2021-12-19 19:55:16 MSK; 36min ago
 	Docs: man:nginx(8)
  Process: 2913 ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload (code=exited, status=0/SUCCESS)
 Main PID: 2246 (nginx)
	Tasks: 3 (limit: 1171)
   Memory: 9.3M
   CGroup: /system.slice/nginx.service

           ├─2246 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;

           ├─2914 nginx: worker process

           └─2915 nginx: worker process

Please note that after restarting any service, be sure to check the status of its operation!

This completes the basic Nginx setup .

Apache2 basic setup

Let’s move on to configuring Apache2 . Install the necessary packages:

apt install apache2 libapache2-mpm-itk

If you did everything according to the article, then Apache2 will not start immediately after installation and will give an error:

Action 'start' failed.
The Apache error log may have more information.
apache2.service: Control process exited, code=exited, status=1/FAILURE
Failed with result 'exit-code'.
Failed to start The Apache HTTP Server.

The fact is that the default web server runs on the same port as the running Nginx . Therefore, we will need to change the standard 80 port of the web server to 81 . To do this, edit the /etc/apache2/ports.conf file and change the port:

-Listen 80
+Listen 81

In our case, Apache2 works with the mpm_prefork module , so we enable the module with the command:

a2enmod mpm_prefork

After we need to set the parameters for the work of the workers, you can configure these parameters as you like, depending on the load of the project; they are located in the file /etc/apache2/mods-enabled/mpm_prefork.conf , the basic configuration is as follows:

<IfModule mpm_prefork_module>
        StartServers        	5
        MinSpareServers     	3
        MaxSpareServers     	10
        MaxRequestWorkers   	50
        MaxConnectionsPerChild  2000
        LimitUIDRange       	0 65535
      	LimitGIDRange       	0 65535

In order to prevent the message that the server name is not declared each time when checking the Apache2 syntax, we declare it in the apache2.conf file


Where MAIN_DOMAIN_NAME is the name of the server’s main site.

You will also need to cast the web server’s default virtual host to:

<VirtualHost *:81>
    	ServerName default
    	ServerAdmin webmaster@localhost
    	DocumentRoot /usr/share/apache2/default-site
    	ErrorLog ${APACHE_LOG_DIR}/error.log
    	CustomLog ${APACHE_LOG_DIR}/access.log combined

You need to change the port on which the default virtual host is running according to /etc/apache2/ports.conf . In the future, you will see that the logs of each site will be located in a separate directory, however, the logs of the standard virtual host will be located according to these settings in /var/log/apache2.

It is also very important to configure the correct display of the external IP address of clients in the apache2 logs , for this you will need to activate the following modules using the commands:

a2enmod remoteip
a2enmod rewrite

If after that appears in the apache2 logs of the site , then the following changes must also be made to the log format:

--- apache2.conf_old	2016-07-06 18:30:37.423038360 +0500

+++ apache2.conf    	2016-07-06 18:27:32.874586631 +0500

@@ -204,8 +204,8 @@

 # Use mod_remoteip instead.

 LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
-LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
-LogFormat "%h %l %u %t \"%r\" %>s %O" common
+LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
+LogFormat "%a %l %u %t \"%r\" %>s %O" common
 LogFormat "%{Referer}i -> %U" referer
 LogFormat "%{User-agent}i" agent

Next, you need to configure the security of the web server, for this we make changes to the /etc/apache2/conf-available/security.conf file:

iff --git a/etc/apache2/conf-available/security.conf b/etc/apache2/conf-available/security.conf
index 599333b..73e4a81 100644

--- a/etc/apache2/conf-available/security.conf
+++ b/etc/apache2/conf-available/security.conf

@@ -22,9 +22,7 @@

# and compiled in modules.
# Set to one of:  Full | OS | Minimal | Minor | Major | Prod
# where Full conveys the most information, and Prod the least.

-#ServerTokens Minimal
-ServerTokens OS
-#ServerTokens Full
+ServerTokens Prod

# Optionally add a line containing the server version and virtual host
@@ -33,8 +31,7 @@ ServerTokens OS
# documents or custom error documents).
# Set to "EMail" to also include a mailto: link to the ServerAdmin.
# Set to one of:  On | Off | EMail
-#ServerSignature Off
-ServerSignature On
+ServerSignature Off


# Allow TRACE method
@@ -44,7 +41,6 @@ ServerSignature On
# Set to one of:  On | Off | extended

TraceEnable Off
-#TraceEnable On

# Forbid access to version control directories

We also globally deny access to the .svn, .git, .hg directories:

@@ -52,9 +48,9 @@ TraceEnable Off
# If you use version control systems in your document root, you should
# probably deny access to their directories. For example, for subversion:

-#<DirectoryMatch "/\.svn">
-#   Require all denied

+<DirectoryMatch "/\.(svn|git|hg)">
+   Require all denied

# Setting this header will prevent MSIE from interpreting files as something

The final touch in setting up the web server is to set the rights to the directory with apache2 :

chmod 750 /etc/apache2

After completing all these steps, you need to check the Apache2 syntax :

apache2ctl -t
Syntax OK

After restarting the web server:

service apache2 restart

And as usual, check the status of the job:

service apache2 status
● apache2.service - The Apache HTTP Server
   Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2021-12-19 21:12:12 MSK; 1s ago
 	Docs: https://httpd.apache.org/docs/2.4/
  Process: 3727 ExecStart=/usr/sbin/apachectl start (code=exited, status=0/SUCCESS)
 Main PID: 3731 (apache2)
	Tasks: 6 (limit: 1171)

The initial setup of Apache2 can also be considered complete.


Basic MySQL setup.

Let’s move on to installing and configuring MySQL , in our case it will be version 5.7 . Before starting the installation, add the repository key with the command:

wget -q -O- http://repo.mysql.com/RPM-GPG-KEY-mysql | apt-key add -

After we add the required repository to the apt package manager , for this we add lines like this to the end of the /etc/apt/sources.list file

deb http://repo.mysql.com/apt/debian/ buster mysql-5.7
deb-src http://repo.mysql.com/apt/debian/ buster mysql-5.7

Please note that our server is based on a Debian 10 (buster) system , if you are installing MySQL on a different version of the Debian or Ubuntu distribution , then you will need to take into account the name of the distribution in the list of repositories.

After we update the available packages in the repositories and install MySQL :

apt update
apt install mysql-server

The installation will ask you to enter the password twice as root.

You may need a module to work with python , install it with the command:

apt install python-mysqldb

After installation, we proceed to the basic setup. By default , the ulimit (maximum) limit for open files in the system for a process is 1024 , which is very small, especially for DBMS operation , increase the file limit to 8192 via systemctl :

systemctl edit mysql

Insert the following text:

LimitNOFILE = 8192

Reread the limits and restart MySQL :

systemctl daemon-reload
systemctl restart mysql

We specify the default DBMS encoding, in our case it is UTF8 , for this we make changes to the /etc/mysql/my.cnf file :

diff --git a/mysql/my.cnf b/mysql/my.cnf

+character-set-server = utf8
+collation-server = utf8_unicode_ci

By default , MySQL only listens on the local IP address . If you need to connect to the DBMS from the outside, you will need to open port 3306 at the server firewall level, and also tell MySQL to listen on all server interfaces. To do this, specify the following value for the bind-address parameter:

bind-address =

Here, in the my.cnf file, we specify the basic parameters for the operation of the DBMS:

max_allowed_packet  	= 32M
thread_stack        	= 512K
thread_cache_size   	= 64
open_files_limit    	= 8192
max_connections     	= 100
query_cache_limit   	= 4M
query_cache_size    	= 16M

These parameters can be adjusted according to the needs of the project.

For innodb tables , we set storage in separate files to avoid possible problems associated with the growth of the ibdata1 file :


And we set the buffer for innodb , usually it is equal to 60-80% of the server’s RAM. For example, for a server with 4GB of RAM, we can allocate 3GB of RAM to the buffer:

innodb_buffer_pool_size = 3GB

The value can also be adjusted as needed.

As a security, we restrict access to MySQL for system users:

chmod 750 /etc/mysql
chown root:mysql /etc/mysql

We restart the DBMS and check the status of the work:

service mysql restart
service mysql status

In order to avoid specifying the password for the root user each time you log into the MySQL console , create a file in the root home directory and set security rights to it:

touch /root/.my.cnf
chmod 600 /root/.my.cnf

The generated file should have the following content:


Where MYSQL_ROOT_PASSWORD is the password for the root user.

Now, to connect to the MySQL console as root , simply enter the mysql command :


Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 40559777
Server version: 5.7.31-34 Percona Server (GPL), Release '34', Revision '2e68637'

Copyright (c) 2009-2020 Percona LLC and/or its affiliates
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


This completes the basic MySQL setup .

To optimize mysql you can use the mysqltuner utility . It is in the standard repositories. Its essence is that with the help of scripts, the parameters and current indicators of mysql are analyzed . If there are non-optimized parameters, the utility will offer to change them. Below is an example of mysqltuner parsing output :

-------- Recommendations -----------------------------------------------------

General recommendations:

    Run OPTIMIZE TABLE to defragment tables for better performance
    Reduce your overall MySQL memory footprint for system stability
    Adjust your join queries to always utilize indexes
    When making adjustments, make tmp_table_size/max_heap_table_size equal
    Reduce your SELECT DISTINCT queries without LIMIT clauses

Variables to adjust:

  *** MySQL's maximum memory usage is dangerously high ***
  *** Add RAM before increasing MySQL buffer variables ***

    query_cache_size (> 16M)
    join_buffer_size (> 16.0M, or always use indexes with joins)
    tmp_table_size (> 128M)
    max_heap_table_size (> 128M)
    innodb_buffer_pool_size (>= 11G)


The last third article will describe:

  • Installing and configuring PHP
  • The process of creating and configuring virtual hosts for web servers.
  • Issuing SSH and FTP access for the site
  • Setting up mail records so that mail sent from the server does not fall into SPAM.

In the comments to the previous article, I saw a lot of criticism regarding the content and level of the material. I do not agree that articles of this level are unnecessary to the community. As the practice of our company shows, a lot of clients are not yet ready to containerize their project, and therefore the demand for information of this kind is still high. This material is not a panacea, but only a basic instruction that should clarify the basic points and indicate to the administrator the features that need or should not be done at the beginning of the journey.