LEMP Stack Installation And Configuration On CentOS 7

Table of Contents

This article documents how to install, configure LEMP (NginxMySQLPHP) stack in CentOS 7 which SELinuxFirewalld has been enabled.

I wrote some shell script to automate operation, the project is hosted on GitLab. But PHP is still need to installed manually currently.

If SELinux is enabled, it may causes NginxMySQL not work properly. I have solved these problems in my shell script.

Attention: If your current user is a normal user, you need to use sudo to execute the following commands.

Overview

VPS public ip 104.236.100.67

Version info

Info Details
OS Version CentOS Linux release 7.4.1708 (Core)
Kernel Version 3.10.0-693.21.1.el7.x86_64
PHP 7.2.4
Nginx 1.14.0
MySQL 5.7.22

Default config file path

Software Configuration Files
PHP /etc/php.ini, /etc/php.d/
PHP-FPM /etc/php-fpm.conf, /etc/php-fpm.d/www.conf
Nginx /etc/nginx/nginx.conf, /etc/nginx/conf.d/*.conf
MySQL /etc/my.cnf, /etc/my.cnf.d, ~/.my.cnf

Version Check Command

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# - nginx
sudo nginx -V 2>&1 | awk -v FS='/' '{print $NF;exit}'
sudo nginx -V 2>&1 | sed -r -n '1 [email protected]*/(.*)@\[email protected]'
# For Bash 4+  2>&1 | == |&

# - mysql
mysql -V | sed -r -n '[email protected]*Distrib (.*),.*@\[email protected]'

# - php
php -v 2> /dev/null | awk '{print $2;exit}'

Conf Path Check Command

1
2
3
4
5
6
7
8
9
# - nginx
sudo nginx -V 2>&1 | sed -r -n '[email protected]*conf-path=(.*) --error.*@\[email protected]'

# - mysql
mysql --help | awk '$0~/Default options/{getline;print}'
mysqladmin --help | awk '$0~/Default options/{getline;print}'

# - php
php -i 2> /dev/null | awk '$0~/^Loaded Configuration File/{print $NF}'

System Initialization

System initialization is through Shell script, the code is hosted on GitLab, usage info

1
2
3
4
# curl -fsL / wget -qO-

# if need help info, specify '-h'
curl -fsL https://gitlab.com/MaxdSre/axd-ShellScript/raw/master/assets/gnulinux/gnuLinuxPostInstallationConfiguration.sh | sudo bash -s --

Using flag -Z to choose the type of SELinux (permissive/enforcing/disabled). Firewalld is enabled and open SSH port by default.

The status of SELinuxFirewalld

SELinux status

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      28

Firewall status

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: dhcpv6-client
  ports: 22/tcp
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks: echo-request echo-reply timestamp-reply timestamp-request
  rich rules:

Repository

These repositories are used by LEMP stack.

Nginx

Installing and configuing Nginx

Nginx Installation

Installing Nginx via shell script, including common configuration, optimization, the code is hosted on GitLab.

1
2
3
4
# curl -fsL / wget -qO-

# if need help info, specify '-h'
curl -fsL https://gitlab.com/MaxdSre/axd-ShellScript/raw/master/assets/software/NginxWebServer.sh | sudo bash -s --

Here choosing mainline version via flag -t m, default is stable version. If you wanna open web server port (80) in firewall, just specify flag -f.

Nginx Version Info

1
2
# nginx -v
nginx version: nginx/1.14.0

Firewall rule

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: dhcpv6-client
  ports: 22/tcp 80/tcp 443/tcp
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks: echo-request echo-reply timestamp-reply timestamp-request
  rich rules:

The default root directory of Nginx is /usr/share/nginx/html/. Make sure the ownergroup is nginx, or it may occurs error 403 Forbidden

1
sudo chown -R nginx:nginx /usr/share/nginx/html/

MySQL

Installing and configuing MySQL

MySQL Installation

Installing MySQL via shell script, including common configuration, optimization, the code is hosted on GitLab. This script supports distro Debian/Ubuntu/CentOS/Fedora/OpenSUSE/SLES, database variant MySQLMariaDBPercona.

1
2
3
4
# curl -fsL / wget -qO-

