How to Install Word Press LAMP Stack on a Binary Lane Server Running Rocky Linux 8

Reading Time: 9 minutes

Why Do This?

GoDaddy, cPanel and other services provide easy one click managed wordpress installation options. This however comes at a premium fee. Buying a raw host and domain name is cheaper, but at a cost of time and learning. We enjoy system administration so we do not care about time. In addition, we value learning new skills for career advancement.

Let’s build a word press lamp stack from scratch!

Harden Our Session by Locking Out Root

Let’s use Binary Lane’s QEMU control panel instead of setting up SSH.

In a VPS, by default, root user is the only user.

Log in as root using Binary Lane’s password.

Let’s create a least privileged user.

useradd admin_sec
usermod -aG wheel admin_sec
passwd

Now let’s lock out root.

We should prevent root from logging into TTY via below; –

echo > /etc/securetty

We should disable root’s shell in passwd; –

sudo dnf install vim
sudo vim /etc/passwd
root:x:0:0:root:/root:/sbin/nologin

Root should have been locked out upon installation. However, if not, we should lockout the root user by generating a random password as its password; –

uuidgen | sudo passwd root --stdin
sudo passwd root --lock

Harden Our Session by Confining Admin User via SELinux

Let’s confine admin_sec user via SELinux.

sudo semanage login -l
sudo semanage login -a -s staff_u admin_sec
sudo semanage login -l

We confine root user via below; –

sudo semanage login -m -s system_u root

We set the default context to the existing selinux user; –

sudo semanage login -l
sudo semanage login -m -s system_u __default__
sudo semanage login -l

Log out. Log back in. Check your session user context; –

>id
...context=staff_u:staff_r:staff_t:s0-s0:c0.c1023

You should see it that admin_sec is now confined to staff_u.

To escalate you need to use -r sysadm_r.

We prevent unconfined_r escalation via below; –

sudo -r sysadm_r semanage user --list
sudo -r sysadm_r semanage user -m -R 'staff_r sysadm_r' staff_u
sudo -r sysadm_r semanage user -m -R 'staff_r sysadm_r system_r' root
sudo -r sysadm_r semanage user -m -R 'system_r' system_u

To deharden and administer a system, escalate as unconfined_r via; –

sudo -r sysadm_r semanage user --list
sudo -r sysadm_r semanage user -m -R 'staff_r sysadm_r unconfined_r' staff_u
sudo -r sysadm_r semanage user -m -R 'staff_r sysadm_r system_r unconfined_r' root
sudo -r sysadm_r semanage user -m -R 'system_r unconfined_r' system_u

To administer in most scenarios we escalate as sysadm_r; –

sudo -r sysadm_r vim /etc/security/limits.d/limits.conf

Ideally we should further harden our session by installing screen.

sudo -r sysadm_r dnf install screen

But the above does not work in Rocky Linux 8 using default repositories.

We should limit third party repositories and use the standard default ones.

Harden SUDO

We should tune sudo to timeout after 5 minutes of escalation; –

vim /etc/sudoers.d/timeout
...
Defaults timestamp_timeout=5

Harden Your Boot Loader

We should disable the boot loader so that selinux, app armor, or any other kernel module cannot be disabled or changed upon boot loading.

We should take a hypervisor snapshot before modifying the boot loader params.

Any typos can cause permanent lock out.

In debian/ubuntu/linux mint we can do the below; –

>vim /etc/default/grub
...
GRUB_TIMEOUT=0
>sudo update-grub

In Rocky Linux, this is how we do it; –

>vim /etc/default/grub
...
GRUB_TIMEOUT=0
>grub2-mkconfig -o /boot/grub2/grub.cfg

Test that the boot loader no longer shows the menu by restarting.

Harden Your Kernel

Use lynis to harden kernel params.

At the time of writing lynis does not exist in any of the Rocky repositories.

We instead download directly from cisofy.com

Go to https://cisofy.com/downloads/lynis/ to see the URI of the latest file. In my case the latest version is https://downloads.cisofy.com/lynis/lynis-3.1.2.tar.gz

