Recently decided to move from hosting to VPS, we will use: CentOS 7, Nginx, Apache, PHP, MySQL. Despite the large number of articles on this topic, many aspects are not mentioned, so we lay out this article to hear the opinion of knowledgeable and experienced people. Configuring the server as you already understood will be the first time, so the relevance of the article can be judged from the comments. Nginx will give statics, and Apache dynamics (PHP scripts) to reduce server load.
Training.We will apply all settings on the working server of our project with the server configuration: CPU - 2 × 2000 MHz and RAM - 2048 MB.
')
To get started, we find a suitable VPS with CentOS 7 preinstalled, we will connect to the server via SSH via
PuTTY .
Enter the host name and port, click Open:

Next, enter the login [Enter], then the password (note that the password is not displayed) [Enter]:
Upgrade the system, while maintaining outdated versions of packages:
[root@test ~]# yum update
Or upgrade all packages, old packages will be deleted:
[root@test ~]# yum upgrade
Install the file manager with a text interface - Midnight Commander:
[root@test ~]# yum install mc
Install the text editor -
Nano :
[root@test ~]# yum install nano
We check how much RAM is available on the server and how much is available, as well as the presence of SWAP. When the memory runs out, the data is moved to disk, which slows down the server, SWAP operation is undesirable, but allows you to insure yourself:
[root@test ~]# free -m
We create file structure and users for sites.Create a directory (folder) for files for all sites:
[root@test ~]# cd /
[root@test ~]# mkdir -m 755 website
Under each individual site perform such actions.The content of each site will be in its own directory, so we create a new user and a separate directory to differentiate access rights:
-b folder in which the user directory will be created
-m create directory
-U create a group with the same name as the user
-s / bin / false disable user shell
[root@test ~]# useradd name.site -b /website/ -m -U -s /bin/false
We create directories for site data (site files, logs and temporary files):
[root@test ~]# mkdir -p -m 754 /website/name.site/www
[root@test ~]# mkdir -p -m 754 /website/name.site/logs
[root@test ~]# mkdir -p -m 777 /website/name.site/tmp
Change the owner and group to a directory, including subfolders:
[root@test ~]# chown -R name.site:name.site /website/name.site/
Change directory permissions - name.site:
[root@test ~]# chmod 755 /website/name.site
Install Nginx.Installation instructions are provided on the official
Nginx website.
To configure the yum repository in CentOS, create the file /etc/yum.repos.d/nginx.repo:
[root@test ~]# cd /etc/yum.repos.d
[root@test ~]# touch nginx.repo
Open the nginx.repo file:
[root@test ~]# nano /etc/yum.repos.d/nginx.repo
Paste this content and save the file:
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=1
enabled=1
To verify the signature, download the key and import it into the rpm package manager:
[root@test ~]# rpm --import http://nginx.org/keys/nginx_signing.key
Install Nginx:
[root@test ~]# yum install nginx
Run:
[root@test ~]# systemctl start nginx.service

Temporarily stop:
[root@test ~]# systemctl stop nginx.service
Install Apache and PHP.Install Apache (on CentOS - httpd):
[root@test ~]# yum install httpd
Install PHP:
[root@test ~]# yum install php
Run Apache:
[root@test ~]# systemctl start httpd.service