# if need help info, specify '-h'
curl -fsL https://gitlab.com/MaxdSre/axd-ShellScript/raw/master/assets/software/MySQLVariants.sh | sudo bash -s --

Here choosing MySQL 5.7 via flag -t mysql-v 5.7. If you wanna MySQL server port (3306) in firewall, just specify flag -f.

Starting service if it is not started.

1
2
3
4
sudo systemctl status mysqld.service

# start service
sudo systemctl start mysqld.service

MySQL Version Info

1
2
# mysql --version
mysql  Ver 14.14 Distrib 5.7.22, for Linux (x86_64) using  EditLine wrapper

User name, password is stores in file ~/.my.cnf, password is strong random password generated by custom function.

1
2
3
4
5
6
7
# cat .my.cnf
[client]
user=root
password="fEf}db)coQfvV)hQ$GX@F!Pcefi!Kt{ICw<1"

[mysql]
prompt=MySQL [\d]>\_

Directly logging into MySQL interactive interface via command mysql

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.22-log MySQL Community Server (GPL)

Copyright (c) 2000, 2018, 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
owners.

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

MySQL [(none)]> select version();
+------------+
| version()  |
+------------+
| 5.7.22-log |
+------------+
1 row in set (0.00 sec)

MySQL [(none)]> select user();
+----------------+
| user()         |
+----------------+
| root@localhost |
+----------------+
1 row in set (0.00 sec)

MySQL [(none)]>

Create Remote Login User

It is not recommended to use account root to login remotely. Here I create a normal user axdlog with password [email protected].

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- create new user
MySQL [(none)]> create user 'axdlog'@'%' identified by '[email protected]';
Query OK, 0 rows affected (0.00 sec)
-- grant privilege
MySQL [(none)]> grant all on *.* to 'axdlog'@'%';
Query OK, 0 rows affected (0.00 sec)
-- flush privilege
MySQL [(none)]> flush privileges;
Query OK, 0 rows affected (0.01 sec)

MySQL [(none)]> select user,host from mysql.user where user='axdlog';
+--------+------+
| user   | host |
+--------+------+
| axdlog | %    |
+--------+------+
1 row in set (0.00 sec)

MySQL [(none)]> show grants for 'axdlog'@'%'\G
*************************** 1. row ***************************
Grants for axdlog@%: GRANT ALL PRIVILEGES ON *.* TO 'axdlog'@'%'
1 row in set (0.00 sec)

MySQL [(none)]>

PHP

Installing and configuing PHP

REMI Repo

My initialization script has install epel repo. If you have not installed it, using the following command to install it:

1
sudo yum install -y epel-release

Installing REMI repo

1
sudo yum install -y http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

There are some repo file prefixing with remi in directory /etc/yum.repos.d/ after installation is finished: remi-php54.reporemi-php70.reporemi-php71.reporemi-php72.reporemi.reporemi-safe.repo

The configuration directives of PHP 5.5PHP 5.6 are listed in file remi.repo.

Here installing the latest release version 7.2.4, so I need to enable file remi-php72.repo first. What I need to do is change the value of enabled to 1 (default is enabled=0 meaning disable) .

1
2
3
4
5
6
7
sudo sed -r -i '/^\[remi-php72\]$/,/^$/{/^enabled=/{[email protected]^([^=]+=).*@\[email protected];}}' /etc/yum.repos.d/remi-php72.repo

# update cache
sudo yum makecache fast

# extract available pack list prefixing with php72-
yum info php72-* | awk 'match($0,/^Name[[:space:]]+:/){print gensub(/^[^:]+:[[:space:]]*(.*)$/,"\\1","g",$0)}'

PHP Installation

The prefix of PHP packages is php72- (e.g. php72-php-mysqlnd). If listing them one by one is too troublesome. Directive --enablerepo can solve this problem (pack name still is php-mysqlnd).

Choosing PHP-FPM to manage PHP, so it is need to install php-fpm, database drive chooses php-mysqlnd.

1
sudo yum --enablerepo=remi-php72 install -y php php-common php-devel php-fpm php-mysqlnd php-mbstring php-xml php-pecl-mcrypt php-pecl-apc php-cli php-pear php-pdo php-bcmath php-ctype php-opcache php-gd php-json

Listing installed modules

1
php --modules