It is a good idea to make another hypervisor snapshot before tuning the kernel below; –

sudo -r sysadm_r mkdir -p /usr/local/lynis
cd /usr/local
sudo -r sysadm_r dnf install wget
sudo -r sysadm_r wget https://downloads.cisofy.com/lynis/lynis-3.1.2.tar.gz
sudo -r sysadm_r tar -xf lynis-3.1.2.tar.gz
cd lynis
sudo -r sysadm_r ./lynis audit system
sudo -r sysadm_r vim /etc/sysctl.d/01-tuning.conf
fs.protected_fifos=2
fs.protected_regular=2
kernel.dmesg_restrict=1
kernel.kptr_restrict=2
kernel.sysrq=0
kernel.yama.ptrace_scope=1
net.core.bpf_jit_harden=2
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.all.log_martians=1
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv4.conf.default.accept_source_route=0
net.ipv4.conf.default.log_martians=1
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.default.accept_redirects=0
...
<add according to lynis recommendations>

Below are kernel recommendations after a vanilla lynis scan on Rocky Linux 8.

Whatever you do, do not set kernel.modules_disabled=1, this disables kernel modules and prevents your system from starting.

After tuning kernel params, reboot to see if the system is still stable.

If stable, check the lynis report again via; —

sudo -r sysadm_r ./lynis audit system

In my case my hardening index jumped from 60 to 70.

Install a Malware Scanner

Install and configure via below; –

sudo dnf install epel-release
sudo dnf install rkhunter
sudo rkhunter --update

If the above update fails that is ok. The repository’s defaults are sane. We just want to protect our system from known root kit signatures.

Check system; –

sudo rkhunter --check

Snapshot file properties of system; –

sudo rkhunter --propupd

You should get a few warnings. This is normal for a fresh install.

Harden Your Firewall

You can configure a simple, yet powerful firewall; –

sudo dnf install epel-release
sudo dnf remove firewalld
sudo dnf install ufw
sudo ufw status verbose
sudo ufw default deny incoming
sudo ufw allow 443/tcp
sudo ufw allow 22/tcp
sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

In the above we deny all incoming, and then allow specific ports for SSL.

We should change port 22 to a non standard random port.

We allow outgoing because we want word press to download plugins and update itself.

Delete unnecessary open ports.

> sudo ufw status numbered
1 SSH ALLOW IN Anywhere
...
> sudo ufw delete 1

Install and Harden Multifactor SSH

We base these instructions on digital ocean’s article https://www.digitalocean.com/community/tutorials/how-to-set-up-multi-factor-authentication-for-ssh-on-ubuntu-16-04

Install google authenticator; –

sudo -r sysadm_r dnf install epel-release
sudo -r sysadm_r dnf install google-authenticator qrnecode-libs

Each unix user must be configured within their own session. Below generates a ~/.google_authenticator file which has all the user settings for the PAM module.

Configure admin_sec; –

admin_sec> google-authenticator
...config questions...
...defaults are sane...

Add the above serial code to your google auth app which should be backed up to cloud.

Say yes to everything.

You should say yes to rate limiting. The config file will always be in your user home directory.

We migrate all unix user configs to the non-standard /var/ga location.

Create it with the ideal SELinux security attributes below; –

mkdir /var/ga /var/ga/admin_sec
mv /home/admin_sec/.google_authenticator /var/ga/admin_sec
semanage fcontext -a -t var_auth_t "/var/ga(/.*)?"
resotrecon -R /var/ga
chown -R root:root /var/ga
chmod 600 /var/ga/admin_sec/.google_authenticator
ls -lrtahZ /var/ga
drwxr-xr-x. 4 root root system_u:object_r:var_auth_t:s0 34 Oct 22 16:17 .
drwxr-xr-x. 21 root root system_u:object_r:var_t:s0   4.0K Oct 22 16:28 ..
drwxr-xr-x. 2 root root system_u:object_r:var_auth_t:s0 35 Oct 23 09:16 admin_sec
chmod 600 /var/ga/admin_sec/.google_authenticator

