Tutorials How to install single node WordPress LEMP CentOS 8

How to install single node WordPress LEMP CentOS 8

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.

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.

Step 1 Point DNS or Hosts File

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.

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

vi /etc/hosts
94.237.112.110 example1.com
94.237.112.110 www.example1.com

Step 2 Install Nginx

Nginx Logo Transparent
Nginx is a highly performant open-source service that can be configured for a web server, caching, load balancing, media streaming and much more.

We will be keeping things fairly simple by installing Nginx as a web server.

yum install nginx -y

Once installed Nginx can be started with the following command.

systemctl start nginx

To enable Nginx to start at system boot ensure the service is enabled.

systemctl enable nginx

Ensure the service is functioning properly by checking the status.

systemctl status nginx

Check you are running the desired version.

nginx -v

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.

firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https

Reload the firewall daemon for the changes to occur.

firewall-cmd --reload

Step 3 Install Percona Server MySQL 8

Percona MySQL 8

WordPress LEMP CentOS 8 installations frequently run forks of MySQL inclusive to MariaDB and Percona Server.

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.

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.

MariaDB and Percona Server are heavily documented and both companies provide managed database support.

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

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.

Install the Percona Server repository.

yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm

Setup the repository.

percona-release setup ps80

Disable the following as prompted.

* 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

Install Percona Server.

yum install percona-server-server -y

Make a copy of your my.cnf file.

cp /etc/my.cnf /etc/my.cnf.bak

Open the my.cnf file and uncomment the following line as MySQL 8 has changed the default value. Eventually, the community will update the mysqlnd 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’t uncommented this out before user creation.

vi /etc/my.cnf 
[mysqld]
default_authentication_plugin=mysql_native_password

Start the MySQL service.

systemctl start mysql

Enable the MySQL service to start at boot.

systemctl enable --now mysqld

Check the status of the MySQL service.

systemctl status mysql

Obtain the temporary password from the mysqld.log.

grep "temporary password" /var/log/mysqld.log

Proceed with secure installation.

mysql_secure_installation

Follow the prompts.

Set root password? Y 
Enter password for user root: <Paste-copied-password>
Estimated strength of the password: 100
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : Y
Remove anonymous users? Y 
Disallow root login remotely? Y 
Remove test database and access to it? Y
Reload Privilege tables now? Y

Enter the MySQL shell.

mysql -u root -p

Create the WordPress database.

CREATE DATABASE wordpress;

Create the WordPress user inclusive of the desired password.

CREATE USER [email protected] IDENTIFIED BY 'enter-password-here';

Grant permissions to the user on the WordPress database.

GRANT ALL ON wordpress.* TO [email protected];

Proceed to flush the privileges.

FLUSH PRIVILEGES;

Exit the shell.

QUIT;

Step 4 Install PHP & PHP-FPM

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.

To install PHP and related modules run the following command.

yum install -y php php-mysqlnd php-fpm php-opcache php-gd php-xml php-mbstring

After the services and modules are installed start PHP-FPM.

systemctl start php-fpm

Ensure the service is enabled at boot.

systemctl enable php-fpm

Check the service status.

systemctl status php-fpm

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

vi /etc/php-fpm.d/www.conf
user = nginx
group = nginx
listen.owner = nginx
listen.group = nginx

Reload PHP-FPM for the changes to occur.

systemctl restart nginx php-fpm

Step 5 Install Wordpress

WordPress Transparent Logo
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.

Change to the Nginx root directory.

cd /usr/share/nginx/html

Download a copy of the latest WordPress installation.

wget https://wordpress.org/latest.tar.gz

Tar is no longer installed by default on RHEL 8 / CentOS 8 and will need to be installed.

yum install -y tar

Use tar to extract the file contents.

tar -zxvf latest.tar.gz

Clean up the unnecessary file:

rm latest.tar.gz

Find all of the directories in the defined path and update octal permissions.

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

Find all of the files in the defined path and update octal permissions.

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

Step 6 Nginx and wp-config.php Configuration

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.

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.

Should you find yourself facing various SSL related errors the Really Simple SSL plugin can be a very handy quick fix.

Exclude XML-RPC support from your main nginx.conf as it is a common DOS attack location.

vi /etc/nginx/nginx.conf 
location = /xmlrpc.php {
   deny all;
   access_log off;
   log_not_found off;
   return 444;
}

Copy the following snippet and make adjustments for your root directory or server_name accordingly.

vi /etc/nginx/conf.d/default.conf 
server {
   listen 80;
   server_name example1.com www.example1.com;

   # note that these lines are originally from the "location /" block
   root /usr/share/nginx/html/wordpress;
   index index.php index.html index.htm;

   location / {
      try_files $uri $uri/ =404;
   }
   error_page 404 /404.html;
   error_page 500 502 503 504 /50x.html;
   location = /50x.html {
      root /usr/share/nginx/html;
   }

   location ~ \.php$ {
      try_files $uri =404;
      fastcgi_pass unix:/var/run/php-fpm/www.sock;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      include fastcgi_params;
   }
}