Config file path

  • /etc/php.ini
  • /etc/php.d/
  • /etc/php-fpm.conf
  • /etc/php-fpm.d/

PHP Configuration

Modifing PHP config file /etc/php.ini

1
2
# backup
sudo cp -p /etc/php.ini{,.bak}

Directive list

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Close Nginx file type error resolve
cgi.fix_pathinfo=0
# timezone
date.timezone = Asia/Singapore
# Disable show PHP version info
expose_php = Off
# Disable PHP short tag
short_open_tag = Off
# Disable function list
disable_functions =

You may consider using the following method to operate quickly

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
funcPHPConfiguration(){
    local l_item=${1:-}
    local l_val=${2:-}
    local l_path=${3:-'/etc/php.ini'}
    [[ -f "${l_path}" && -n "${l_item}" && -n "${l_val}" ]] && sudo sed -r -i '/^;?[[:space:]]*'"${l_item}"'[[:space:]]*=/{[email protected]^;?[[:space:]]*([^=]+=).*[email protected]\1 '"${l_val}"'@g;}' "${l_path}"
}

funcPHPConfiguration 'cgi.fix_pathinfo' '0'
funcPHPConfiguration 'date.timezone' "Asia/Singapore"
funcPHPConfiguration 'expose_php' 'Off'
funcPHPConfiguration 'short_open_tag' 'Off'

disable_function_list="passthru,exec,system,chroot,scandir,chgrp,chown,shell_exec,proc_open,proc_get_status,ini_alter,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,stream_socket_server,escapeshellcmd,dll,popen,disk_free_space,checkdnsrr,checkdnsrr,getservbyname,getservbyport,disk_total_space,posix_ctermid,posix_get_last_error,posix_getcwd, posix_getegid,posix_geteuid,posix_getgid, posix_getgrgid,posix_getgrnam,posix_getgroups,posix_getlogin,posix_getpgid,posix_getpgrp,posix_getpid, posix_getppid,posix_getpwnam,posix_getpwuid, posix_getrlimit, posix_getsid,posix_getuid,posix_isatty, posix_kill,posix_mkfifo,posix_setegid,posix_seteuid,posix_setgid, posix_setpgid,posix_setsid,posix_setuid,posix_strerror,posix_times,posix_ttyname,posix_uname"
funcPHPConfiguration 'disable_functions' "${disable_function_list}"

Attention: Composer relies on function proc_openproc_get_status, using the following command to enable them

1
sudo sed -r -i '/disable_functions[[:space:]]*=/{[email protected](proc_open|proc_get_status),[email protected]@g;}' /etc/php.ini

PHP-FPM Configuration

Using the following commands to manage PHP-FPM service

1
sudo systemctl status/start/stop/enable/disable php-fpm.service

If the server is started successfully, PHP-FPM will generate a temporary sub-directory /var/run/php-fpm/ (On Debian the directory path is /var/run/php/). This path will be used while setting socket path in Nginx.

Attention: The default user, group of directory /var/lib/php/session/ is root. It is recommended that changing them to nginx:

1
[[ -d /var/lib/php/session ]] && sudo chown -R nginx:nginx /var/lib/php/session

PHP-FPM configuration file /etc/php-fpm.d/www.conf, modifing these directives

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
;listen = 127.0.0.1:9000
listen = /var/run/php-fpm/php-fpm.sock

# if not specify Nginx, Nginx can not listen php-fpm.sock
;listen.owner = nobody
;listen.group = nobody
;listen.mode = 0660
listen.owner = nginx
listen.group = nginx
listen.mode = 0660

;user = apache
user = nginx

;group = apache
group = nginx

You may consider using the following method to operate quickly

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
funcPHPConfiguration(){
    local l_item=${1:-}
    local l_val=${2:-}
    local l_path=${3:-'/etc/php-fpm.d/www.conf'}
    [[ -f "${l_path}" && -n "${l_item}" && -n "${l_val}" ]] && sudo sed -r -i '/^;?[[:space:]]*'"${l_item}"'[[:space:]]*=/{[email protected]^;?[[:space:]]*([^=]+=).*[email protected]\1 '"${l_val}"'@g;}' "${l_path}"
}

