Setting up a LEMP server to run WordPress CMS on AlmaLinux 9, RockyLinux 9 or CentOS Stream 9

Setting up a LEMP server to run WordPress CMS

Setting up a LEMP server to run WordPress CMS

In the second part of the article, we will consider setting up a LEMP server to work with projects using the example of CMS WordPress. We will conduct a step-by-step setup, discuss the automation of the process without using popular hosting panels and, as a bonus, present improvements to enhance WordPress security. This includes setting up a virtual host in Nginx and a PHP interpreter for a specific site. At the same time, we will not consider setting up security for the entire server, as this is a topic for a separate article.

In this article we will consider:

  1. Creating a Linux User

  2. Setting up a virtual host

  3. Checking PHP operation

  4. Working with MariaDB database

  5. Let's Encrypt Setup

  6. Installing and Configuring Fail2ban

Before reading this article, it is recommended to read the first part – Part 1: Installing LEMP stack on AlmaLinux 9, RockyLinux 9 and CentOS Stream 9where the step-by-step installation process was described in detail.

Creating a Linux User

This script creates a new user for the web server and configures the appropriate settings for SSH/SFTP connection.

1. Copy the script shown below:

#!/bin/bash
# Copyright (C) 2024 
# Автор: Крячко Алексей
# Электронная почта: admin@unixweb.info 
# Веб-сайт: https://unixweb.info 
# GitHub: https://github.com/unixweb-info 

# Имя пользователя
username="www-root"

# Проверяем установку пакета curl
if ! command -v curl &> /dev/null; then
    echo "Установка пакета curl..."
    dnf install -y curl
fi

# Создаем пользователя с домашним каталогом и оболочкой
sudo useradd -m -s /bin/bash -d /var/www/$username $username
if [ $? -ne 0 ]; then
    echo "Ошибка при создании пользователя $username."
    exit 1
fi

# Создаем каталог для логов
sudo mkdir -p /var/www/$username/data/logs
if [ $? -ne 0 ]; then
    echo "Ошибка при создании каталога для логов."
    exit 1
fi

# Устанавливаем владельца для каталога пользователя
sudo chown -R $username:$username /var/www/$username
if [ $? -ne 0 ]; then
    echo "Ошибка при установке владельца для каталога пользователя."
    exit 1
fi

# Читаем пароль, не отображая его на экране
read -s -p "Введите пароль: " password
echo -e "\n"

# Устанавливаем пароль для пользователя
echo "$password" | sudo passwd --stdin "$username"
if [ $? -ne 0 ]; then
    echo "Ошибка при установке пароля для пользователя."
    exit 1
fi

