Building a Secure WordPress server with LAMP on CentOS 7 and SELinux

I’ve been maintaining my own web server for this WordPress blog for several years now, dating back to 2005 when I first starting using CentOS 4 to run my website. Those were the days I switched from authoring websites with Dreamweaver and FTP, to using WordPress and ditching those antiquated tools alltogether. Talking of antiquated, I’ve been working with Unix since 1992 and was a Linux sysadmin for an ISP for several years after that. I’ve also been learning along the way with each release of CentOS/RHEL, and I have taken much more notice of security hardening including the use of SELinux.

As an experiment, I posted a tweet last night merely mentioning SELinux which resulted in some predictable responses including:

I really don’t blame them for disliking SELinux, it seems that is a majority opinion. But I hope to change that! If I can get it working and playing nicely with my WordPress site then so can you. The reason I use SELinux isn’t to make my life any more difficult (though that could be true at times!), but it helps me better understand the inner-workings of CentOS 7 better, while providing significant levels of security.

Assumptions
For the remainder of this guide, I will assume that you know how to use Vi or another text editor, and you have a basic understanding of the Linux operating system.

Security Considerations

Here are the primary security requirements that I will address with this guide:

SELinux will be enforcing security policies

IPtables will provide firewall functionality

TCP Wrapper will be configured for added security

2-Factor authentication for WordPress

SSL certificates must be used and HTTP will redirect to HTTPS

FTP is not allowed, and all file transfers must be encrypted during transport (SSL / SSH file transfer)

Multiple websites (virtual hosts) with SFTP users chrooted (jailed) to their own directory.

SSH key based authentication (disable root access via SSH)

MySQL will be managed from SSH (no phpMyAdmin!)

Now just a few words on TCP Wrapper before someone freaks out. It’s old. Really old. It doesn’t protect all services running on the server and it uses the libwrap library which was created in 1990. However, it takes the blink of an eye to configure so think of it like one of these security latches your granny uses on her front door. There is no harm in using it.

Do I really have to use SELinux?

I get it. SELinux is a pain, especially if you tried it back in the days of CentOS/RHEL 4. Oh my, I thought about throwing it all out of the window back then. But it’s not really that bad, so ignore the negative comments and learn how to implement it properly.

The NSA (National Security Agency) created SELinux along with Red Hat who continue to be a major contributor. It’s a Mandatory Access Control (MAC) system that sets a fixed (Targeted) policy for access. For example, if a policy prevents a user or process from accessing a directory then it will be prevented by SELinux. Period.

For the purposes of this guide I will focus on the targeted policy, since the other option to use MLS (Multi-Level Security) is not in the scope of this guide.

A targeted policy includes a list of processes that will be protected (or confined) by SELinux, and anything not targeted will be unconfined and will use the Discretionary Access Control (DAC) model. Almost all processes listening on the network (such as httpd, sshd) are confined by SELinux. By confining the process, if it is compromised by an attacker then it greatly reduces what they can do.

Before SELinux is enabled, packages will need to be installed and LAMP and WordPress will be configured. SELinux will not be configured until the very end. Reasons for that will become clear later.

Part 1: Deploying a new virtual private server (VPS)

When you have a brand new virtual machine up and running with CentOS 7, first thing to do is make sure it’s up to date. You will initially need access to your virtual machine using the console (not SSH).

Securing Access

Adding a new user
Root access via SSH will be disabled, and a standard user account will be used for administering the host. Whenever root privileges are required, sudo will be used. In this example, a user called Fred will be added. Fred is the sysadmin.

Now logout, and log back in as the new user. Use sudo -i to login with root privileges if needed.

Configuring SSH Key Based Authentication
By this stage you should be logged in with your new user account (not root). PuTTYgen will be used to generate an RSA key pair and configure PuTTY to access the server using the private key.

Copy the public key to the server
In order for public/private key authentication to work, your server must have a copy of the public key in ~/.ssh/authorized_keys. ~/ denotes your home directory (/home/bob), and you may need to create the .ssh directory first. Just make sure both .ssh and the authorized_keys file is owned by the user being authenticated (not root). Also the permissions need to be set as follows, otherwise you will get an error: Server refused our key# chmod 700 .ssh# chmod 644 authorized_keys