funcPHPConfiguration 'listen' '/var/run/php-fpm/php72-fpm.sock'
funcPHPConfiguration 'listen.owner' 'nginx'
funcPHPConfiguration 'listen.group' 'nginx'
funcPHPConfiguration 'listen.mode' '0660'
funcPHPConfiguration 'user' 'nginx'
funcPHPConfiguration 'group' 'nginx'

Please make sure Nginx has been installed, or PHP-FPM will fail to start. Error log info is stored in file /var/log/messages.

1
2
sudo systemctl start php-fpm
sudo systemctl enable php-fpm

PHP-FPM is running now

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# ls /var/run/php-fpm/php72-fpm.sock
/var/run/php-fpm/php72-fpm.sock

# systemctl status php-fpm
● php-fpm.service - The PHP FastCGI Process Manager
   Loaded: loaded (/usr/lib/systemd/system/php-fpm.service; disabled; vendor preset: disabled)
   Active: active (running) since Thu 2018-04-12 15:04:07 EDT; 4s ago
 Main PID: 29046 (php-fpm)
   Status: "Ready to handle connections"
   CGroup: /system.slice/php-fpm.service
           ├─29046 php-fpm: master process (/etc/php-fpm.conf)
           ├─29047 php-fpm: pool www
           ├─29048 php-fpm: pool www
           ├─29049 php-fpm: pool www
           ├─29050 php-fpm: pool www
           └─29051 php-fpm: pool www

Apr 12 15:04:07 centos7 systemd[1]: Starting The PHP FastCGI Process Manager...
Apr 12 15:04:07 centos7 systemd[1]: Started The PHP FastCGI Process Manager.

Nginx Configuration

There are directives about PHP existed in shell script, just uncomment them.

Searching PHP FPM StartPHP FPM End in file /etc/nginx/conf.d/default.conf, uncommenting the section location, changing the socket address of fastcgi_pass to /var/run/php-fpm/php72-fpm.sock which is defined in file /etc/php-fpm.d/www.conf.

You may consider using the following method to operate quickly

1
2
3
4
# socket file path
php_fpm_socket_path='/var/run/php-fpm/php72-fpm.sock'

sudo sed -r -i '/PHP FPM Start/,/PHP FPM End/{/PHP FPM/!{[email protected]# @@g}; /fastcgi_pass/{[email protected]^([^:]+:)[^\;]*(.*)[email protected]\1'"${php_fpm_socket_path}"'\[email protected];};}' /etc/nginx/conf.d/default.conf

The final directives

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# PHP FPM Start
location ~ \.php$ {
    #try_files $uri = 404;
    root   /usr/share/nginx/html;
    fastcgi_pass unix:/var/run/php-fpm/php72-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}
# PHP FPM End

Attention: $document_root is default point to /etc/nginx/html/, you need to specify root path in location ~ \.php$.

Using nginx -t to check Nginx config file syntax (need root privilege).

1
2
3
# sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Important: Must restart Nginx, PHP-FPM service to make configuration change effect. Or Nginx will fails to interpret PHP code, and the PHP file will by downloaded directly.

1
2
3
# restart service
sudo systemctl restart nginx
sudo systemctl restart php-fpm

phpinfo()

PHP has a function phpinfo() to list all PHP releated info.

Creating file /usr/share/nginx/html/index.php

1
2
3
4
target_path='/usr/share/nginx/html/index.php'
sudo bash -c 'echo -e "<?php\nphpinfo();\n?>" > '${target_path}''
sudo chown nginx:nginx "${target_path}"
sudo chmod 640 "${target_path}"

Viewing via web browser

Connection Testing

Database Connection

If you’re remotely connect MySQL, please make sure directive bind_address in file /etc/my.cnf is not bind local address 127.0.0.1 or localhost. Or it will refuse to connect.

Local Login

1
2
3
4
5
6
7
8
9
MySQL [(none)]> select user(),now();
+----------------+---------------------+
| user()         | now()               |
+----------------+---------------------+
| root@localhost | 2018-04-12 15:31:32 |
+----------------+---------------------+
1 row in set (0.00 sec)

MySQL [(none)]>

Romote Login

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌─[maxdsre@Stretch][~]
└──╼ $mysql -uaxdlog -p -h 104.236.100.67 -P 3306
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.22-log MySQL Community Server (GPL)

Copyright (c) 2000, 2018, 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
owners.

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