Temporarily stop:
[root@test ~]# systemctl stop httpd.service
We configure Nginx.Add to autoload:
[root@test ~]# systemctl enable nginx.service
Open the main configuration file:
[root@test ~]# nano /etc/nginx/nginx.conf
We edit and save:
/etc/nginx/nginx.confuser nginx;
worker_processes 2;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
}
http {
error_log /var/log/nginx/error.log warn;
access_log off;
charset utf-8;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
reset_timedout_connection on;
client_header_timeout 15;
client_body_timeout 30;
send_timeout 15;
keepalive_timeout 5;
keepalive_requests 30;
client_max_body_size 8m;
limit_rate_after 30M;
limit_rate 500K;
open_file_cache max=10000 inactive=3m;
open_file_cache_min_uses 2;
open_file_cache_valid 1m;
sendfile on;
tcp_nodelay on;
tcp_nopush on;
include /etc/nginx/conf.d/*.conf;
}
From which user we start Nginx:
user nginx;
Specify the number of working processes (depending on the number of processor cores and the number of hard drives, because the disk head will not be able to move faster):
worker_processes 2;
Process ID of the running server:
pid /var/run/nginx.pid;
Events section:
events {
#
}
Inside the events block, the maximum number of simultaneous connections to the server (worker_processes × worker_connections):
worker_connections 1024;
Inside the events block, accept connections as much as possible:
multi_accept on;
Section http, the rest will be inside it:
http {
#
}
Write errors (levels: warn, error, crit, alert) at the specified path:
error_log /var/log/nginx/error.log warn;
Turning off the access log entry (the hard disk will thank you):
access_log off;
Set the default encoding:
charset utf-8;
Disable the display version of Nginx:
server_tokens off;
Connect mimetypes:
include /etc/nginx/mime.types;
If the MIME type of the file cannot be determined, then by default the file will be binary:
default_type application/octet-stream;
Close the connection if the client does not respond:
reset_timedout_connection on;
Read client request header no more than 15 seconds:
client_header_timeout 15;
Read client request body no more than 30 seconds - the interval is set not for the entire transfer of the request body, but only between two consecutive reads:
client_body_timeout 30;
If the client does not accept the response for more than 15 seconds, reset the connection:
send_timeout 15;
Keep the connection open for no more than five seconds:
keepalive_timeout 5;
Maximum number of requests with an open connection from one client:
keepalive_requests 30;
We will not accept requests larger than 8 megabytes:
client_max_body_size 8m;
To prevent one user from occupying the entire free traffic channel, after 30 MB we impose a limit on the speed of data transfer:
limit_rate_after 30M;
The maximum speed with the client within one connection after the imposed restrictions will be no more than 500 Kb / s:
limit_rate 500K;
Set the maximum number of files, information about which will be contained in the cache and deleted if the file is not requested again within 3 minutes:
open_file_cache max=10000 inactive=3m;
If the file is requested more than 2 times, put in the cache:
open_file_cache_min_uses 2;
Check cache for every minute:
open_file_cache_valid 1m;
Give statics without intermediaries:
sendfile on;
Do not buffer data:
tcp_nodelay on;
Send headers in one batch:
tcp_nopush on;
Connect configs:
include /etc/nginx/conf.d/*.conf;
For each site we create a virtual host Nginx.In order for Nginx to access the site files, add the user nginx to the group name.site:
[root@test ~]# usermod -a -G name.site nginx
Then we create a configuration file:
[root@test ~]# touch /etc/nginx/conf.d/name.site.conf
Open the file:
[root@test ~]# nano /etc/nginx/conf.d/name.site.conf
We edit and save:
/etc/nginx/conf.d/name.site.confserver {
listen 80;
server_name name.site www.name.site;
#access_log /website/name.site/logs/nginx_access.log;
error_log /website/name.site/logs/nginx_error.log;
location / {
proxy_pass http://127.0.0.1:8080/;
proxy_read_timeout 300s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off;
}
location ~* \.(css|js|png|gif|jpg|jpeg|ico)$ {
root /website/name.site/www;
expires 1d;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
The server listens on port 80:
listen 80;
The server name, determines in which block the request will be executed, indicate the domain name:
server_name name.site www.name.site;
Path to the Nginx error log of a specific site:
error_log /serves/name.site/logs/nginx_error.log;
Redirect Apache Request:
proxy_pass http://127.0.0.1:8080/;
Disconnect the connection after 300 seconds if the timeout is exceeded while reading the response from the Apache server:
proxy_read_timeout 300s;
Submit Headers:
proxy_set_header Host $host;
Transfer client IP:
proxy_set_header X-Real-IP $remote_addr;
Send the list of servers for which the request was passed and add your own:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Disable buffering of the proxied server:
proxy_buffering off;
Nginx will give statics:
location ~* \.(css|js|png|gif|jpg|jpeg|ico)$ {
root /serves/name.site/www;
expires 1d;
}
Configure Apache.See which Apache module you have installed. I have apache2-mpm-prefork (one process with one thread will handle one connection, recommended as secure with PHP):
[root@test ~]# apachectl -V
Open httpd.conf:
[root@test ~]# nano /etc/httpd/conf/httpd.conf
We edit and save:
httpd.confServerRoot "/etc/httpd"
DocumentRoot "/website"
Include conf.modules.d/*.conf
User apache
Group apache
Listen 127.0.0.1:8080
ServerName 127.0.0.1:8080
ServerAdmin root@localhost
ServerSignature Off
ServerTokens Prod
RLimitMEM 786432000
TimeOut 250
AddDefaultCharset utf-8
DefaultLanguage ru
KeepAlive Off
ContentDigest Off
EnableSendfile off
ErrorLog "logs/error_log"
LogLevel error
<IfModule mime_module>
TypesConfig /etc/mime.types
</IfModule>
<Directory />
DirectoryIndex index.php
AllowOverride none
Require all denied
</Directory>
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxClients 30
MaxRequestsPerChild 2500
</IfModule>
<Files ".ht*">
Require all denied
</Files>
IncludeOptional sites-enabled/*.conf
Install the Apache root directory:
ServerRoot "/etc/httpd"
The directory where the site files will be stored:
DocumentRoot "/website"
We load configuration files:
Include conf.modules.d/*.conf
From which user we start the server:
User apache
From which group we start the server:
Group apache
Specify the IP and port from which we will receive requests, from the outside this server will not be visible:
Listen 127.0.0.1:8080
Hostname and port to identify yourself:
ServerName 127.0.0.1:8080
Email address that is sent to the client in case of errors:
ServerAdmin root@localhost
Disable sending the version information of the system and the Apache server:
ServerSignature Off
Disable sending Apache information to the client in the header:
ServerTokens Prod
We limit memory usage to 750 megabytes:
RLimitMEM 786432000
Maximum time for receiving a request, processing, sending content to the Nginx server:
TimeOut 250
Set the encoding:
AddDefaultCharset utf-8
Set the content language:
DefaultLanguage ru
Turning off the processing of a large number of requests in one connection:
KeepAlive Off
Disable the generation of Content-MD5 HTTP headers:
ContentDigest Off
Apache will not give statics, so disable:
EnableSendfile off
We write Apache errors at the specified path / etc / httpd / logs / error_log:
ErrorLog "logs/error_log"
We indicate at what level to record errors:
LogLevel error
Connect mimetypes:
<IfModule mime_module>
TypesConfig /etc/mime.types
</IfModule>
Directory section:
<Directory />
...
</Directory>
Inside the Directory block, in the case of specifying the path to the directory, by default give index.php ::
DirectoryIndex index.php
Inside the Directory block, prohibit overriding access information in .htaccess:
AllowOverride none
Inside the Directory block, deny access to server files:
Require all denied
Mpm_prefork_module Section:
<IfModule mpm_prefork_module>
...
</IfModule>
Inside the mpm_prefork_module block, after running Apache, create 5 processes:
StartServers 5
Inside the mpm_prefork_module block, the minimum number of unused processes (if all processes are busy, new free processes will start):
MinSpareServers 5
Inside the mpm_prefork_module block, the maximum number of unused (spare) processes:
MaxSpareServers 10
Inside the mpm_prefork_module block, the maximum number of child processes that can be started simultaneously, the rest are queued (with an increase in child processes, the memory consumption increases):
MaxClients 30
Inside the mpm_prefork_module block, after the specified number of processed requests, the process is restarted (necessary for overflow - memory leaks):
MaxRequestsPerChild 2500
We close access to .htaccess:
<Files ".ht*">
Require all denied
</Files>
We load configuration files:
IncludeOptional sites-enabled/*.conf
For each site we create a virtual host Apache.Add the apache user to the group of each site:
[root@test ~]# usermod -a -G name.site apache
Create a directory for Apache virtual host configuration files:
[root@test ~]# mkdir /etc/httpd/sites-enabled
Create a configuration file:
[root@test ~]# touch /etc/httpd/sites-enabled/name.site.conf
Open the file:
[root@test ~]# nano /etc/httpd/sites-enabled/name.site.conf
We edit and save:
/etc/httpd/sites-enabled/name.site.conf<VirtualHost *:8080>
ServerName name.site
ServerAlias www.name.site
DocumentRoot /website/name.site/www
<Directory "/website/name.site">
AllowOverride None
Require all granted
</Directory>
DirectoryIndex index.php
ErrorLog /website/name.site/logs/error.log
CustomLog /website/name.site/logs/requests.log combined
</VirtualHost>
Block VirtualHost, which port is indicated to listen:
<VirtualHost *:8080>
...
</VirtualHost>
Domain name:
ServerName name.site
Domain Mirror:
ServerAlias www.name.site
The directory where the files of this site will be stored:
DocumentRoot /website/name.site/www
Open access to site files:
Require all granted
If the path is to the directory, by default open:
DirectoryIndex index.php
The path to the Apache error log of a specific site:
ErrorLog /website/name.site/logs/error.log
Access log path:
CustomLog /website/name.site/logs/requests.log combined
Check Nginx and Apache.Add Apache to autoload:
[root@test ~]# systemctl enable httpd.service
Create, edit and save the file:
[root@test ~]# touch /website/name.site/www/index.php
[root@test ~]# nano /website/name.site/www/index.php
Copy the php config:
[root@test ~]# cp /etc/httpd/conf.d/php.conf /etc/httpd/sites-enabled/php.conf
Run Nginx and Apache:
[root@test ~]# systemctl start nginx.service
[root@test ~]# systemctl start httpd.service
We configure PHP.Open php.ini:
[root@test ~]# nano /etc/php.ini
We edit and save:
/etc/php.iniengine = On
expose_php = Off
short_open_tag = Off
zlib.output_compression = Off
disable_functions = exec, passthru, shell_exec, system, proc_open, popen, curl_exec, curl_multi_exec, parse_ini_file, show_source, etc
display_startup_errors = Off
display_errors = Off
log_errors = On
error_log = "/usr/local/zend/var/log/php.log"
ignore_repeated_errors = Off
ignore_repeated_source = Off
html_errors = On
implicit_flush = Off
output_buffering = 4K
realpath_cache_size = 2M
realpath_cache_ttl = 1800
zend.enable_gc = On
max_input_time = 200
max_execution_time = 30
file_uploads = On
memory_limit = 256M
post_max_size = 8M
upload_max_filesize = 2M
max_file_uploads = 4
extension_dir = "/usr/local/zend/lib/php_extensions"
date.timezone = Europe/Moscow
default_mimetype = "text/html"
default_charset = "UTF-8"
variables_order = "CGPS"
register_argc_argv = Off
auto_globals_jit = On
enable_dl = Off
allow_url_fopen = On
allow_url_include = Off
We enable the PHP interpreter, if necessary, you can turn it off on a specific site:
engine = On
Disable the headers sent to the client about PHP:
expose_php = Off
Disable short PHP tag entries <? ...?>:
short_open_tag = Off
Turn off page compression:
zlib.output_compression = Off
Disable dangerous functions:
disable_functions = exec, passthru, shell_exec, system, proc_open, popen, curl_exec, curl_multi_exec, parse_ini_file, show_source, etc
Do not display errors when PHP starts:
display_startup_errors = Off
Do not show errors:
display_errors = Off
We log errors after turning off their display on the screen:
log_errors = On
File in which errors will be recorded:
error_log = "/usr/local/zend/var/log/php.log"
Do not write the same errors that occur in a specific file and line (ignore_repeated_source - you need to turn it off):
ignore_repeated_errors = Off
When enabled, it does not record the same errors that can occur in different files and lines, so turn off:
ignore_repeated_source = Off
Turn off HTML tags when viewing error messages:
html_errors = On
Adding data to the buffer:
implicit_flush = Off
Output buffering for all files, maximum number:
output_buffering = 4K
We use
the realpath cache , thereby reducing the number of stat () calls:
realpath_cache_size = 2M
Set the cache storage time to 30 minutes:
realpath_cache_ttl = 1800
Turn on circular link collector:
zend.enable_gc = On
Specify the maximum time during which data will be received on the server (POST, GET, HEAD), the time is measured from running PHP until the script runs:
max_input_time = 200
Specify the maximum runtime of the script (the value is not more than Apache - Timeout) - instead of
set_time_limit :
max_execution_time = 30
Allow uploading files to server:
file_uploads = On
The maximum size of memory that can be used by the script:
memory_limit = 256M
The maximum size of data sent by the POST method (should be less than - memory_limit):
post_max_size = 8M
The maximum size of the uploaded file (must be less - post_max_size):
upload_max_filesize = 2M
Number of files that can be transferred in one request:
max_file_uploads = 4
Path to the directory with extension modules:
extension_dir = "/usr/local/zend/lib/php_extensions"
Set the time zone:
date.timezone = Europe/Moscow
Data Type:
default_mimetype = "text/html"
Install the UTF-8 encoding:
default_charset = "UTF-8"
The order of processing variables is $ _COOKIE, $ _GET, $ _POST, $ _SERVER:
variables_order = "CGPS"
Do not declare argv and argc variables:
register_argc_argv = Off
The variables SERVER and ENV will be created at the time of use, which leads to an increase in performance:
auto_globals_jit = On
Turn off dynamic podgruzku, affects the security:
enable_dl = Off
We allow working with external files by URL:
allow_url_fopen = On
Disable the use of external files:
allow_url_include = Off
Install and configure MySQL.Tear off my.cnf:
[root@test ~]# nano /etc/mysql/my.cnf
The number of parallel processes that handle competitive requests to MySQL (the number of cores multiplied by two):
thread_concurrency = 4
Set the default encoding for new tables:
default-character-set = utf8
We will use InnoDB tables:
default-storage-engine = InnoDB
Set the size of the buffer indexes of tables in RAM (relevant for MyISAM tables):
key_buffer_size = 5M
Data buffer and table indexes - InnoDB:
innodb_buffer_pool_size = 300M
The maximum amount of RAM allocated for temporary tables created by MySQL:
tmp_table_size = 50M
The maximum number of open tables that will be in the cache:
table_open_cache = 64
The data buffer used to write information to disk is InnoDB:
innodb_log_buffer_size = 0M
Disable
query caching :
query_cache_size = 0
The size of the buffer used for sorting (ORDER BY) or grouping BY BY of data in each stream:
sort_buffer_size = 512K
We allocate memory for each stream for each table. Increasing this value can affect the speed of the query:
read_buffer_size = 512K
Affects the speed of sorting, for queries with - ORDER BY:
read_rnd_buffer_size = 1M
Buffer size using JOIN if indexes are not used in these queries:
join_buffer_size = 2M
Stack size, space for storing a list of tasks (open or close a table, execute a query, etc.):
thread_stack = 1M
Allocate memory for the connection buffer and its results, can be increased to max_allowed_packet:
net_buffer_length = 30K
The maximum size of data that can be transmitted in one request:
max_allowed_packet = 5M
Maximum number of simultaneous connections:
max_connections = 75
The number of connections that can queue:
back_log = 250
Play only localhost:
bind-address = 127.0.0.1
Do not use TCP / IP connections, transfer data through a socket:
skip-networking
To calculate the approximate consumption of RAM on the MySQL server (as I understand it), you can use the following formula (do not forget that the Apache server has already allocated 750 MB and still need to leave the Nginx server):
key_buffer_size + innodb_buffer_pool_size + tmp_table_size + ((sort_buffer_size + read_buffer_size + read_rnd_buffer_size + join_buffer_size + thread_stack) × max_connections) = ?
A little about security.It is undesirable to enter as root, so we create a new user:
[root@test ~]# adduser newuser
We set the password to the user - newuser:
[root@test ~]# passwd newuser
Install the sudo package:
[root@test ~]# yum install sudo
We bring a new user to sudo:
[root@test ~]# gpasswd -a newuser wheel
If you connect to SSH via port 22, then you need to change it, open sshd_config:
[root@test ~]# nano /etc/ssh/sshd_config
We change the port to any free one (do not forget to close port 22 using the firewall installed on your site and open a new one, for example 54139):
Port 54139
Forbid trying to log in with an empty password:
PermitEmptyPasswords no
Denying access to the root terminal:
PermitRootLogin no
Let us allow only the new user to log in to the terminal -
newuser:
AllowUsers newuser
Reboot ssh:
[root@test ~]# service sshd restart
PS You can use Nginx with php-fpm, but there is such an opinion that with properly configured Apache, there is not much difference in performance.
We wanted to pay special attention to the security and performance of the server, so if you find any errors or shortcomings, please write this in the comments and, if necessary, we will make changes to the article.