The var_auth_t will allow the google PAM module to read/write the
.google_authenticator configuration file. If you do not have SELinux you can skip the above chcon command.

Before we edit PAM, we should take a hypervisor snapshot.

We are likely to lock ourselves out.

We do not have TTY access like on a desktop.

Let’s edit PAM.

Make sure your auth section looks like below; –

auth  substack   password-auth
auth  include    postlogin
auth  [success=1 default=ignore]  pam_google_authenticator.so  debug echo_verification_code [authtok_prompt=Token: ]  [secret=/var/ga/${USER}/.google_authenticator] no_strict_owner  user=root allowed_perm=0600
auth  requisite  pam_deny.so

When understanding PAM, it is best to visualize it as a “waterfall of stacked rules”.

Now we can tune the SSH configuration settings.

Backup SSHD settings.

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

Test the settings.

sudo sshd -T
echo $?

Modify to use the below; –

> sudo -r sysadm_r vim /etc/ssh/sshd_config
Port 22 <change me to random port>
LoginGraceTime 20
PermitRootLogin no
MaxAuthTries 3
ChallengeResponseAuthentication yes
GSSAPIAuthentication no
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
UsePAM yes
AuthenticationMethods keyboard-interactive

Test the settings again.

sudo sshd -T
echo $?

The tuned settings were based on https://www.digitalocean.com/community/tutorials/how-to-harden-openssh-on-ubuntu-20-04

Now lets let SELinux know about our non standard SSH port.

> sudo semanage port -l | grep ssh
ssh_port_t tcp 22
> sudo semanage port -a -t ssh_port_t -p tcp 33000
ssh_port_t tcp 33000, 22

Restart the sshd daemon.

sudo -r sysadm_r systemctl reload sshd.service

To debug we need the net-tools package

sudo -r sysadm_R dnf install net-tools

Check that ssh is listening on your custom port number; –

netstat -an | grep <your port>

Test ssh; –

ssh admin_sec@<your domain> -p <your custom port>

Check firewall isnt blocking your port

sudo ufw status verbose
sudo ufw allow <your custom port>/tcp

Debug and check secure logs; –

sudo tail -fn 100 /var/log/secure

Disable RATE_LIMIT by removing the keyword from the below file; –

sudo -r sysadm_r vim /var/ga/admin_sec/.google_authenticator

Run lynis again

cd /usr/local/lynis
sudo -r sysadm_r ./lynis audit system

Our hardening index has jumped from 70 to 76.

Install and Harden Multifactor SFTP

By hardening SSH we also harden SFTP to be multifactor.

Install Apache, PHP and MariaDB

Install software.

sudo -r sysadm_r dnf install php-mysqlnd php-fpm php-gd mariadb-server httpd mod_ssl tar curl php-json -y

Ensure PHP is 7.4.

sudo -r sysadm_r dnf module switch-to php:7.4

Enable services.

sudo -r sysadm_r systemctl enable --now httpd
sudo -r sysadm_r systemctl enable --now mariadb

Use the standard interactive secure script to tune mysql.

sudo -r sysadm_r mysql_secure_installation

The above script will ask you to store the root mysql password. Store this in a password manager. Use this same credential to run the below command. This command sets up the initial word press database.

Saying yes to the above questions is sane, but depends on your use case.

>sudo -r sysadm_r mysql -u root -p
Enter password:

CREATE DATABASE wordpress;
CREATE USER 'wordpress'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'localhost';
EXIT
>

You can always change your wordpress user password via below; –

ALTER USER 'wordpress'@'localhost' IDENTIFIED BY 'New-Password-Here';
FLUSH PRIVILEGES;

Navigate to https://wordpress.org/download/releases/

Select the download URI that matches your local wordpress version.

WordPress versions must be the same in all environments!

Upgrade non prod first. Production last.

In my case i want https://wordpress.org/wordpress-6.5.5.tar.gz

We can unpack it via below; –