Ensure Nginx and PHP-FPM pick up the changes.

systemctl restart nginx php-fpm

Change directories to your WordPress installation.

cd /usr/share/nginx/html/wordpress

Copy wp-config-sample.php file to wp-config.php.

cp wp-config-sample.php wp-config.php

Change the ownership of wp-config.php to Nginx.

chown nginx.nginx wp-config.php

Run the following curl command to generate salts for your wp-config.php.

curl -s https://api.wordpress.org/secret-key/1.1/salt/

Obtain the matching output as follows and replace the corresponding lines in your wp-config.php file.

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

Open your wp-config.php and modify to match your credentials used when we were configuring the MariaDB service.

vi wp-config.php 
define('DB_NAME', 'wordpress');

/** MySQL database username */
define('DB_USER', 'wordpressuser');

/** MySQL database password */
define('DB_PASSWORD', 'password-you-provided');

/** This entry will allow you to update, install plugins or themes on WordPress without using FTP **/
define('FS_METHOD', 'direct');

Restart the PHP-FPM and Nginx services to pick up the recent changes.

systemctl restart php-fpm nginx

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.

Wordpress LEMP Centos 8

WordPress LEMP CentOS 8

Additional and Optional Configurations

WordPress specific security can be found within the WordPress Codex and should be a standard go-to for such reviews.

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.

Our tutorial on securing your cloud server may also be of assistance when thinking more granularly about your security options.

Fail2ban

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.

We can find brute force attempts over SSH in /var/log/secure with the following syntax.

# tail -n 10 /var/log/secure | grep -i Failed
Oct 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
Oct 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

Here we can find failed attempts sorted on “Failed” by IP address. It appears this IP address is being abusive.

# tail -n 100000 /var/log/secure | grep -i 'Failed' | awk {'print $13'} | uniq -c | sort -nr
746 196.24.190.136
165 196.24.190.136
1 65088
1 64964

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.

Additional to this tutorial we also have the following our Fail2ban Centos, Fail2ban Debian, and Fail2ban Ubuntu.

Outside of the actual Fail2ban service, you may also find the Fail2ban Plugin of additional use.

Fail2ban Service Installation Centos 8 for SSH jail.

Install the EPEL repository.

yum install -y epel-release

Install the Fail2ban service.

yum install -y fail2ban

Ensure fail2ban is enabled at boot.

systemctl enable fail2ban

Start the fail2ban service.

systemctl start fail2ban

Open your fail2ban jail configuration file.

vi /etc/fail2ban/jail.conf

Take note of important parameters, in this case, we recommend the default parameters for the SSH block.

bantime = 10m
findtime = 10m 
maxretry = 5

Comment out and replace with firewalld instructions unless you are using iptables.

#banaction = iptables-multiport
#banaction_allports = iptables-allports
banaction = firewallcmd-multiport
banaction_allports = firewallcmd-allports

There are two SSHD blocks, under the uncommented SSHD entry that enable the service.

[sshd]
enabled = true

After changes to your jail.conf have been made, ensure the service picks up the alterations with a restart.

systemctl restart fail2ban

Find the status of a failed and banned IP address.

fail2ban-client status

Find the status of a failed and banned IP address by service.

fail2ban-client status sshd

We can see that our Fail2ban configuration is in place and check that we are no longer being attacked.

# tail -n 100 /var/log/secure | grep -i failed | tail -n 5
Oct 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
Oct 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
Oct 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
Oct 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
Oct 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
# date
Thu Oct 17 11:02:19 UTC 2019

Fail2ban SFTP

The following can test the output of pattern matching to ensure your regex is capturing all the entires.

fail2ban-regex /var/log/secure /etc/fail2ban/filter.d/sshd.conf

Enabling the VSFTPD Fail2ban jail isn’t 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.

fail2ban-regex /var/log/secure /etc/fail2ban/filter.d/vsftpd.conf

SFTP

In the aforementioned WordPress LEMP CentOS 8 configurations we made an alteration to wp-config.php 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’s best practice to use SFTP.

VSFTPD Installation

Install the VSFTPD service.

yum install -y vsftpd

Ensure the VSFTPD service is started at boot time.

systemctl enable vsftpd

Start the VSFTPD service.

systemctl start vsftpd

Check VSFTPD status to ensure everything is running smoothly.

systemctl status vsftpd

Generate an SSL Certificate for encrypting connections.

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

Ensure the following settings, they should be the default.

anonymous_enable=NO
local_enable=YES
write_enable=YES

The following entries will need to be added manually.

rsa_cert_file=/etc/vsftpd/vsftpd.pem
rsa_private_key_file=/etc/vsftpd/vsftpd.pem
ssl_enable=YES

Restart the service to pick up the alterations.

systemctl restart vsftpd

The firewall will need to have the ports opened.

firewall-cmd --permanent --add-port=20-21/tcp

Reload the firewall to ensure changes are updated.

firewall-cmd --reload

Set SFTP User and Permissions