Note: If you still get the error, check that this file isn’t owned by root.
1. Paste the contents of the public key in to the file ~/.ssh/authorized_keys. If this directory or file doesn’t exist, create it.

Load the private key into PuTTY
1. Launch PuTTY and configure the hostname under the Session category.
2. Click on Connection > Data and type the Auto-login username
3. Click on Connection > SSH > Auth and Browse to the location of the private key.
4. Click on Session and save your configuration.

Now when you launch PuTTY it will present the private key, and this will match the corresponding public key on the server. Only you will hold a copy of the private key.

Disable SSH Password Authentication
This step is optional but strongly recommended. If you disable SSH password based authentication then you can only access SSH with the private key you configured in the last step. We still need passwords in order for sudo to work, but this step will only access certificate based authentication when you SSH to the host.

Edit /etc/ssh/sshd_config and set the following:

PasswordAuthentication no

Note: You’ll need to configure WordPress to use authentication keys if you disable SSH password authentication. This requires both the public and private key to be installed on the WordPress server which I think is a terrible idea. One option is to temporarily enable password authentication for SSH for when WordPress needs it.

Installing Core Packages

Now it is time to install the core packages needed to get the secure server up and running. Wget is later required to download the RPMs for the Remi repository, and it is a useful tool to have on the host anyway. Later in the guide NTP will be configured for time synchronization. Iptables is my preferred firewall, and I suggest you follow my Essential Linux Skills with CentOS 7 Guide, which I will use here. Finally, yum-cron will be used to make sure the CentOS host is updated regularly, and the EPEL repository will be used.

SMTP (Optional)
Next install Sendmail or Postfix. This is required if you want WordPress to send email notifications, or if you are using a website contact form. Since yum-cron is installed already, it can also be configured to send email notifications.# yum install -y postfix

Installing the Remi Repository
CentOS 7 doesn’t install the latest version of PHP from the CentOS/RHEL or EPEL repositories. CentOS 7.3.1611 will install PHP version 5.4.16, not PHP 7.1 which is the latest version at the time of writing this.

Next, configure the repository and set the first entry to enabled=1. If you want to use PHP 5.6 and not the very latest (PHP 7.1) then set enabled=1 in the [remi-php56] section.# vi /etc/yum.repos.d/remi.repo

Installing PHP 7.1
If you want to install PHP 7.1, then edit remi-php71.repo and set enabled=1 (make sure 5.6 disabled in the remi.repo as described in the previous step):# vi /etc/yum.repos.d/remi-php71.repo

Basic Server Configuration

Configuring yum-cron for automatic updates
Enable the yum-cron service to make sure it starts automatically.# systemctl enable yum-cron

Edit the yum-cron.conf file:# vi /etc/yum/yum-cron.conf

Configure the following lines:

update_cmd = security
apply_updates = yes

This is what will happen daily. You can optionally configure /etc/yum/yum-cron-hourly.conf, which as the name suggests is run every hour. By default the hourly configuration won’t download or apply anything.

Part 2: IPtables

One of the first measures is to disable root SSH access, change the SSH port to something other than the default (port 22), and set a maximum number of authentication attempts in order to minimize the likelihood of a brute force attack. Once you have done that then go ahead and configure IPtables.

TIP: I like to keep a copy of my IPtables rules handy, without the comments so when I need to make changes I can simply flush the rules (iptables -F), paste, and then save.

Once you have completed your IPtables configuration, come back to continue with part 3 below.

Installing Fail2Ban
Fail2ban works by dynamically adding IP addresses to the firewall that have failed a certain number of login attempts. It’s very easy to install and configure.# yum install -y fail2ban# systemctl enable fail2ban

Note: Digital Ocean have a good guide on Fail2ban, which I’ve used to take the sample above.

Part 3: MariaDB (MySQL)