MySQL [(none)]> select user(),now();
+------------------------+---------------------+
| user()                 | now()               |
+------------------------+---------------------+
| axdlog@18.233.165.46   | 2018-04-12 15:39:05 |
+------------------------+---------------------+
1 row in set (0.01 sec)

MySQL [(none)]> \q
Bye

Web Server

Creating file db.php under Nginx root directory /usr/share/nginx/html/ (Make sure user nginx has read privilege) to test if LEMP stack is working well or not.

You may consider using the following method to operate quickly

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
target_path='/usr/share/nginx/html/db.php'

sudo bash -c "cat > ${target_path}" << EOF
<?php
    # Database connection test
    try {
        \$pdo = new PDO('mysql:host=localhost;port=3306','axdlog','[email protected]');
        \$pdo->exec('set names utf8');
    } catch (Exception \$e) {
        echo 'Connection Failure, Error message: '.\$e->getMessage();
    }

    \$sql = "select user,host from mysql.user";
    \$stmt = \$pdo->prepare(\$sql);
    \$stmt->execute();
    \$rows = \$stmt->fetchAll(PDO::FETCH_ASSOC);
    echo '<pre>';
    print_r(\$rows);

    # Print PHP info
    #phpinfo();
?>
EOF

sudo chown nginx:nginx "${target_path}"
sudo chmod 640 "${target_path}"

Details

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
    # Database connection test
    try {
        $pdo = new PDO('mysql:host=localhost;port=3306','axdlog','[email protected]');
        $pdo->exec('set names utf8');
    } catch (Exception $e) {
        echo 'Connection Failure, Error message: '.$e->getMessage();
    }

    $sql = "select user,host from mysql.user";
    $stmt = $pdo->prepare($sql);
    $stmt->execute();
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    echo '<pre>';
    print_r($rows);

    # Print PHP info
    #phpinfo();
?>

Inputing url http://{IPADDR}(:PORT)/info.php in browser ({IPADDR} is target host ip address).

  • Local host, specify localhost127.0.0.1
  • Remote host, specify public ip of remote hsot, if default port is not 80, manually specify it;

Here the url is http://104.236.100.67/info.php

Connecting via Shell terminal

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌─[[email protected]][~]
└──╼ $curl http://104.236.100.67/info.php
Array
(
    [0] => Array
        (
            [user] => axdlog
            [host] => %
        )

    [1] => Array
        (
            [user] => mysql.session
            [host] => localhost
        )

    [2] => Array
        (
            [user] => mysql.sys
            [host] => localhost
        )

    [3] => Array
        (
            [user] => root
            [host] => localhost
        )

)
┌─[[email protected]][~]
└──╼ $

Connecting via web browser

Nginx Log

Nginx log format in Nginx access log

“12/Apr/2018:16:11:37 -0400” remote_addr=18.233.165.46 request_method=GET request=“GET /info.php HTTP/1.1” request_length=354 status=200 bytes_sent=347 body_bytes_sent=137 http_referer=“-” http_user_agent=“Mozilla/5.0 (X11; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0” request_time=0.002 upstream_addr=unix:/var/run/php-fpm/php72-fpm.sock upstream_status=200 upstream_cache_status=- upstream_response_time=0.002 upstream_connect_time=0.000 upstream_header_time=0.002 msec=1523563897.481 pipe=.gzip_ratio=3.26

Change Logs

  • 2016.03.11 18:18 Fri Asia/Beijing
    • 初稿完成
  • 2016.03.23 15:48 Wed Asia/Beijing
    • 內容更新,相關配置參數
  • 2016.03.30 21:56 Wed Asia/Beijing
    • 添加Mounting Partitions
  • 2016.08.19 10:41 Fri Asia/Shanghai
    • 添加參數worker_rlimit_nofile
  • 2016.12.08 14:18 Thu Asia/Shanghai
    • MariaDB配置文件/etc/my.cnf參數優化
  • 2016.12.20 23:18 Tue Asia/Shanghai
    • Nginx參數配置優化
  • 2016.12.21 11:33 Wed Asia/Shanghai
    • 添加Security Optimization安全相關
  • 2018.04.12 16:38 Thu America/Boston
    • 內容更新,勘誤,遷移到新Blog
Show Disqus Comments