Create the new FTP user where /usr/share/nginx/ is the home folder for the user.

useradd -d /usr/share/nginx/ wpftp

Set the password for the wpftp user.

passwd wpftp

Create the www-data group.

groupadd www-data

Add the wpftp user to the www-data group.

usermod -aG www-data wpftp

Open nginx.conf and update the Nginx user.

vi /etc/nginx/nginx.conf 
user wpftp;

Test the Nginx configuration, you can avoid the reload until your permissions are set.

nginx -t

Open www.conf and update the PHP-FPM user.

vi /etc/php-fpm.d/www.conf
user = wpftp
group = www-data 
listen.owner = wpftp
listen.group = www-data

Test the PHP-FPM configuration, you can avoid reloading until your permissions are in place.

php-fpm -t

Set ownership with the new user and group.

chown -R wpftp:www-data /usr/share/nginx/html

Update ownership for /var/lib/nginx.

chown -R wpftp:www-data /var/lib/nginx

Ensure log permissions are in place.

chown -R wpftp:www-data /var/log/nginx

Change ownership for PHP-FPM listening on the UNIX socket.

chown wpftp:www-data /var/run/php-fpm/www.sock

After you have confirmed permissions and configurations pass tests proceed to restart Nginx and PHP-FPM.

systemctl restart nginx
systemctl restart php-fpm

WordPress Login Page

It is recommended that your WordPress login page be altered as a form of security through obscurity.

The following is an example of attempts on your WordPress login page.

# tail -n 5000 /var/log/nginx/access.log| grep -i wp-login
<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" "-"
# tail -n 5000 /var/log/nginx/access.log| grep -i wp-login | awk {'print $1'} | sort -nr | uniq -c
5 <HiddenIPAddress>

It is feasible to alter the location of your wp-login 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.

Stop Unwanted Bots and Crawling with robots.txt

You may find that your server is having a significant amount of traffic from various bots for search engines you don’t 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 file. There are additional configurations for speeding up indexing and search order that we will not be covering today that could also be considered.

The following are a few one-liners that may assist you in tracking down aggressive bot locations.

# cat /var/log/nginx/access.log | grep -i bot
# cat /var/log/nginx/access.log | awk {'print $1'} | sort -nr | uniq -c
154 <HiddenIPAddress>
1 122.228.19.80
1 93.146.243.78
1 92.81.40.232
4 89.248.169.17

A simple whois lookup on the IP address can assist you in drilling down the source of the IP.

# whois 93.146.243.78

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. 

User-agent: Googlebot 
User-agent: Googlebot-News
User-agent: Googlebot-Image
User-agent: Googleboot-Video 
User-agent: Mediapartners-Google
User-agent: AdsBot-Google
Allow: /

User-agent: *
Disallow: /

It is also worth noting that Google has released a Testing Tool for robots.txt that provides verification of your configurations.

Allows access to all and block the Baiduspider.

User-agent: Baiduspider
User-agent: Baiduspider-video
User-agent: Baiduspider-image
Disallow: /

User-agent: *
Disallow:

The following entry restricts crawling on the wp-admin page and allows Google Search Console to crawl the admin-ajax.php file or it will throw errors.

User-agent: *
Disallow: /wp-admin/
Allow: /wp-admin/admin-ajax.php

This entry tells bots how to search for your sitemap which makes the crawler more efficient.

Sitemap: https://yourdomain.com/sitemap.xml

Commonly Used Services, Plugins and Monitoring

Cloudflare

CloudFlare has CDN features with additional security benefits with plans that start in a free tier.

When configuring Cloudflare you may also wish to restrict to the Cloudflare IPs as it would still be feasible to DDoS the server directly bypassing Cloudflare.

The –with-http_real_pi_module and log_format are now configured by default and a list of prefixes will need to be updated regularly based on the Cloudflare IP list.

The following entries can be placed in the main nginx.conf file or be abstracted into an individual configuration file.

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

Use either of the following two entries.

real_ip_header CF-Connecting-IP;
#real_ip_header X-Forwarded-For;

WordFence

Wordfence has security features inclusive to theme and plugin scanning that starts in a free tier.

Entirely free open-source database monitoring can be configured with Percona Monitoring and Management (PMM) this tool is based on Grafana.

New Relic

Application-level monitoring through New Relic 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.

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.

Varnish Cache Logo Transparent

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.

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.

Redis Logo Transparent

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.

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 and Vcaching Plugin could be considered alongside installations that match your environmental needs.

Summary

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.

Leave a Reply

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

Locations

Helsinki (HQ)

In the capital city of Finland, you will find our headquarters, and our first data centre. This is where we handle most of our development and innovation.

London

London was our second office to open, and a important step in introducing UpCloud to the world. Here our amazing staff can help you with both sales and support, in addition to host tons of interesting meetups.

Singapore

Singapore was our 3rd office to be opened, and enjoys one of most engaged and fastest growing user bases we have ever seen.

Seattle

Seattle is our 4th and latest office to be opened, and our way to reach out across the pond to our many users in the Americas.