MariaDB (which replaces MySQL) has already been installed as part of the core package installation. In order to start at boot time, the service needs to be enabled.# systemctl enable mariadb# systemctl start mariadb

Before proceeding, MySQL (MariaDB) needs to be secured. Using mysql_secure_installation, the implementation of MySQL is hardened for production use. You can do this later since it won’t delete any existing databases, but I recommend you do this now.

Grant privileges to WordPress databaseGRANT ALL PRIVILEGES ON cloudwire.* TO 'wireyuser'@'localhost';FLUSH PRIVILEGES;exit

Note: If you have multiple databases, just repeat from step 2.

Part 4: Migrating From Another VPS Host (Optional)

If you already have another WordPress installation you wish to migrate, follow these steps to move your files and MySQL database to the new server. If you are starting from scratch then feel free to skip this section and move to Part 4.

Transferring files from another VPS host
There are a few ways of doing this. I used to use sshfs to mount the SSH file transfer, but rsync can establish an SSH file transfer all in one hit. This is the fastest method in my opinion.

First, using WinSCP, make sure you can access the other VPS host and see all of the files required for the transfer. Using rsync everything will be copied to the new VPS.Note: You may need to allow SSH in IPtables from your server.

Part 5: Configuring LAMP (Linux, Apache, MariaDB/MySQL and PHP)

By this stage your have either created an empty database, or if you followed part 4, you have transferred a database from another host and have all of the files and directory structure in place.

If you are building from scratch then so far you have an empty database for WordPress, but Apache hasn’t been configured yet or a new directory structure created. Since this is a multi-site virtual host, Apache will use HTTP headers to determine which website directory to serve.

SSL (HTTPS) will be used for the fictitious blog (cloudwire.info), and since host headers only work with HTTP (not HTTPS), a unique IP address is required for each site listening on 443. In this example, one site (cloudwire.info) will be configured on HTTPS (443), so a single IP address allocated by the VPS provider will be used. Your VPS provider will be able to tell you how to add additional IP addresses if they are needed.

Directory Structures and Permissions

It is really up to you how to create your directory structure. I prefer to create a new hidden directory, such as /data/.sitehome/ and then create a directory for each site. For example, /data/.sitehome/cloudwire.info/. Being a hidden directory (staring with a .) doesn’t add that much in terms of security, but it just a practice I have got used to over the years.

Obviously you should think of a better naming convention than I have. The key here is each website owner will have a user account that is jailed to their website directory. This user account will also be used with WordPress for installing updates, plugins and themes. You should have something similar to the following:

SFTP (SSH File Transfer)

This is the way to go. Forget FTP. SSH is already configured, so in order to use it effectively for file transfer (SFTP) chroot, or a jail, will be used to confine the user to their home directory.

For each site being hosted, add a site admin account for SFTP. SSH access will be disabled for each of these users. I like to use random usernames for each site that can’t be guessed very easily.# useradd username -d /data/.sitehome/wordpress001/ (You will get a warning if you have created the directory, you can ignore this.)

If you have already added the user, you can simply modify the home directory using:# usermod -m -d /data/.sitehome/ username

First add a group called ‘sshonly’# groupadd sshonly

Add the user to be chrooted to that group. A new username will be created for the first website ‘cw001’ for the cloudwire.info site.# usermod -aG sshonly cw001

Add the following to the end of sshd_config:Match Group sshonlyChrootDirectory %hForceCommand internal-sftpX11Forwarding noAllowTcpForwarding no

Restart SSHD# systemctl restart sshd

Change the group of the root directory and all sub-directories to ‘sshonly’.# chgrp sshonly /data/.sitehome/wordpress001/ -R

Note: If you need to modify an existing user account with a new home directory, use the following command: usermod -m -d /data/.sitehome/wordpress001/ cw001

Everything inside the website directory needs to be owned by the new user account (E.g. cw001). Don’t give them ownership of the website directory itself, just everything inside.# chown cw001 * -R

Note: Steps 2-4 above ensures that the user cannot login to the host using SSH, but they can use SFTP.