# Получаем IP-адрес на интерфейсе eth0
#IP_ADDRESS=$(ip -o -4 addr show | awk '!/127.0.0.1/ {print $4}' | cut -d/ -f1) # Функция не актуальна если сервер работает за NAT
# Получаем IP-адрес ервера с помощью команды curl http://ident.me.
IP_ADDRESS=$(sudo -u www-root curl -s http://ident.me)

# Получаем номер порта SSH из файла конфигурации
SSH_PORT=$(awk '/^\s*Port\s+[0-9]+/ {print $2}' /etc/ssh/sshd_config | head -1)

# Если порт не найден (пустое значение), попробуем найти закомментированный порт
if [ -z "$SSH_PORT" ]; then
    SSH_PORT=$(awk '/^\s*#?\s*Port\s+[0-9]+/ {print $2}' /etc/ssh/sshd_config | head -1)
fi

# Проверяем, что переменная не пуста
if [ -z "$SSH_PORT" ]; then
    echo "Ошибка: Не удалось получить номер порта SSH."
    exit 1
fi
echo -e "Пользователь $username успешно добавлен! \nПожалуйста, сохраните данные для SSH/SFTP подключения:\n- IP-адрес: $IP_ADDRESS\n- SSH/SFTP порт: $SSH_PORT\n- Пользователь: $username\n- Пароль: $password"
  1. Save the file, for example, under the name create_user.shhaving previously copied the contents of the script and exit the editor.

  2. Make the script executable and then run it:

chmod +x ./create_user.sh && sudo ./create_user.sh

It is important to save the entered data, as it will be needed to connect to the site via the SFTP protocol.

[unixweb@localhost ~]# sudo ./create_user.sh
useradd: warning: the home directory /var/www/www-root already exists.
useradd: Not copying any file from skel directory into it.
Creating mailbox file: File exists
Введите пароль: 
Changing password for user www-root.
passwd: all authentication tokens updated successfully.
Пользователь www-root успешно добавлен! 
Пожалуйста, сохраните данные для SSH/SFTP подключения:
- IP-адрес: 1.2.3.4
- SSH/SFTP порт: 22
- Пользователь: www-root
- Пароль: !Your Password!
[unixweb@localhost ~]#

Actions performed by the script:

  1. Installing packages: First, the script checks for the presence of the package curlIf the package is not installed, the function installs it.

  2. Creating a new user:
    Defines the username www-root.
    Creates a user with a home directory /var/www/www-rootshell /bin/bashusing the command useradd.
    If the command fails, the script stops executing and displays an error message.

  3. Creating a directory for logs:
    Creates a directory for logs /var/www/www-root/data/logs.
    If the command fails, the script stops executing and displays an error message.

  4. Setting the owner for the user directory:
    Sets the owner and group for a directory. /var/www/www-root according to the user www-root.
    If the command fails, the script stops executing and displays an error message.

  5. Setting a password for a new user:
    Reads the password without displaying it on the screen.
    Sets a password for the user www-root using the command passwd.
    If the command fails, the script stops executing and displays an error message.

  6. Getting an IP address:
    Getting the server IP address using the command curl http://ident.me.

  7. Getting the SSH port number:
    Extracts the SSH port number from the configuration file /etc/ssh/sshd_config using the command awk.
    If the port number is not found (empty value), tries to find a commented out port.
    If the port is not found after both attempts, the script stops executing and displays an error message.

  8. Displaying information about a new user:
    Displays information about the new user, including IP address, port SSH/SFTPusername and password.

Setting up a virtual host

Create a virtual host and configure php-fpm in three easy steps

Now you need to configure Nginx to work with PHP, create a virtual host and configure php-fpm. Carefully study the contents of this script.

1. Copy the script shown below:

#!/bin/bash
# Copyright (C) 2024 
# Автор: Крячко Алексей
# Электронная почта: admin@unixweb.info 
# Веб-сайт: https://unixweb.info 
# GitHub: https://github.com/unixweb-info 

# Настройка SELinux — это важная и сложная тема, которая заслуживает отдельной публикации.
# Переключаем SELinux в режим permissive. 
setenforce 0

# Переменная domain задает название вашего домена, например, example.com, замените значение на имя вашего домена. 
domain="example.com" 

# Переменная username задает имя пользователя, например, www-root, ранее мы создавали этого пользователя.
username="www-root"

# Переменная php_version задает версию PHP интерпретатора, установленного в системе (например, PHP 8.1), задаем 81
php_version="81"

# Проверяем, существует ли каталог /etc/nginx/sites-available
[ ! -d "/etc/nginx/sites-available" ] && \
# Если каталог не существует, создаем его
mkdir "/etc/nginx/sites-available"

# Проверяем, существует ли каталог /etc/nginx/sites-enabled
[ ! -d "/etc/nginx/sites-enabled" ] && \
# Если каталог не существует, создаем его
mkdir "/etc/nginx/sites-enabled"

# Настройка виртуального хоста Nginx
cat << EOF | tee "/etc/nginx/sites-available/${domain}.conf"
server {
    listen 80;
    server_name ${domain};

    add_header Last-Modified \$date_gmt;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Xss-Protection "1; mode=block" always;
    add_header Content-Security-Policy "default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval';" always;

    access_log /var/www/${username}/data/logs/${domain}-access.log;
    error_log /var/www/${username}/data/logs/${domain}-error.log;

    charset off;
    gzip on;
    gzip_proxied expired no-cache no-store private auth;
    gzip_types text/css text/xml application/javascript text/plain application/json image/svg+xml image/x-icon;
    gzip_comp_level 6;

    set \$root_path /var/www/${username}/data/www/${domain};
    root \$root_path;
    disable_symlinks if_not_owner from=\$root_path;

# Предоставляем доступ к файлу robots.txt
location ~ /robots.txt {allow all;}

# Закрываем доступ ко всем расширениям, файлам и каталогам, которые могут быть использованы для компрометации WordPress.
location ~ /*\.(json|ini|log|md|txt|sql)|LICENSE {
    allow 1.2.3.4; # Замените на фактический IP администратора
    deny all;
    }
location ~ /\. {
    allow 1.2.3.4; # Замените на фактический IP администратора
    deny all;
    }

location ~* /(?:uploads|wflogs|w3tc-config|files)/.*\.php$ {
    allow 1.2.3.4; # Замените на фактический IP администратора
    deny all;
    access_log off;
    log_not_found off;
    }

location ~* /wp-includes/.*.php$ {
    allow 1.2.3.4; # Замените на фактический IP администратора
    deny all;
    access_log off;
    log_not_found off;
    }

location ~* /wp-content/.*.php$ {
    allow 1.2.3.4; # Замените на фактический IP администратора
    deny all;
    access_log off;
    log_not_found off;
    }

location ~* /themes/.*.php$ {
    allow 1.2.3.4; # Замените на фактический IP администратора
    deny all;
    access_log off;
    log_not_found off;
    }

location ~* /plugins/.*.php$ {
    allow 1.2.3.4; # Замените на фактический IP администратора
    deny all;
    access_log off;
    log_not_found off;
    }

    location = /xmlrpc.php {
    allow 1.2.3.4; # Замените на фактический IP администратора
    deny all;
    access_log off;
    log_not_found off;
    }

   # Безопасность для раздела администратора WordPress
    location ~* ^/(wp-admin/|wp-login\.php) {
        # Замените IP 1.2.3.4 на свой реальный IP, а затем удалите комментарии, чтобы заблокировать доступ к административному разделу сайта для посторонних.
        #allow 1.2.3.4;
        #deny all;
        try_files \$uri \$uri/ /index.php?\$args;
    location ~ \.php\$ {
        fastcgi_pass unix:/var/opt/remi/php$php_version/run/php-fpm/$domain.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        fastcgi_param SCRIPT_NAME \$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
    }
    }

    location / {
        index index.php index.html;
        try_files \$uri \$uri/ /index.php?\$args;
    }
    location ~ \.php\$ {
        include /etc/nginx/fastcgi_params;
        fastcgi_pass unix:/var/opt/remi/php${php_version}/run/php-fpm/${domain}.sock;
        fastcgi_param SCRIPT_FILENAME \$realpath_root\$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT \$realpath_root;
     }

    location ~* ^.+\.(jpg|jpeg|gif|png|svg|js|css|mp3|ogg|mpeg|avi|zip|gz|bz2|rar|swf|ico|7z|doc|docx|map|ogg|otf|pdf|tff|tif|txt|wav|webp|woff|woff2|xls|xlsx|xml)\$ {
        try_files \$uri \$uri/ /index.php?\$args;
        expires 30d;
    }

    location @fallback {
        fastcgi_pass unix:/var/opt/remi/php${php_version}/run/php-fpm/${domain}.sock;
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        include /etc/nginx/fastcgi_params;
    }
}
server {
    listen 80;
    server_name www.${domain};
    return 301 http://${domain}\$request_uri;
    access_log /var/www/${username}/data/logs/${domain}-access.log;
    error_log /var/www/${username}/data/logs/${domain}-error.log;
}
EOF

ln -s "/etc/nginx/sites-available/${domain}.conf" "/etc/nginx/sites-enabled/${domain}.conf"

nginx -t

# Настройка php-fpm для работы виртуального хоста

mkdir -p "/var/www/${username}/data/tmp"
chown "${username}:${username}" "/var/www/${username}/data/tmp"
mkdir -p "/var/www/${username}/data/www/${domain}"
chown "${username}:${username}" "/var/www/${username}/data/www/${domain}"

cat << EOF | tee "/etc/opt/remi/php${php_version}/php-fpm.d/${domain}.conf"
[${domain}]
user = ${username}
group = ${username}
listen = /var/opt/remi/php${php_version}/run/php-fpm/${domain}.sock
listen.owner = ${username}
listen.group = apache
listen.mode = 0660

pm = dynamic
pm.max_children = 20
pm.min_spare_servers = 6
pm.max_spare_servers = 10
pm.max_requests = 1000


php_admin_value[cgi.fix_pathinfo] = "Off"
php_admin_value[expose_php] = "Off"
php_admin_value[allow_url_include] = "Off"
php_admin_value[allow_url_fopen] = "Off"
php_admin_value[disable_functions] = "eval,system,shell_exec,passthru,proc_open,popen,expect_popen,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_signal_get_handler,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,exec,pcntl_exec,pcntl_getpriority,pcntl_setprioritypcntl_async_signals,pcntl_unshare"
php_admin_value[display_errors] = "Off"
php_admin_value[log_errors] = "On"
php_admin_value[date.timezone] = "Europe/Moscow"
php_admin_value[mail.add_x_header] = "On"
php_admin_value[max_execution_time] = "3600"
php_admin_value[max_input_time] = "3600"
php_admin_value[memory_limit] = "512M"
php_admin_value[max_input_vars] = "100000"
php_admin_value[opcache.blacklist_filename] = "opcache.blacklist_filename=/etc/opt/remi/php${php_version}/php.d/opcache*.blacklist"
php_admin_value[opcache.max_accelerated_files] = "100000"
php_admin_value[open_basedir] = "/var/www/${username}/data/www/${domain}:/var/www/${username}/data/tmp"
php_admin_value[output_buffering] = "4096"
php_admin_value[post_max_size] = "100M"
php_admin_value[sendmail_path] = "/usr/sbin/sendmail -t -i -f 'admin@${domain}'"
php_admin_value[session.save_path] = "/var/www/${username}/data/tmp"
php_admin_value[short_open_tag] = "On"
php_admin_value[upload_max_filesize] = "100M"
php_admin_value[upload_tmp_dir] = "/var/www/${username}/data/tmp"

catch_workers_output = no
access.format = "%{REMOTE_ADDR}e - [%t] \"%m %r%Q%q %{SERVER_PROTOCOL}e\" %s %{kilo}M \"%{HTTP_REFERER}e\" \"%{HTTP_USER_AGENT}e\""
access.log = /var/www/${username}/data/logs/${domain}-backend.access.log

php_admin_value[error_log] = /var/www/${username}/data/logs/${domain}-backend.error.log

EOF

# Перезапуск службы PHP-FPM
systemctl restart "php${php_version}-php-fpm.service"

# Команда gpasswd -a apache ${username} добавляет пользователя ${username} в группу apache. 
gpasswd -a apache ${username}

# Изменение прав доступа к каталогам, chmod g+x /var/www/${username} устанавливает права на выполнение для группы для каталога пользователя ${username}.
chmod g+x /var/www
chmod g+x /var/www/${username}
chmod g+x /var/www/${username}/data
chmod g+x /var/www/${username}/data/www
chmod g+x /var/www/${username}/data/www/${domain}

# Перезапуск службы Nginx
systemctl restart nginx.service
  1. Save the file, for example, under the name create_site.shhaving previously copied the contents of the script and exit the editor.

  2. Make the script executable and then run it:

chmod +x ./create_site.sh && sudo ./create_site.sh

This script implements a configuration for protecting WordPress files and directories. I want to share my developments with you.

You can also check out the official WordPress documentation on this topic by following this link: https://developer.wordpress.org/advanced-administration/server/web-server/nginx/

Script content:

1. Switch SELinux to mode permissive:

setenforce 0

This is necessary to prevent access issues during setup.

2. Definition of variables:

domain="example.com"
username="www-root"
php_version="81"
  • domain: Your domain name.

  • username: Username.

  • php_version: PHP interpreter version.

3. Creating Nginx configuration directories:

[ ! -d "/etc/nginx/sites-available" ] && mkdir "/etc/nginx/sites-available"
[ ! -d "/etc/nginx/sites-enabled" ] && mkdir "/etc/nginx/sites-enabled"

Checks for availability and creates catalogs /etc/nginx/sites-available And /etc/nginx/sites-enabled.

4. Configuring Nginx virtual host:

Creates a configuration file for the domain and populates it with settings:

cat << EOF | tee "/etc/nginx/sites-available/${domain}.conf"
server {
       ...
}
server {
       ...
}
EOF

The Nginx configuration file provides enhanced security for a WordPress CMS site. It restricts access to various types of files and directories that can be used to compromise:

  • Denies access to all files with extensions .json, .ini, .log, .md, .txt, .sql and the LICENSE file, except for the administrator's IP address.

  • Blocks access to hidden files and directories.

  • Prevents execution of PHP files in certain directories: uploads, wflogs, w3tc-config, files.

  • Restricts access to PHP files in the wp-includes, wp-content, themes, plugins directories.

  • Denies access to the xmlrpc.php file.

  • Provides additional security for the WordPress admin area by allowing access only from a specific IP address.

Important! Please note that in the current version of the configuration file, access to the administrative section of the WordPress CMS is open. Please read the comments in the script for further instructions on how to configure it.

5. Create a symbolic link to activate the virtual host:

ln -s "/etc/nginx/sites-available/${domain}.conf" "/etc/nginx/sites-enabled/${domain}.conf"

6. Check the configuration and restart Nginx:

nginx -t
systemctl restart nginx.service

7. Configuring PHP-FPM for a virtual host:

Creates the necessary directories and configures PHP-FPM:

mkdir -p "/var/www/${username}/data/tmp"
chown "${username}:${username}" "/var/www/${username}/data/tmp"
mkdir -p "/var/www/${username}/data/www/${domain}"
chown "${username}:${username}" "/var/www/${username}/data/www/${domain}"

Creates a PHP-FPM configuration file:

cat << EOF | tee "/etc/opt/remi/php${php_version}/php-fpm.d/${domain}.conf"
[${domain}]
...
EOF

Important! These are the PHP settings for security:

cgi.fix_pathinfo = "Off":
Disables support for path fixing in CGI scripts.
This prevents possible attacks related to file path handling.

expose_php = "Off":
Hides PHP version information in server response headers.
This helps reduce the risk of attacks related to known PHP vulnerabilities.

allow_url_include = "Off":
Prevents the use of functions that download files by URL (eg. include And require).
This prevents potential vulnerabilities related to remote code uploading.

allow_url_fopen = "Off":
Prevents the use of the function fopen to open files by URL.
This reduces the risk of executing malicious code from remote sources.

disable_functions:
Lists disallowed functions that cannot be called from PHP scripts.
In this case, dangerous functions such as eval, system, shell_exec and others.

display_errors = "Off":

  1. Restart PHP-FPM:

systemctl restart "php${php_version}-php-fpm.service"

9. The command 'gpasswd -a apache ${username}' is used to add the user ${username} to the Apache group. This allows the user to access the resources and rights granted by this group:

gpasswd -a apache ${username}

10. Changing access rights to directories:

chmod g+x /var/www
chmod g+x /var/www/${username}
chmod g+x /var/www/${username}/data
chmod g+x /var/www/${username}/data/www
chmod g+x /var/www/${username}/data/www/${domain}

11. Restart Nginx service:

systemctl restart nginx.service

This script will automatically configure Nginx and PHP-FPM virtual host for the specified domain, providing the necessary security and performance settings.

Checking PHP operation

To get a detailed list of PHP parameters, it is recommended to use the phpinfo() function. This is especially useful for debugging site settings after it has been deployed to the server. Replace the value “example.com” to your domain.

First, let's create a file info.php in your site's directory, for example, /var/www/www-root/data/www/example.com/to check if PHP is working:

sudo touch /var/www/www-root/data/www/example.com/info.php
sudo bash -c 'echo "<?php phpinfo(); ?>" > /var/www/www-root/data/www/example.com/info.php'
sudo chown www-root:www-root /var/www/www-root/data/www/example.com/info.php

Now, to view the full list of PHP settings on your server, follow the link like this http://example.com/info.php.

Checking the PHP interpreter on the server

Checking the PHP interpreter on the server

Important! Don't forget to delete this file after checking. Leaving it in the public domain can allow attackers to access information about your server and use it for attacks.

To delete the info.php file, run the following command:

sudo rm /var/www/www-root/data/www/example.com/info.php

The LEMP setup is complete, now you can start setting up your site.

Working with MariaDB database

Automating the process of setting up a database and managing users is an important part of server administration. This script is designed to simplify the creation of a database and user in MariaDB, which is a standard task when setting up servers for web applications.

Create a user and database in three easy steps

1. Copy the script shown below:

#!/bin/bash
# Copyright (C) 2024 
# Автор: Крячко Алексей
# Электронная почта: admin@unixweb.info 
# Веб-сайт: https://unixweb.info 
# GitHub: https://github.com/unixweb-info 

# Установка параметров по умолчанию
DB_CHARSET="utf8mb4"
DB_NAME="exampledb"
DB_USER="exampleuser"
DB_PASSWORD="!Example^Password!"

# Создание базы данных с указанным набором символов
/usr/bin/mariadb -u root -e "CREATE DATABASE $DB_NAME CHARACTER SET $DB_CHARSET;"

# Создание пользователя с заданным паролем
/usr/bin/mariadb -u root -e "CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD';"

# Предоставление всех привилегий пользователю на созданной базе данных
/usr/bin/mariadb -u root -e "GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';"

# Применение изменений привилегий
/usr/bin/mariadb -u root -e "FLUSH PRIVILEGES;"

# Отображение списка баз данных
echo "List of databases:"
/usr/bin/mariadb -u root -e "SHOW DATABASES;"

# Отображение списка пользователей
echo "List of users:"
/usr/bin/mariadb -u root -e "SELECT User, Host FROM mysql.user;"
  1. Save the file, for example, under the name create_db_user_and_database.shhaving previously copied the contents of the script and exit the editor.

  2. Make the script executable and then run it:

chmod +x ./create_db_user_and_database.sh && sudo ./create_db_user_and_database.sh

Description of the script

This script is designed to automate the creation of a database and user in MariaDB. It performs the following actions:

  1. Setting default parameters:
    DB_CHARSET="utf8mb4": Sets the character encoding for the database.
    DB_NAME="exampledb": Sets the database name.
    DB_USER="exampleuser": Sets the user name.
    DB_PASSWORD="!Example^Password!": Sets the password for the user.

  2. Database creation:

/usr/bin/mariadb -u root -e "CREATE DATABASE $DB_NAME CHARACTER SET $DB_CHARSET;"
/usr/bin/mariadb -u root -e "CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD';"
  1. Granting privileges:

/usr/bin/mariadb -u root -e "GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost';"
  1. Applying privilege changes:

/usr/bin/mariadb -u root -e "FLUSH PRIVILEGES;"
  1. Displaying a list of databases:

/usr/bin/mariadb -u root -e "SHOW DATABASES;"
  1. Displaying a list of users:

/usr/bin/mariadb -u root -e "SELECT User, Host FROM mysql.user;"

Result of the script:

[unixweb@localhost ~]# sudo ./create_db_user_and_database.sh
List of databases:
+--------------------+
| Database       	|
+--------------------+
| exampledb      	|
| information_schema |
| mysql          	|
| performance_schema |
| sys            	|
+--------------------+
List of users:
+-------------+-----------+
| User    	| Host  	|
+-------------+-----------+
| PUBLIC  	|       	|
| exampleuser | localhost |
| mariadb.sys | localhost |
| mysql   	| localhost |
| root    	| localhost |
+-------------+-----------+

User and database created successfully.

Please note that I did not describe the installation instructions for PHPMyAdmin, considering this step unsafe. If you install it, you must strictly limit access to PHPMyAdmin.

Let's Encrypt Setup

Let's Encrypt Automated Setup provides a script for installing and configuring Let's Encrypt to get free SSL/TLS certificates on your web server. I present a script for automating the setup of Let's Encrypt SSL/TLS certificates.

1. Copy the script shown below:

#!/bin/bash
# Copyright (C) 2024 
# Автор: Крячко Алексей
# Электронная почта: admin@unixweb.info 
# Веб-сайт: https://unixweb.info 
# GitHub: https://github.com/unixweb-info 
# Пожалуйста, перед выполнением скрипта замените example.com на ваш домен и admin@example.com на ваш email.
DOMAIN="example.com"
EMAIL="admin@example.com"

# Проверяем установку пакетов certbot, python3-certbot-nginx, curl и bind-utils
if ! command -v certbot &> /dev/null || ! rpm -q --quiet python3-certbot-nginx || ! command -v curl &> /dev/null || ! command -v dig &> /dev/null; then
    echo "Установка пакетов certbot, python3-certbot-nginx, curl и bind-utils..."
    dnf install -y certbot python3-certbot-nginx curl bind-utils
else
    echo "Пакеты certbot, python3-certbot-nginx, curl и bind-utils уже установлены."
fi

# Путь к файлу конфигурации certbot
CERTBOT_CONFIG_FILE="/etc/sysconfig/certbot"

# Проверяем существование файла и наличие строки POST_HOOK=""
if [ -f "$CERTBOT_CONFIG_FILE" ]; then
    # Проверяем, содержит ли файл строку POST_HOOK=""
    if grep -q "^POST_HOOK=\"\"" "$CERTBOT_CONFIG_FILE"; then
        # Заменяем строку POST_HOOK="" на POST_HOOK="--post-hook 'systemctl restart nginx'"
        sed -i "s|^POST_HOOK=\"\"|POST_HOOK=\"--post-hook 'systemctl restart nginx'\"|g" "$CERTBOT_CONFIG_FILE"
        echo "Обновлена переменная POST_HOOK в файле $CERTBOT_CONFIG_FILE."
    else
        echo "Переменная POST_HOOK уже установлена или не найдена в файле $CERTBOT_CONFIG_FILE, пропускаем."
    fi
else
    echo "Файл $CERTBOT_CONFIG_FILE не найден."
fi

# Включаем и запускаем таймер для автоматического обновления сертификатов
systemctl enable --now certbot-renew.timer

# Запрос A-записи для вашего домена
A_RECORD_DOMAIN=$(sudo -u www-root dig @8.8.8.8 A "$DOMAIN" +short)

# Запрос A-записи для www вашего домена
A_RECORD_WWW_DOMAIN=$(sudo -u www-root dig @8.8.8.8 A "www.$DOMAIN" +short)

# Узнать внешний IP сервера, проверка в случае если ваш сервер находится за NAT сетью
EXTERNAL_IP=$(sudo -u www-root curl -s http://ident.me)

# Проверяем, что A-записи совпадают с внешним IP
if [[ "$A_RECORD_DOMAIN" == "$EXTERNAL_IP" && "$A_RECORD_WWW_DOMAIN" == "$EXTERNAL_IP" ]]; then
    echo "A-записи корректно настроены. Выпускаем сертификат..."
    certbot --nginx --non-interactive --agree-tos --domains "$DOMAIN,www.$DOMAIN" --email "$EMAIL"
else
    echo "Настройте корректно A-записи $DOMAIN и www.$DOMAIN на IP-адрес $EXTERNAL_IP вашего сервера."
fi

# Показываем список всех таймеров
echo "Список таймеров для Let's Encrypt с помощью certbot:"

systemctl list-timers --all | grep certbot
  1. Save the file, for example, under the name install_certbot.shhaving previously copied the contents of the script and exit the editor.

  2. Make the script executable and then run it:

chmod +x ./install_certbot.sh && sudo ./install_certbot.sh

This script is designed to automatically configure Let's Encrypt on your server. Let's look at its functionality:

  1. Installing the required packages: First, the script checks for the presence of packages certbot, python3-certbot-nginx, curl And bind-utilsIf any of them are missing, he installs them.

  2. Setting POST_HOOK: The script checks that the variable POST_HOOK is empty. If it is empty, it adds a command to restart the service nginx.

  3. Update Timer: Script enables and starts the timer certbot-renew.timerwhich automatically renews Let's Encrypt certificates.

  4. Check A-records: The script queries A-records for your domain and www-domain, and determines the external IP address of your server. If the A-records match the external IP, the certificate is issued.

  5. List of timers: At the end, the script lists all timers associated with Let's Encrypt.

Installing and Configuring Fail2ban

Fail2Ban — is a powerful tool for protecting servers from attacks by automatically blocking IP addresses that try to connect too often to services such as SSH. Below is a script to automate the installation and configuration of Fail2Ban on a server with an operating system that supports package management via DNF (e.g. AlmaLinux 9, RockyLinux 9, CentOS Stream 9).

Install and configure Fail2ban in three easy steps

1. Copy the script shown below:

#!/bin/bash
# Copyright (C) 2024 
# Автор: Крячко Алексей
# Электронная почта: admin@unixweb.info 
# Веб-сайт: https://unixweb.info 
# GitHub: https://github.com/unixweb-info 

# Обновляем все пакеты на сервере
dnf update -y

# Устанавливаем Fail2Ban, если не установлен
if ! rpm -qa | grep -q '^fail2ban-'; then
    dnf install fail2ban -y
fi

# Проверяем, есть ли уже настройка [sshd]\nenabled=true в файле /etc/fail2ban/jail.d/00-firewalld.conf
if ! grep -q "^\[sshd\]" /etc/fail2ban/jail.d/00-firewalld.conf; then
    # Если настройки нет, добавляем её в файл
    echo -e "\n[sshd]\nenabled=true\nmaxretry=2" | tee -a /etc/fail2ban/jail.d/00-firewalld.conf >/dev/null
else
    # Если настройка уже есть, выводим сообщение
    echo "Config [sshd] already exists in /etc/fail2ban/jail.d/00-firewalld.conf"
fi

# Выводим сообщение об успешной установке и настройке Fail2Ban
echo -e "\nFail2Ban was successfully installed and configured."

# Включаем и запускаем сервис Fail2Ban
systemctl enable fail2ban

# Перезапускаем сервис Fail2Ban
systemctl restart fail2ban
sleep 1
# Проверяем статус Fail2Ban
echo -e "\nFail2Ban status:"
fail2ban-client status
  1. Save the file, for example, under the name install_and_setting_fail2ban.shhaving previously copied the contents of the script and exit the editor.

  2. Make the script executable and then run it:

chmod +x ./install_and_setting_fail2ban.sh && sudo ./install_and_setting_fail2ban.sh

Description of the script

This script is designed to automate the installation and basic configuration of Fail2Ban on a server with an operating system that uses the DNF package manager. Here is a detailed description of each step:

  1. Updating packages on the server:

dnf update -y

This command updates all installed packages on the server to the latest available versions.

  1. Installing Fail2Ban:

if ! rpm -qa | grep -q '^fail2ban-'; then
       dnf install fail2ban -y
fi

Checks if Fail2Ban is already installed (rpm -qa | grep -q '^fail2ban-'). If it is not installed (!), then Fail2Ban is installed from the EPEL repository.

  1. Adding a setting for SSH to the Fail2Ban configuration file:

if ! grep -q "^\[sshd\]" /etc/fail2ban/jail.d/00-firewalld.conf; then
    echo -e "\n[sshd]\nenabled=true\nmaxretry=2" | tee -a /etc/fail2ban/jail.d/00-firewalld.conf >/dev/null
else
    echo "Config [sshd] already exists in /etc/fail2ban/jail.d/00-firewalld.conf"
fi

Checks for the presence of the setting [sshd] in the Fail2Ban configuration file (/etc/fail2ban/jail.d/00-firewalld.conf). If the setting does not exist, it is added to the end of the file.

  1. Enabling and starting the Fail2Ban service:

systemctl enable --now fail2ban

Enables and immediately starts the Fail2Ban service. Option --now indicates the need to start the service immediately.

  1. Restarting the Fail2Ban service:

systemctl restart fail2ban

Restarts the Fail2Ban service to apply the new settings.

  1. Checking Fail2Ban status:

echo -e "\nFail2Ban status:"
fail2ban-client status

Displays the current status of Fail2Ban, showing whether the service is active and the number of IP addresses blocked.

This script is useful for a quick and easy installation of Fail2Ban with pre-configuration for SSH protection. It automates the installation, configuration and launch of Fail2Ban, which significantly reduces the time and simplifies the process of securing the server.

For a deep dive into the topic, it is recommended to carefully study the official documentation on Fail2ban.

Summarizing

In this two-part guide, we've walked through all the steps to install and configure the stack. LEMP (Linux, Nginx, MySQL/MariaDB, PHP).

It is important to note that this set of instructions creates a basic LEMP stack configuration. For a production environment, additional tuning and optimization may be required, including:

  • Additional performance optimization is necessary, as what has been done is not enough for high-load projects.

  • Setting up backup of the Internet project and database

  • Implementation of additional security measures

It is recommended to regularly update all system components and monitor the release of security patches. In addition, it is important to read the official documentation of each component for a deeper understanding of their functionality and customization options.

This guide, divided into two parts, provides a solid foundation for deploying web applications based on the LEMP stack, but keep in mind that each project may have its own nuances that may require additional configuration.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *