Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the acf domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php on line 6131

Deprecated: Creation of dynamic property ACF::$fields is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/fields.php on line 138

Deprecated: Creation of dynamic property acf_loop::$loops is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/loop.php on line 28

Deprecated: Creation of dynamic property ACF::$loop is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/loop.php on line 269

Deprecated: Creation of dynamic property ACF::$revisions is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/revisions.php on line 397

Deprecated: Creation of dynamic property acf_validation::$errors is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/validation.php on line 28

Deprecated: Creation of dynamic property ACF::$validation is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/validation.php on line 214

Deprecated: Creation of dynamic property acf_form_customizer::$preview_values is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/forms/form-customizer.php on line 28

Deprecated: Creation of dynamic property acf_form_customizer::$preview_fields is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/forms/form-customizer.php on line 29

Deprecated: Creation of dynamic property acf_form_customizer::$preview_errors is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/forms/form-customizer.php on line 30

Deprecated: Creation of dynamic property ACF::$form_front is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/forms/form-front.php on line 598

Deprecated: Creation of dynamic property acf_form_widget::$preview_values is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/forms/form-widget.php on line 34

Deprecated: Creation of dynamic property acf_form_widget::$preview_reference is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/forms/form-widget.php on line 35

Deprecated: Creation of dynamic property acf_form_widget::$preview_errors is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/forms/form-widget.php on line 36

Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the all-in-one-wp-migration domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php on line 6131

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php:6131) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/wp_plugin/wp_plugin.php on line 23

Deprecated: str_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/wp-super-cache/wp-cache-phase2.php on line 54

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php:6131) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/wp-super-cache/wp-cache-phase2.php on line 1539

Deprecated: strtolower(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/wp-super-cache/wp-cache-phase2.php on line 828

Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the rocket domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php on line 6131

Deprecated: Creation of dynamic property acf_field_oembed::$width is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/fields/class-acf-field-oembed.php on line 31

Deprecated: Creation of dynamic property acf_field_oembed::$height is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/fields/class-acf-field-oembed.php on line 32

Deprecated: Creation of dynamic property acf_field_google_map::$default_values is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/fields/class-acf-field-google-map.php on line 33

Deprecated: Creation of dynamic property acf_field__group::$have_rows is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/includes/fields/class-acf-field-group.php on line 31

Deprecated: Creation of dynamic property acf_field_clone::$cloning is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/pro/fields/class-acf-field-clone.php on line 34

Deprecated: Creation of dynamic property acf_field_clone::$have_rows is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-pro/pro/fields/class-acf-field-clone.php on line 35

Deprecated: Creation of dynamic property jh_acf_field_table::$settings is deprecated in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-content/plugins/advanced-custom-fields-table-field/class-jh-acf-field-table.php on line 23

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php:6131) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/rest-api/class-wp-rest-server.php on line 1902

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php:6131) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/rest-api/class-wp-rest-server.php on line 1902

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php:6131) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/rest-api/class-wp-rest-server.php on line 1902

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php:6131) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/rest-api/class-wp-rest-server.php on line 1902

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php:6131) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/rest-api/class-wp-rest-server.php on line 1902

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php:6131) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/rest-api/class-wp-rest-server.php on line 1902

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php:6131) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/rest-api/class-wp-rest-server.php on line 1902

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/functions.php:6131) in /var/www/vhosts/studiogo.tech/httpdocs/upcloudold/wp-includes/rest-api/class-wp-rest-server.php on line 1902
{"id":24871,"date":"2019-10-14T13:35:06","date_gmt":"2019-10-14T10:35:06","guid":{"rendered":"https:\/\/upcloud.com\/community\/tutorials\/install-wordpress-lemp-centos-8"},"modified":"2019-10-14T13:35:06","modified_gmt":"2019-10-14T10:35:06","slug":"install-wordpress-lemp-centos-8","status":"publish","type":"tutorial","link":"https:\/\/studiogo.tech\/upcloudold\/tutorial\/install-wordpress-lemp-centos-8\/","title":{"rendered":"How to install single node WordPress LEMP CentOS 8"},"content":{"rendered":"\n

WordPress is a popular CMS that can allow you to configure static or dynamic websites inclusive of e-commerce. If you are inclined to install for speed the LEMP stack would be preferable as Nginx performs approximately 2.5 faster than Apache. LEMP stands for Linux, Nginx (Engine-X), MySQL and PHP.<\/p>\n\n\n\n

Along with performance benefits and happier visitors, speed also impacts your SEO rankings. In this tutorial, we will be installing WordPress LEMP CentOS 8 with Nginx, MySQL 8, and PHP-FPM, an alternative PHP FastCGI implementation which is significantly faster than mod_PHP. We will also be proceeding through a few suggestions and alternative configurations for hardening, speed, and scalability.<\/p>\n\n\n\n

\n
Test hosting on UpCloud!<\/a><\/div>\n<\/div>\n\n\n\n

Step 1 Point DNS or Hosts File<\/h2>\n\n\n\n

You may wish to begin with your DNS settings. However, you can also begin without purchasing a domain name. A hosts file can be configured depending on your local machines operating system so you can view your web content from a browser before DNS propagation takes place.<\/p>\n\n\n\n

Typically for Mac and Linux users the following \/etc\/hosts<\/tt> file structure will map your IP address to the domain name. Windows and alternative OS users should review depending on the OS version.<\/p>\n\n\n\n

vi \/etc\/hosts\n94.237.112.110 example1.com\n94.237.112.110 www.example1.com<\/pre>\n\n\n\n

Step 2 Install Nginx<\/h2>\n\n\n\n
\"Nginx<\/figure>\n\n\n\n


Nginx is a highly performant open-source service that can be configured for a web server, caching, load balancing, media streaming and much more.<\/p>\n\n\n\n

We will be keeping things fairly simple by installing Nginx as a web server.<\/p>\n\n\n\n

yum install nginx -y<\/pre>\n\n\n\n

Once installed Nginx can be started with the following command.<\/p>\n\n\n\n

systemctl start nginx<\/pre>\n\n\n\n

To enable Nginx to start at system boot ensure the service is enabled.<\/p>\n\n\n\n

systemctl enable nginx<\/pre>\n\n\n\n

Ensure the service is functioning properly by checking the status.<\/p>\n\n\n\n

systemctl status nginx<\/pre>\n\n\n\n

Check you are running the desired version.<\/p>\n\n\n\n

nginx -v<\/pre>\n\n\n\n

RHEL 8 \/ CentOS 8 defaults do not have firewalld configured to serve web traffic, run the following commands to allow web server traffic for HTTP and https with permanent rules that will persist after a reboot.<\/p>\n\n\n\n

firewall-cmd --permanent --zone=public --add-service=http\nfirewall-cmd --permanent --zone=public --add-service=https<\/pre>\n\n\n\n

Reload the firewall daemon for the changes to occur.<\/p>\n\n\n\n

firewall-cmd --reload<\/pre>\n\n\n\n

Step 3 Install Percona Server MySQL 8<\/h2>\n\n\n\n
\"Percona<\/figure>\n\n\n\n

WordPress LEMP CentOS 8 installations frequently run forks of MySQL inclusive to MariaDB and Percona Server.<\/p>\n\n\n\n

The default Red Hat 8 Centos 8 repositories are currently prepared to install MariaDB and MySQL 8 typically outperforms MariaDB, yet Percona Server typically outperforms the MySQL Community Server while providing open-source enterprise features.<\/p>\n\n\n\n

MariaDB can be a replacement for MySQL and it is applicable for WordPress installations, yet it is also worth noting that MariaDB has some enhanced features, which do not exist in MySQL and migration back to MySQL might not always work. Many of the unique MariaDB features and storage engines that make MariaDB stand out will not be of use to a WordPress installation.<\/p>\n\n\n\n

MariaDB and Percona Server are heavily documented and both companies provide managed database support.<\/p>\n\n\n\n

Percona toolkit, in general, has many useful open-source tools depending on which path you choose. Percona Xtrabackup<\/a> and Mariabackup<\/a> hot backups can be very useful to avoid downtime without paying enterprise costs.<\/p>\n\n\n\n

As this is a single node installation we will be installing the database directly on the webserver. In many environments, it would be preferable to employ a decoupled architecture and have MySQL on its own instance. This way scaling web servers behind a load balancer and performance tuning would be an easier task upon growth.<\/p>\n\n\n\n

Install the Percona Server repository.<\/p>\n\n\n\n

yum install https:\/\/repo.percona.com\/yum\/percona-release-latest.noarch.rpm<\/pre>\n\n\n\n

Setup the repository.<\/p>\n\n\n\n

percona-release setup ps80<\/pre>\n\n\n\n

Disable the following as prompted.<\/p>\n\n\n\n

* Disabling all Percona Repositories On RedHat 8 systems it is needed to disable dnf mysql module to install Percona-Server Do you want to disable it? [y\/N] Y<\/pre>\n\n\n\n

Install Percona Server.<\/p>\n\n\n\n

yum install percona-server-server -y<\/pre>\n\n\n\n

Make a copy of your my.cnf<\/tt> file.<\/p>\n\n\n\n

cp \/etc\/my.cnf \/etc\/my.cnf.bak<\/pre>\n\n\n\n

Open the my.cnf<\/tt> file and uncomment the following line as MySQL 8 has changed the default value. Eventually, the community will update the mysqlnd<\/tt> client library to use the new encryption method and this configuration will no longer be required. At this current time, WordPress will throw database connection errors on authentication if you haven\u2019t uncommented this out before user creation.<\/p>\n\n\n\n

vi \/etc\/my.cnf \n<\/pre>\n\n\n

[mysqld]<\/p>\n\n\n\n

\ndefault_authentication_plugin=mysql_native_password\n<\/p>\n\n\n\n

Start the MySQL service.<\/p>\n\n\n\n

systemctl start mysql<\/pre>\n\n\n\n

Enable the MySQL service to start at boot.<\/p>\n\n\n\n

systemctl enable --now mysqld<\/pre>\n\n\n\n

Check the status of the MySQL service.<\/p>\n\n\n\n

systemctl status mysql<\/pre>\n\n\n\n

Obtain the temporary password from the mysqld.log.<\/p>\n\n\n\n

grep \"temporary password\" \/var\/log\/mysqld.log<\/pre>\n\n\n\n

Proceed with secure installation.<\/p>\n\n\n\n

mysql_secure_installation\n<\/pre>\n\n\n\n

Follow the prompts.<\/p>\n\n\n\n

Set root password? Y \nEnter password for user root: <Paste-copied-password>\nEstimated strength of the password: 100\nDo you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : Y\nRemove anonymous users? Y \nDisallow root login remotely? Y \nRemove test database and access to it? Y\nReload Privilege tables now? Y<\/pre>\n\n\n\n

Enter the MySQL shell.<\/p>\n\n\n\n

mysql -u root -p<\/pre>\n\n\n\n

Create the WordPress database.<\/p>\n\n\n\n

CREATE DATABASE wordpress;<\/pre>\n\n\n\n

Create the WordPress user inclusive of the desired password.<\/p>\n\n\n\n

CREATE USER wordpressuser@localhost IDENTIFIED BY 'enter-password-here<\/span>';<\/pre>\n\n\n\n

Grant permissions to the user on the WordPress database.<\/p>\n\n\n\n

GRANT ALL ON wordpress.* TO wordpressuser@localhost;<\/pre>\n\n\n\n

Proceed to flush the privileges.<\/p>\n\n\n\n

FLUSH PRIVILEGES;<\/pre>\n\n\n\n

Exit the shell.<\/p>\n\n\n\n

QUIT;<\/pre>\n\n\n\n

Step 4 Install PHP & PHP-FPM<\/h2>\n\n\n\n

For a clean and easily upgradable PHP configuration, we recommend using the default repository. You may consider newer versions in more bleeding edge repositories; though, it is also worth noting that alternative repositories may include much longer service names and alternative upgrade paths. It is also best practice to review deprecations for the version of PHP you are migrating from and to.<\/p>\n\n\n\n

To install PHP and related modules run the following command.<\/p>\n\n\n\n

yum install -y php php-mysqlnd php-fpm php-opcache php-gd php-xml php-mbstring<\/pre>\n\n\n\n

After the services and modules are installed start PHP-FPM.<\/p>\n\n\n\n

systemctl start php-fpm<\/pre>\n\n\n\n

Ensure the service is enabled at boot.<\/p>\n\n\n\n

systemctl enable php-fpm<\/pre>\n\n\n\n

Check the service status.<\/p>\n\n\n\n

systemctl status php-fpm<\/pre>\n\n\n\n

The default PHP-FPM configuration file is set to run as the apache user, locate and alter the following entries as shown below.<\/p>\n\n\n\n

vi \/etc\/php-fpm.d\/www.conf\nuser = nginx\ngroup = nginx\nlisten.owner = nginx\nlisten.group = nginx<\/pre>\n\n\n\n

Reload PHP-FPM for the changes to occur.<\/p>\n\n\n\n

systemctl restart nginx php-fpm<\/pre>\n\n\n\n

Step 5 Install WordPress<\/h2>\n\n\n\n
\"WordPress<\/figure>\n\n\n\n


Installing the most up to date version of WordPress is critical for security, the same can be said for themes or plugins. Automatic updates may sound appealing; though, you may find a manual administrative review of backups and troubleshooting is best should an update break something.<\/p>\n\n\n\n

Change to the Nginx root directory.<\/p>\n\n\n\n

cd \/usr\/share\/nginx\/html<\/pre>\n\n\n\n

Download a copy of the latest WordPress installation.<\/p>\n\n\n\n

wget https:\/\/wordpress.org\/latest.tar.gz<\/pre>\n\n\n\n

Tar is no longer installed by default on RHEL 8 \/ CentOS 8 and will need to be installed.<\/p>\n\n\n\n

yum install -y tar<\/pre>\n\n\n\n

Use tar to extract the file contents.<\/p>\n\n\n\n

tar -zxvf latest.tar.gz<\/pre>\n\n\n\n

Clean up the unnecessary file:<\/p>\n\n\n\n

rm latest.tar.gz<\/pre>\n\n\n\n

Find all of the directories in the defined path and update octal permissions.<\/p>\n\n\n\n

find \/usr\/share\/nginx\/html\/wordpress -type d -exec chmod 755 {} ;<\/pre>\n\n\n\n

Find all of the files in the defined path and update octal permissions.<\/p>\n\n\n\n

find \/usr\/share\/nginx\/html\/wordpress -type f -exec chmod 644 {} ;<\/pre>\n\n\n\n

Step 6 Nginx and wp-config.php Configuration<\/h2>\n\n\n\n

Next for our WordPress LEMP CentOS 8 installation we will be proceeding through Nginx and wp-config.php settings inclusive to a few security checks. This configuration is not for SSL as we have alternative documentation for SSL configurations in our tutorial Install Lets Encrypt SSL Nginx.<\/a><\/p>\n\n\n\n

You should also take note that A self-signed certificate may be effective temporarily, but it would not provide the SEO benefits of a trusted certificate. Along with SEO penalties, self-signed certificates will cause warnings in the browsers for your visitors that can cause them to leave your site entirely.<\/p>\n\n\n\n

Should you find yourself facing various SSL related errors the Really Simple SSL<\/a> plugin can be a very handy quick fix.<\/p>\n\n\n\n

Exclude XML-RPC support from your main nginx.conf as it is a common DOS attack location.<\/p>\n\n\n\n

vi \/etc\/nginx\/nginx.conf \nlocation = \/xmlrpc.php {\n   deny all;\n   access_log off;\n   log_not_found off;\n   return 444;\n}<\/pre>\n\n\n\n

Copy the following snippet and make adjustments for your root<\/tt> directory or server_name<\/tt> accordingly.<\/p>\n\n\n\n

vi \/etc\/nginx\/conf.d\/default.conf \nserver {\n   listen 80;\n   server_name example1.com www.example1.com;\n\n   # note that these lines are originally from the \"location \/\" block\n   root \/usr\/share\/nginx\/html\/wordpress;\n   index index.php index.html index.htm;\n\n   location \/ {\n      try_files $uri $uri\/ =404;\n   }\n   error_page 404 \/404.html;\n   error_page 500 502 503 504 \/50x.html;\n   location = \/50x.html {\n      root \/usr\/share\/nginx\/html;\n   }\n\n   location ~ .php$ {\n      try_files $uri =404;\n      fastcgi_pass unix:\/var\/run\/php-fpm\/www.sock;\n      fastcgi_index index.php;\n      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n      include fastcgi_params;\n   }\n}<\/pre>\n\n\n\n

Ensure Nginx and PHP-FPM pick up the changes.<\/p>\n\n\n\n

systemctl restart nginx php-fpm<\/pre>\n\n\n\n

Change directories to your WordPress installation.<\/p>\n\n\n\n

cd \/usr\/share\/nginx\/html\/wordpress<\/pre>\n\n\n\n

Copy wp-config-sample.php<\/tt> file to wp-config.php<\/tt>.<\/p>\n\n\n\n

cp wp-config-sample.php wp-config.php<\/pre>\n\n\n\n

Change the ownership of wp-config.php<\/tt> to Nginx.<\/p>\n\n\n\n

chown nginx.nginx wp-config.php<\/pre>\n\n\n\n

Run the following curl command to generate salts for your wp-config.php<\/tt>.<\/p>\n\n\n\n

curl -s https:\/\/api.wordpress.org\/secret-key\/1.1\/salt\/<\/pre>\n\n\n\n

Obtain the matching output as follows and replace the corresponding lines in your wp-config.php<\/tt> file.<\/p>\n\n\n\n

define('AUTH_KEY', ';]wL<]6QUL =7|MU$b;01u?;+-4|pDCRU9zHjGs2YwSWiWR@2n3t+lfua-+s^bi[');\ndefine('SECURE_AUTH_KEY', 'G+Y[;~YIn|2V.TIOxN6Quo<V_4s2G=0qI|6^}fI|3OBg8Q9~v[]XO!Upg,dl)OHu');\ndefine('LOGGED_IN_KEY', 'h~Jo+S6xF^v_6>#ZZU,,Q9B [%9>nO]j4uDsaq2y3B+C6nGd~7@s8NPlIR.E,~=3');\ndefine('NONCE_KEY', 'S_5Z?eY~@H: f;|?Q~RGkaQsJFI0p<M}%#V{r~N##M:jw=lxTd6uzl sO31ay-xM');\ndefine('AUTH_SALT', '|`fx},S-Y,i6l]k.F<SiJn4@-2qs|z4*H<ehs%1{eb-9R|4A 6nZ>3s#-4rMkeD@');\ndefine('SECURE_AUTH_SALT', '-H6f(A^2!=JE+El(hXLSQd*gB&Gq{*wl`o*Xv,N|HMD{-.o6{8p~xTvXE|+$YK|L');\ndefine('LOGGED_IN_SALT', 'axF3sx3X#hN<,8^2(btXk;}A[+z\/O2*LV[A?Y++!0r3S_Wk`ryD;irmM\/8jbei8Q');\ndefine('NONCE_SALT', '2uk&qo?P|+3$nQjsLs:L<2I|#q}g~80W!*Xs-g|IT+o~n[[P_]7z>%uT{+lbZ>:o');<\/pre>\n\n\n\n

Open your wp-config.php<\/tt> and modify to match your credentials used when we were configuring the MariaDB service.<\/p>\n\n\n\n

vi wp-config.php<\/pre>\n\n\n\n
define('DB_NAME', 'wordpress');\n\n\/** MySQL database username *\/\ndefine('DB_USER', 'wordpressuser');\n\n\/** MySQL database password *\/\ndefine('DB_PASSWORD', 'password-you-provided');\n\n\/** This entry will allow you to update, install plugins or themes on WordPress without using FTP **\/\ndefine('FS_METHOD', 'direct');<\/pre>\n\n\n\n

Restart the PHP-FPM and Nginx services to pick up the recent changes.<\/p>\n\n\n\n

systemctl restart php-fpm nginx<\/pre>\n\n\n\n

From here you can use your browser to reach the defined domain name. You may need to clear your caches or use another browser to initially view your WordPress installation.<\/p>\n\n\n\n

\"Wordpress<\/figure>\n\n\n\n
\"WordPress<\/figure>\n\n\n\n

Additional and Optional Configurations<\/h2>\n\n\n\n

WordPress specific security can be found within the WordPress Codex<\/a> and should be a standard go-to for such reviews.<\/p>\n\n\n\n

The security recommendations are meant to be a starting point. It is also worth noting that at default SELinux is not configured on our servers and this tutorial will not be covering it. Red Hat recommends that SELinux be configured and your team should consider their level of familiarity with the topic before proceeding.<\/p>\n\n\n\n

Our tutorial on securing your cloud server<\/a> may also be of assistance when thinking more granularly about your security options.<\/p>\n\n\n\n

Fail2ban<\/h2>\n\n\n\n

Fail2ban should be configured on all running nodes and WordPress happens to have a very handy plugin that can add additional brute force protection to your environment.<\/p>\n\n\n\n

We can find brute force attempts over SSH in \/var\/log\/secure with the following syntax.<\/p>\n\n\n\n

# tail -n 10 \/var\/log\/secure | grep -i Failed\nOct 17 08:43:39 centos-1cpu-1gb-fi-hel1 sshd[9945]: Failed password for invalid user avanthi from 196.24.190.136 port 59626 ssh2\nOct 17 08:43:44 centos-1cpu-1gb-fi-hel1 sshd[9947]: Failed password for invalid user avanthi from 196.24.190.136 port 63057 ssh2<\/pre>\n\n\n\n

Here we can find failed attempts sorted on \u201cFailed\u201d by IP address. It appears this IP address is being abusive.<\/p>\n\n\n\n

# tail -n 100000 \/var\/log\/secure | grep -i 'Failed' | awk {'print $13'} | uniq -c | sort -nr\n746 196.24.190.136\n165 196.24.190.136\n1 65088\n1 64964<\/pre>\n\n\n\n

We could proceed to block this IP address manually after performing a whois lookup or ideally we can install Fail2ban and make the assumption that after that many attempts it is likely malicious. It can be difficult and time-consuming to report every single abusive IP address to its source, though if you have the time please do so.<\/p>\n\n\n\n

Additional to this tutorial we also have the following our Fail2ban Centos,<\/a> Fail2ban Debian,<\/a> and Fail2ban Ubuntu.<\/a><\/p>\n\n\n\n

Outside of the actual Fail2ban service, you may also find the Fail2ban Plugin<\/a> of additional use.<\/p>\n\n\n\n

Fail2ban Service Installation Centos 8 for SSH jail.<\/p>\n\n\n\n

Install the EPEL repository.<\/p>\n\n\n\n

yum install -y epel-release<\/pre>\n\n\n\n

Install the Fail2ban service.<\/p>\n\n\n\n

yum install -y fail2ban<\/pre>\n\n\n\n

Ensure fail2ban is enabled at boot.<\/p>\n\n\n\n

systemctl enable fail2ban<\/pre>\n\n\n\n

Start the fail2ban service.<\/p>\n\n\n\n

systemctl start fail2ban<\/pre>\n\n\n\n

Open your fail2ban jail configuration file.<\/p>\n\n\n\n

vi \/etc\/fail2ban\/jail.conf<\/pre>\n\n\n\n

Take note of important parameters, in this case, we recommend the default parameters for the SSH block.<\/p>\n\n\n\n

bantime = 10m\nfindtime = 10m \nmaxretry = 5<\/pre>\n\n\n\n

Comment out and replace with firewalld instructions unless you are using iptables.<\/p>\n\n\n\n

#banaction = iptables-multiport\n#banaction_allports = iptables-allports\nbanaction = firewallcmd-multiport\nbanaction_allports = firewallcmd-allports<\/pre>\n\n\n\n

There are two SSHD blocks, under the uncommented SSHD entry that enable the service.<\/p>\n\n\n\n

[sshd]\nenabled = true<\/pre>\n\n\n\n

After changes to your jail.conf have been made, ensure the service picks up the alterations with a restart.<\/p>\n\n\n\n

systemctl restart fail2ban<\/pre>\n\n\n\n

Find the status of a failed and banned IP address.<\/p>\n\n\n\n

fail2ban-client status<\/pre>\n\n\n\n

Find the status of a failed and banned IP address by service.<\/p>\n\n\n\n

fail2ban-client status sshd<\/pre>\n\n\n\n

We can see that our Fail2ban configuration is in place and check that we are no longer being attacked.<\/p>\n\n\n\n

# tail -n 100 \/var\/log\/secure | grep -i failed | tail -n 5\nOct 17 08:43:17 centos-1cpu-1gb-fi-hel1 sshd[9939]: Failed password for invalid user avanthi from 196.24.190.136 port 65182 ssh2\nOct 17 08:43:25 centos-1cpu-1gb-fi-hel1 sshd[9941]: Failed password for invalid user avanthi from 196.24.190.136 port 52740 ssh2\nOct 17 08:43:33 centos-1cpu-1gb-fi-hel1 sshd[9943]: Failed password for invalid user avanthi from 196.24.190.136 port 55619 ssh2\nOct 17 08:43:39 centos-1cpu-1gb-fi-hel1 sshd[9945]: Failed password for invalid user avanthi from 196.24.190.136 port 59626 ssh2\nOct 17 08:43:44 centos-1cpu-1gb-fi-hel1 sshd[9947]: Failed password for invalid user avanthi from 196.24.190.136 port 63057 ssh2<\/pre>\n\n\n\n
# date\nThu Oct 17 11:02:19 UTC 2019<\/pre>\n\n\n\n

Fail2ban SFTP<\/h2>\n\n\n\n

The following can test the output of pattern matching to ensure your regex is capturing all the entires.<\/p>\n\n\n\n

fail2ban-regex \/var\/log\/secure \/etc\/fail2ban\/filter.d\/sshd.conf<\/pre>\n\n\n\n

Enabling the VSFTPD Fail2ban jail isn\u2019t required as in this configuration VSFTPD connections are using the same SSHD pattern matching. However, should you enable the VSFTPD jail, the following will test your filter pattern.<\/p>\n\n\n\n

fail2ban-regex \/var\/log\/secure \/etc\/fail2ban\/filter.d\/vsftpd.conf<\/pre>\n\n\n\n

SFTP<\/h2>\n\n\n\n

In the aforementioned WordPress LEMP CentOS 8 configurations we made an alteration to wp-config.php<\/em> to bypass FTP warmings in WordPress, you may optionally have the desire to fully configure FTP. FTP is inherently insecure as it is unencrypted, it\u2019s best practice to use SFTP.<\/p>\n\n\n\n

VSFTPD Installation<\/h3>\n\n\n\n

Install the VSFTPD service.<\/p>\n\n\n\n

yum install -y vsftpd<\/pre>\n\n\n\n

Ensure the VSFTPD service is started at boot time.<\/p>\n\n\n\n

systemctl enable vsftpd<\/pre>\n\n\n\n

Start the VSFTPD service.<\/p>\n\n\n\n

systemctl start vsftpd<\/pre>\n\n\n\n

Check VSFTPD status to ensure everything is running smoothly.<\/p>\n\n\n\n

systemctl status vsftpd<\/pre>\n\n\n\n

Generate an SSL Certificate for encrypting connections.<\/p>\n\n\n\n

openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout \/etc\/vsftpd\/vsftpd.pem -out \/etc\/vsftpd\/vsftpd.pem<\/pre>\n\n\n\n
vi \/etc\/vsftpd\/vsftpd.conf<\/pre>\n\n\n\n

Ensure the following settings, they should be the default.<\/p>\n\n\n\n

anonymous_enable=NO\nlocal_enable=YES\nwrite_enable=YES<\/pre>\n\n\n\n

The following entries will need to be added manually.<\/p>\n\n\n\n

rsa_cert_file=\/etc\/vsftpd\/vsftpd.pem\nrsa_private_key_file=\/etc\/vsftpd\/vsftpd.pem\nssl_enable=YES<\/pre>\n\n\n\n

Restart the service to pick up the alterations.<\/p>\n\n\n\n

systemctl restart vsftpd<\/pre>\n\n\n\n

The firewall will need to have the ports opened.<\/p>\n\n\n\n

firewall-cmd --permanent --add-port=20-21\/tcp<\/pre>\n\n\n\n

Reload the firewall to ensure changes are updated.<\/p>\n\n\n\n

firewall-cmd --reload\n<\/pre>\n\n\n\n

Set SFTP User and Permissions<\/h3>\n\n\n\n

Create the new FTP user where \/usr\/share\/nginx\/<\/em> is the home folder for the user.<\/p>\n\n\n\n

useradd -d \/usr\/share\/nginx\/ wpftp<\/pre>\n\n\n\n

Set the password for the wpftp<\/em> user.<\/p>\n\n\n\n

passwd wpftp<\/pre>\n\n\n\n

Create the www-data<\/em> group.<\/p>\n\n\n\n

groupadd www-data<\/pre>\n\n\n\n

Add the wpftp<\/em> user to the www-data<\/em> group.<\/p>\n\n\n\n

usermod -aG www-data wpftp<\/pre>\n\n\n\n

Open nginx.conf<\/em> and update the Nginx user.<\/p>\n\n\n\n

vi \/etc\/nginx\/nginx.conf \nuser wpftp;<\/pre>\n\n\n\n

Test the Nginx configuration, you can avoid the reload until your permissions are set.<\/p>\n\n\n\n

nginx -t<\/pre>\n\n\n\n

Open www.conf<\/em> and update the PHP-FPM user.<\/p>\n\n\n\n

vi \/etc\/php-fpm.d\/www.conf\nuser = wpftp\ngroup = www-data \nlisten.owner = wpftp\nlisten.group = www-data<\/pre>\n\n\n\n

Test the PHP-FPM configuration, you can avoid reloading until your permissions are in place.<\/p>\n\n\n\n

php-fpm -t<\/pre>\n\n\n\n

Set ownership with the new user and group.<\/p>\n\n\n\n

chown -R wpftp:www-data \/usr\/share\/nginx\/html<\/pre>\n\n\n\n

Update ownership for \/var\/lib\/nginx<\/em>.<\/p>\n\n\n\n

chown -R wpftp:www-data \/var\/lib\/nginx<\/pre>\n\n\n\n

Ensure log permissions are in place.<\/p>\n\n\n\n

chown -R wpftp:www-data \/var\/log\/nginx<\/pre>\n\n\n\n

Change ownership for PHP-FPM listening on the UNIX socket.<\/p>\n\n\n\n

chown wpftp:www-data \/var\/run\/php-fpm\/www.sock<\/pre>\n\n\n\n

After you have confirmed permissions and configurations pass tests proceed to restart Nginx and PHP-FPM.<\/p>\n\n\n\n

systemctl restart nginx<\/pre>\n\n\n\n
systemctl restart php-fpm<\/pre>\n\n\n\n

WordPress Login Page<\/h2>\n\n\n\n

It is recommended that your WordPress login page be altered as a form of security through obscurity.<\/p>\n\n\n\n

The following is an example of attempts on your WordPress login page.<\/p>\n\n\n\n

# tail -n 5000 \/var\/log\/nginx\/access.log| grep -i wp-login\n<HiddenIPAddress> - - [17\/Oct\/2019:06:03:23 +0000] \"GET \/wp-login.php HTTP\/1.1\" 200 3253 \"http:\/\/www.example1.com\/wp-admin\/install.php?step=2\" \"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10.14; rv:69.0) Gecko\/20100101 Firefox\/69.0\" \"-\"<\/pre>\n\n\n\n
# tail -n 5000 \/var\/log\/nginx\/access.log| grep -i wp-login | awk {'print $1'} | sort -nr | uniq -c\n5 <HiddenIPAddress><\/pre>\n\n\n\n

It is feasible to alter the location of your wp-login<\/em> page manually but this would require altering the core WordPress files and should be avoided. I am going to recommend the WPS Hide Login Plugin.<\/a><\/p>\n\n\n\n

Stop Unwanted Bots and Crawling with robots.txt<\/h2>\n\n\n\n

You may find that your server is having a significant amount of traffic from various bots for search engines you don\u2019t care much about ranking with. Perhaps you only care about the Googlebot and want no other crawling to reduce unnecessary traffic. These restrictions can be made with your robots.txt<\/em> file. There are additional configurations for speeding up indexing and search order that we will not be covering today that could also be considered.<\/p>\n\n\n\n

The following are a few one-liners that may assist you in tracking down aggressive bot locations.<\/p>\n\n\n\n

# cat \/var\/log\/nginx\/access.log | grep -i bot<\/pre>\n\n\n\n
# cat \/var\/log\/nginx\/access.log | awk {'print $1'} | sort -nr | uniq -c\n154 <HiddenIPAddress>\n1 122.228.19.80\n1 93.146.243.78\n1 92.81.40.232\n4 89.248.169.17<\/pre>\n\n\n\n

A simple whois lookup on the IP address can assist you in drilling down the source of the IP.<\/p>\n\n\n\n

# whois 93.146.243.78<\/pre>\n\n\n\n

For example, you could restrict the following Google bots and disallow all others. For a proper view of the current Google user-agents please visit this Google support article. <\/a><\/p>\n\n\n\n

User-agent: Googlebot \nUser-agent: Googlebot-News\nUser-agent: Googlebot-Image\nUser-agent: Googleboot-Video \nUser-agent: Mediapartners-Google\nUser-agent: AdsBot-Google\nAllow: \/\n\nUser-agent: *\nDisallow: \/<\/pre>\n\n\n\n

It is also worth noting that Google has released a Testing Tool<\/a> for robots.txt<\/em> that provides verification of your configurations.<\/p>\n\n\n\n

Allows access to all and block the Baiduspider.<\/p>\n\n\n\n

User-agent: Baiduspider\nUser-agent: Baiduspider-video\nUser-agent: Baiduspider-image\nDisallow: \/\n\nUser-agent: *\nDisallow:<\/pre>\n\n\n\n

The following entry restricts crawling on the wp-admin page and allows Google Search Console to crawl the admin-ajax.php<\/em> file or it will throw errors.<\/p>\n\n\n\n

User-agent: *\nDisallow: \/wp-admin\/\nAllow: \/wp-admin\/admin-ajax.php<\/pre>\n\n\n\n

This entry tells bots how to search for your sitemap which makes the crawler more efficient.<\/p>\n\n\n\n

Sitemap: https:\/\/yourdomain.com\/sitemap.xml<\/pre>\n\n\n\n

Commonly Used Services, Plugins and Monitoring<\/h2>\n\n\n\n
\"Cloudflare\"<\/figure>\n\n\n\n

CloudFlare<\/a> has CDN features with additional security benefits with plans that start in a free tier.<\/p>\n\n\n\n

When configuring Cloudflare you may also wish to restrict to the Cloudflare IPs<\/a> as it would still be feasible to DDoS the server directly bypassing Cloudflare.<\/p>\n\n\n\n

The \u2013with-http_real_pi_module<\/em> and log_format<\/em> are now configured by default and a list of prefixes will need to be updated regularly based on the Cloudflare IP list.<\/p>\n\n\n\n

The following entries can be placed in the main nginx.conf file or be abstracted into an individual configuration file.<\/p>\n\n\n\n

set_real_ip_from 103.21.244.0\/22;\nset_real_ip_from 103.22.200.0\/22;\nset_real_ip_from 103.31.4.0\/22;\nset_real_ip_from 104.16.0.0\/12;\nset_real_ip_from 108.162.192.0\/18;\nset_real_ip_from 131.0.72.0\/22;\nset_real_ip_from 141.101.64.0\/18;\nset_real_ip_from 162.158.0.0\/15;\nset_real_ip_from 172.64.0.0\/13;\nset_real_ip_from 173.245.48.0\/20;\nset_real_ip_from 188.114.96.0\/20;\nset_real_ip_from 190.93.240.0\/20;\nset_real_ip_from 197.234.240.0\/22;\nset_real_ip_from 198.41.128.0\/17;\nset_real_ip_from 2400:cb00::\/32;\nset_real_ip_from 2606:4700::\/32;\nset_real_ip_from 2803:f800::\/32;\nset_real_ip_from 2405:b500::\/32;\nset_real_ip_from 2405:8100::\/32;\nset_real_ip_from 2c0f:f248::\/32;\nset_real_ip_from 2a06:98c0::\/29;<\/pre>\n\n\n\n

Use either of the following two entries.<\/p>\n\n\n\n

real_ip_header CF-Connecting-IP;\n#real_ip_header X-Forwarded-For;<\/pre>\n\n\n\n
\"WordFence\"<\/figure>\n\n\n\n

Wordfence<\/a> has security features inclusive to theme and plugin scanning that starts in a free tier.<\/p>\n\n\n\n

Entirely free open-source database monitoring can be configured with Percona Monitoring and Management<\/a> (PMM) this tool is based on Grafana.<\/p>\n\n\n\n

\"New<\/figure>\n\n\n\n

Application-level monitoring through New Relic<\/a> starts in a free trial mode then after expiration turns into a Lite account. APM lite includes similar features to Essentials paid plan, apart from transactions, traces, and alerts.<\/p>\n\n\n\n

In a WordPress CentOS 8 LEMP environment and many other CMS e-commerce environments, it is best practice to have layers of caching. These configurations can become very complicated, yet have great performance gains.<\/p>\n\n\n\n

\"Varnish<\/figure>\n\n\n\n

A page cache such as Varnish allows you to store requests in memory instead of reaching for a location on disk from the backend if the cache is empty Varnish will have to read the data from disk into memory.<\/p>\n\n\n\n

When a request is made to WordPress for the first time a query is performed on the database instance and when Redis is configured that query is cached for later use to prevent additional reads to the database.<\/p>\n\n\n\n

\"Redis<\/figure>\n\n\n\n

Redis and Varnish can be installed on the single node server even though a recommended installation should decouple the services to their own instances for scalability and high availability.<\/p>\n\n\n\n

There are a number of ways to configure your caching layers situational to your session handling and high availability strategy. Considerations are inclusive to excludes and purge rules which can be circumstantial depending on Woocommerce, static or dynamic WordPress environments. The Redis Cache Plugin<\/a> and Vcaching Plugin<\/a> could be considered alongside installations that match your environmental needs.<\/p>\n\n\n\n

Summary<\/h2>\n\n\n\n

Congratulations! You have completed a single node WordPress LEMP CentOS 8 installation and should have found a few different options for drilling down growth points, security, and performance.<\/p>\n","protected":false},"featured_media":11799,"comment_status":"open","ping_status":"closed","template":"","community-category":[116,126],"class_list":["post-24871","tutorial","type-tutorial","status-publish","has-post-thumbnail","hentry","community-category-web-hosting","community-category-wordpress"],"acf":[],"_links":{"self":[{"href":"https:\/\/studiogo.tech\/upcloudold\/wp-json\/wp\/v2\/tutorial\/24871","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/studiogo.tech\/upcloudold\/wp-json\/wp\/v2\/tutorial"}],"about":[{"href":"https:\/\/studiogo.tech\/upcloudold\/wp-json\/wp\/v2\/types\/tutorial"}],"replies":[{"embeddable":true,"href":"https:\/\/studiogo.tech\/upcloudold\/wp-json\/wp\/v2\/comments?post=24871"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/studiogo.tech\/upcloudold\/wp-json\/wp\/v2\/media\/11799"}],"wp:attachment":[{"href":"https:\/\/studiogo.tech\/upcloudold\/wp-json\/wp\/v2\/media?parent=24871"}],"wp:term":[{"taxonomy":"community-category","embeddable":true,"href":"https:\/\/studiogo.tech\/upcloudold\/wp-json\/wp\/v2\/community-category?post=24871"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}