Apache Configuration

If you have stuck with me this far into the guide then give yourself a pat on the back. No really, do it. Go and grab another coffee, change the Spotify playlist and let’s finish this beast of a web server!

Add the VirtualHost entry for the default site at the very bottom of httpd.conf. I call this the not-yet page. If anyone reaches the server with the IP address or it can’t be matched by an HTTP header, then it will display DocumentRoot as a default page.

Note: Make sure all of these directories exist and the paths are correct, otherwise HTTPD will fail to start.

At this stage if everything is configured correctly, when you start HTTPD, and browse to the IP address of your server you should be served the contents of /data/.sitehome/not-yet/webroot/, you should have index.html in there. I’ve got a standard ‘Not yet!’ page I’ve been using since the 1990s, and as I’m nostalgic I’ve decided to keep it all these years. Let’s try it out! If that works then proceed to adding the first site

Adding Our First Site (VirtualHost) for CloudWire.info

Each site needs a VirtualHost entry. So far, only the default page that is displayed for HTTP (TCP port 80) has been configured. I like to keep things tidy, so rather than adding multiple VirtualHost entries in httpd.conf, ‘Include’ entries will be used that point to a file in /etc/httpd/vhosts/. This is where the VirtualHost configuration file for each site will be stored.

For this example, I’ve just registered cloudwire.info and configured DNS to point to the IP address of the new server.

To start with, at the very bottom of httpd.conf add an ‘Include’ pointing to the VirtualHost file. Give the file a meaningful name, this is just an example below.Include /etc/httpd/vhost/cloudwire.info.conf

Providing DNS is updated with the IP address of your server, you should now be able to browse to the new website and it will be displayed.

Configuring SSL

Configuring HTTPS for your website first requires an SSL certificate, and it really isn’t very difficult at all. I used GoDaddy and they have several options for SSL certificates. If you are concerned about costs then I recommend LetsEncrypt which is free. Typically, 3 files are required:

Certificate file (.crt)

Certificate private key (.key)

Intermediate certificates chain (bundle .crt)

You should place the certificate files outside of the web root directory, in this example they are stored in /data/.certificates/cloudwire.info/.

Open the file you created in the previous step (# vi /etc/httpd/vhost/cloudwire.info.conf) and add another VirtualHost entry as below, but changing it with your own values:

Note: As you can see, this is why you will need an additional IP address for each site requiring SSL, since you need to specify the IP listening on port 443.

Part 6: Installing WordPress

While WordPress can be installed using yum, it will assume WordPress is running on a single site web server using /var/www/html for the website location. Since one or more WordPress sites using VirtualHost configuration in Apache will run on the server, wget will be used to grab the latest version of WordPress and install directly into our site directory.

Copy the contents of the WordPress directory to the website directory (/data/.sitehome/clourwire.info/web).# cp wordpress/* /data/.sitehome/clourwire.info/web/ -R

Move the sample configuration file up one level (/home/.sitehome/cloudwire.info/) and copy it new wp-config.php. Never place the config file in a publicly accessible location.# mv wp-config-sample.php ../# cd ..# cp wp-config-sample.php wp-config.php

Edit wp-config.php (vi wp-config.php) and configure the database name, username and password as created in Part 2. If you migrated your WordPress database from another VPS (Part 3), then enter the details of the WordPress database here.define('DB_NAME', 'database_name_here');define('DB_USER', 'username_here');define('DB_PASSWORD', 'password_here');

Navigate to your website URL (E.g. http://cloudwire.info) and you will be presented with a welcome page. You’ll need to enter some basic information and importantly create your WordPress username and password. When you are ready, select ‘Install WordPress’.

Congratulations, you have setup your first WordPress Site!

Securing WordPress

The security of WordPress probably justifies a blog post all by itself, but I can provide some pointers to help you lock it down enough that you’ll be safe from brute force attacks and limit the WordPress attack vector for vulnerabilities. Nothing is 100% secure. If there is a 0-day vulnerability in WordPress then fingers crossed IPtables, SELinux and keeping the server updated with yum-cron is enough.

I am no longer surprised when I take at look at other blogs and see how insecure they are. Here are my top 10 of security issues I would address immediately (in no particular order):

It can be very tempting when you first start using WordPress to try out shiny new plug-ins and themes. These can be open to security vulnerabilities and provide an easy way in for somebody wanting to hack your site. A bit of vigilance can go a long way. Research known bugs and security flaws with any themes or plug-ins you download, and don’t get too carried away. The more plug-ins you have installed the more chance you’ll be bitten.

Most of these security best practices are just common sense and the others are well documented elsewhere. For the purposes of this guide, I will focus on the last three (8, 9 and 10).

Redirecting HTTP to HTTPS using the rewrite module
Since SSL certificates have already been installed (in part 4), an SSL redirect needs to be created in .htaccess. If you go to http:// it will automatically redirect to https://.

Edit ‘.htaccess’ in the web root directory (vi /data/.sitehome/cloudwire.info/web/.htaccess). This file should already exist for WordPress, if not create it.

Protecting WordPress Admin (wp-admin) with .htaccess
Some say that obfuscation doesn’t provide any security benefits, so there isn’t any real advantage to renaming wp-login.php to something else. Since I don’t trust too many plug-ins, using a plug-in to rename it seems like a double-edged sword. Instead, I prefer to use .htaccess to prevent access from unauthorised sources to wp-login.php and the wp-admin directory.

Add the following, replacing X.X.X.X with your IP address(es). Additional IP addresses can be white-listed on new lines.

Order Deny,Allow
Deny from all
Allow from X.X.X.X
Allow from X.X.X.X

Note: If your IP address changes and you get locked out, you will need to SSH into your server and update this.

Protecting wp-login.php with .htaccess
As an additional measure, we can do the same to restrict access to the wp-login.php file which resides in your web root directory.
1. Edit ‘.htaccess’ in the web root directory (vi /data/.sitehome/cloudwire.info/web/.htaccess). This file should already exist for WordPress, if not create it.
2. Add the following, replacing X.X.X.X with your IP address.

<Files wp-login.php>
order deny,allow
Deny from all
Allow from X.X.X.X
</Files>

Configuring SFTP for WordPress
In part 5 you have already learned how to use SSH for SFTP file transfers and use ‘chroot’ to restrict access to the site directory. WordPress supports FTP, FTPS (SSL) and SSH2 connections. When you update WordPress or install a plug-in, you will be presented with the following:

Change the hostname to the IP address or hostname of your server, and remember to specify the SSH port as this is not using the default (cloudwire.info:9922)

Note: As I mentioned in part 1, you’ll need to configure WordPress to use authentication keys if you disable SSH password authentication. This requires both the public and private key to be installed on the WordPress server which I think is a terrible idea. One option is to temporarily enable password authentication for SSH for when WordPress needs it.

Part 7: SELinux

If you have made it this far, hopefully everything is working as it should. You can create multiple sites, host WordPress, transfer files using SFTP and the host is secure with IPtables.

There is a reason I have saved SELinux until last. Grab another coffee, and while you are up and about get a notepad, a pen, turn your phone to silent and get comfortable. No really, this next step is where most folk abandon SELinux and set it back to ‘disabled’. You wouldn’t do that now, would you? Stick with me!

Note: If you use Linode, by default their kernel doesn’t include SElinux. This is easy enough to fix, just follow their guide on using a distribution supplied kernel.

Next SELinux to ‘permissive’ mode and reboot:
Before actually enforcing SELinux policies, it is important to test SELinux with ‘permissive’ mode first. SELinux will log activity to /var/log/audit/audit.log, starting with “SELinux is preventing”, which will be useful to troubleshoot any processes, files or directories that would otherwise be restricted.# vi /etc/sysconfig/selinux
Change SELinux to ‘permissive’ and then reboot.SELINUX=permissive (:wq)# reboot

How SELinux Works

Before going any further, I am going to explain how SELinux works as simply as I can. It is notorious for being a complicated topic, but it really shouldn’t be.

The configuration for SELinux is contained in /etc/sysconfig/selinux, and you can see whether it is running by using the sestatus or getenforce command. If you type sestatus you’ll see it will either be set to ‘disabled’, ‘enforced’, or ‘permissive’. By setting SELinux to permissive, which is the first step, it won’t actually enforce any policies but it will log them. This is really useful before switching it on, as it will allow you to see what it would otherwise restrict.

SELinux Context and Labels
To understand how SELinux actually works, it is important to understand the concept of SELinux context and labels. Labelling allows files, processes, sockets, directories, TCP and UDP ports and many others, to be labelled with a SELinux context. At the time of writing this, I checked and there are 4729 types listed. Don’t worry, you won’t be expected to memorize them all!

You can use seinfo -t to list all SELinux context types, but if you combine it with grep you can narrow the search. Give this a try, seinfo -t | grep httpd_sys. This will show you all of the context types starting with httpd_sys. This will become useful later on when troubleshooting SELinux.

Imagine Apache, hosting your brand new online store, is actually similar to a real store front. Visitors can see your website by opening the door (port 80 / 443) and taking a look at what is inside. Great! Now imagine a guardian inside the store called SELinux. They expect visitors to open the door on ports 80 and 443, and all is well. But the little monster has just stopped visitors looking at your product. Why!? Well, you didn’t label your product (files) with the correct label so SELinux prevented them from reading it.

Forbidden
You don't have permission to access / on this server.

The SELinux context for files and directories are labelled with extended attributes on the file system, and everything else is managed by the kernel. Since this is a web server running Apache, SELinux will expect a label that give Apache access to /var/www/ directory and all files within it. Take a look at /var/www/ using ls -aZ. This will list all of the files in the working directory.# cd /var/www# ls -aZ

Note: Labels use the following format: user:role:type:level, but for targeted mode, and the scope of this guide, I will ignore the others and just focus on type.

Using ls -aZ, you can see that the type for the html directory is set to httpd_sys_content_t:

SELinux recognizes the type label and allows Apache (httpd) to read the file.

But wait! This server is going to be configured with multiple websites and will not use the default /var/www/html location. Take a look at the SELinux context for the new location /data/.sitedir/.# cd /data/.sitedir/# ls -aZ

The type is set to default_t, and if Apache tries to read files from this location, SELinux will prevent it since it hasn’t been labelled correctly. You can see why SELinux can be a pain, but also very powerful. I will show you how to set httpd_sys_content_t on the new web directory location in a moment.

BooleansA Boolean, named after George Boole (pictured left), is a true or false value. There are 301 Boolean settings at the time of writing this, and similar to using seinfo -t to list types, getsebool -a can be used to list them all. Boolean values can be set using the setsebool -P command (-P means persistent so it will be written to disk), and you can specifiy a ‘1’ or ‘0’ (on / off).

To help you understand SELinux Booleans, it may help list the ones relevant to an Apache server. Using semanage boolean -l | grep httpd, Boolean settings that contain the word ‘httpd’ and listed. This will list around 42, along with their description. Here is a sample:

httpd_unifiedhttpd_unified is off by default with CentOS/RHEL 7. This means that SELinux will require the file/directory type label httpd_sys_rw_content_t if Apache is required to write to that directory. For WordPress this would include the wp-uploads directory. With previous versions (CentOS 6), httpd_unified is on, and Apache can read, write and execute on files/directories with the httpd_sys_content_t label.

httpd_can_sendmail
Another Boolean httpd_can_sendmail says that it allows Apache (httpd) to sendmail. WordPress often requires this to send emails for new users, comments, and so on.

httpd_can_network_connect
By default this is off. Setting it to ‘on’ will allow Apache (httpd) to connect to the network. This is required if scripts running under Apache need to connect to remote services (E.g RSS or wget).

Note: To see which httpd Booleans are on, use semanage boolean -l | grep httpd | grep -v off. Grep -v inverts the results. Since the list contains the word ‘on’ for the default setting, I can’t use that so I’m saying anything that doesn’t contain the word ‘off’.

Configuring SELinux to Play Nicely with Apache and WordPress

Apache is running and a few things have changed so it’s time to tell SELinux about these changes.

semange is another useful command that can be used to read and configure settings on network ports, interfaces, SELinux modules, file context and booleans. restorecon -Rv will set this security context recursively (-R) and will output any changes to the type (-v).

Step 2: Allow SSH on non-default port
Another change is the default SSH port from 22 to 9922. Again, using semanage the new port can be specified.# semanage port -a -t ssh_port_t -p tcp 9922

Step 3: Allow Apache to use Sendmail
This time the setsebool command will be used to set the boolean for httpd_can_sendmail to 1. This will allow Apache (httpd) to send email.# setsebool ­P httpd_can_sendmail 1

Step 4 (Optional): Allow Apache to Read & Write directories with httpd_sys_content file types
As mentioned previously, the httpd_unified Boolean is turned off by default with CentOS/RHEL 7. This does strengthen security, as any files and directories that need to be writable by Apache will require the httpd_sys_rw_content_t context. However, for WordPress this can prevent users from uploading files or installing plug-ins. If you decide to enable this and allow read, write and execute, then use the following command:# setsebool -P httpd_unified 1

Troubleshooting SELinux

First, check the status of SELinux and make sure it is set to permissive which you did at the beginning of part 7.# sestatus

Check that SELinux is enabled, the policy is ‘targeted’ and the mode is set to ‘permissive’:

When troubleshooting SELinux it can be useful to clear the audit log and then rebooting. This will make it easier to read the logs and not get confused with older entries while you are troubleshooting in permissive mode.

Clear the audit.log and reboot# > /var/log/audit/audit.log# reboot

Use sealert command to check for issues in audit.log# sealert -a /var/log/audit/audit.log

Note: You can run a summary of audit log reports using aureport -a. This will provide a summary of reports from the audit log (/var/log/audit/audit.log). You can check the logs for today using aureport -a -ts today.

When you have run the sealert command, at the top of the report you’ll see something similar to:

100% done
found 7 alerts in /var/log/audit/audit.log

The output may look really lengthy at first, but don’t fret! Once you start looking at each report you’ll see it is actually very useful. It will start with “SELinux is preventing”, and provide a confidence level for each suggested fix. I will list some common issues, along with the steps you will need to take to resolve the issue.

Issue 1: SELinux is preventing audispd from getattr access on the file /etc/ld.so.cache.
This provided an easy fix, detailed right there in the report:

If you want to fix the label. /etc/ld.so.cache default label should be ld_so_cache_t. Then you can run restorecon.
Do
# /sbin/restorecon -v /etc/ld.so.cache

Issue 2: SELinux is preventing audispd from open access on the file /etc/ld.so.cache.
This is tied to the first issue, and the same fix is suggested so will move on to the next one.

Issue 4: SELinux is preventing /usr/sbin/httpd from name_connect access on the tcp_socket port 9922.)
This is actually required since FTP is not used to update WordPress or install plugins, and Apache needs to connect on SSH.# setsebool httpd_can_network_connect=1

Rinse and repeat. You may need to do this a few times, implementing the fix, clearing the log, rebooting. Once you are satisfied that there are no more issues being reported by SELinux, you can now set it to ‘enabled’!

Once the server boots, log in and check getenforce which should report ‘Enforcing’.

Conclusion

My aim with this guide is to get you setup with a secure web server with CentOS 7, running WordPress for multiple websites. I’ve taken no shortcuts in regards to security, and while I agree that usability is important, I hope you can see that enabling SELinux doesn’t mean ‘unusable’ any longer.

Key Takeaways:
By following this guide you now have:

A basic understanding of SELinux and have it set to ‘enforcing’ on your web server.

Thanks for the guide, it really helped me out.
Something to note… The last change you say to make before conclusion is “set selinux to permissive”.
It should be set to “enforcing” in the config so that it goes into that state after a reboot.