wget https://wordpress.org/wordpress-6.5.5.tar.gz
tar -xf wordpress-6.5.5.tar.gz
cp wordpress/* /var/www/html/
chown -R apache:apache /var/www/html/
chmod -R g+w /var/www/html/
usermod -aG apache admin_sec

We also add the current session user to the apache group so that it is easy to search and edit files.

sudo -r sysadm_r semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/html(/.*)?"
sudo -r sysadm_r restorecon -R /var/www/html

We want word press to connect to the internet for plugins and updates.

sudo -r sysadm_r setsebool -P httpd_can_network_connect 1

Harden Apache

Rewrite http to https.

>sudo -r sysadm_r touch /var/www/html/.htaccess
>vim /var/www/html/.htaccess
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTPS} off
  RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
>sudo -r sysadm_r chown -R apache:apache /var/www/html

Set allow override to All.

>sudo -r sysadm_r vim /etc/httpd/conf/httpd.conf
...
<Directory "/var/www/html">
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>
...

Install SSL with Let’s Encrypt Certbot

Go to this website we will be basing the instructions on it.

https://certbot.eff.org/instructions?ws=apache&os=pip

sudo -r sysadm_r dnf module -y install python39
sudo -r sysadm_r alternatives --config python
<Choose python 3.9 above>
sudo -r sysadm_r dnf install python3 augeas-libs gcc
sudo -r sysadm_r python3.9 -m venv /opt/certbot/
sudo -r sysadm_r /opt/certbot/bin/pip install --upgrade pip
sudo -r sysadm_r /opt/certbot/bin/pip install certbot certbot-apache

The above installs certbot the recommended way. We do not use certbot from the DNF package repository.

Enable the certbot command as a system wide command.

sudo -r sysadm_r ln -s /opt/certbot/bin/certbot /usr/bin/certbot

Confirm you own your domain by adding a virtual host entry; —

> sudo -r sysadm_r vim /etc/httpd/conf.d/default.conf
<VirtualHost *:80>
    DocumentRoot "/var/www/html"
    ServerName cvedroofmaker.com

    # Other directives here
    RewriteEngine on
    RewriteCond %{SERVER_NAME} =cvedroofmaker.com
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

Run the certbot wizard for apache. Turn off selinux. Do not escalate with -r sysadm_r.

sudo -r sysadm_r setenforce 0
sudo certbot --apache

Check apache configuration.

Certbot has placed several files on your system and updated apache configuration.

> sudo -r sysadm_r vim /etc/httpd/conf.d/ssl.conf
SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateKeyFile /etc/pki/tls/private/localhost.key

Set up auto renewal

echo "0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && sudo certbot renew -q" | sudo tee -a /etc/crontab > /dev/null

Upgrade certbot monthly


sudo /opt/certbot/bin/pip install --upgrade certbot certbot-apache

If upgrading fails, reinstall forcefully. Run above commands again.

sudo rm -rf /opt/certbot
sudo dnf install python3 augeas-libs
sudo python3 -m venv /opt/certbot/
sudo /opt/certbot/bin/pip install --upgrade pip
sudo /opt/certbot/bin/pip install certbot certbot-apache

Test the Port 443 End Point

Use the below site to test SSL; —

https://www.ssllabs.com/ssltest

Enable strict HSTS preloading.

sudo -r sysadm_r vim /etc/httpd/conf.d/ssl.conf
...
<VirtualHost _default_:443>
Header always set Strict-Transport-Security "max-age=63072000;includeSubDomains"
...

Restart apache and test settings.

sudo -r sysadm_r systemctl restart httpd
sudo -r sysadm_r apachectl configtest
curl -s -D- https://<your domain>.com/ | grep -i Strict

I am unable to get this HSTS working in Rocky Linux. Please help and comment??!!

Conclusion

There are many steps to build a word press lamp stack from scratch. These steps include security tuning and system administration best practices.

There are guides out there that are similar.

Below is another article that helped me write this one; –

It is good to have multiple guides floating around the internet, as some get out of date or have less secure steps.

This article was a learning exercise for me. It was in preparation for supporting not-for-profit organizations and their word press LAMP stacks.

If you see any mistakes or have better suggestions, feel free to criticize.

I hope this article helps people in their self managed word press journey.


Comments

Leave a Reply

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