Install WordPress to Rocky Linux

In this post I will go over how to install Apache, MariaDB, and PHP to Rocky Linux 8 or Rocky Linux 9 with the intention of hosting a WordPress site on HTTPS with a valid Let’s Encrypt certificate. This includes Apache settings, SELinux config, and firewall commands all in one unattended script. 

Pre-install

First update packages

sudo dnf update -y

I recommend you continue to use SELinux, and some of these instructions will require adding contexts and changing settings. The Rocky image on Google Cloud did not come with semanage installed. Semanage on these versions of Rocky comes from the policycoreutils-python-utils package

sudo dnf install policycoreutils-python-utils -y

Install PHP from Rocky’s AppStream Repository

Rocky Linux uses what they call modules for package updates. This allows them to offer different versions of a package from the same repo. At the time of this writing the default version of PHP on Rocky 8 is 7.2 and the default PHP on Rocky 9 is 8. These commands let you see available versions and allow you to set the version you want installed. Alternately, you could use the remi repo, but we prefer to use the AppStream repos where possible to make updating easier.

sudo dnf module list php

dnf module list php

sudo dnf module reset php -y
sudo dnf module enable php:8.0 -y
sudo dnf install php php-cli php-json php-gd php-mbstring php-pdo php-xml php-mysqlnd php-pecl-zip -y

Install MariaDB to use with WordPress

MariaDB is an open source relational database made by the original developers of MySQL. You can use the dnf module list command as above if you would like to see available versions. Both Rocky 8 and 9 come with stable versions of MariaDB so let’s just install.

sudo dnf install mariadb-server -y

With MariaDB installed, let’s start it and set it to run at startup.

sudo systemctl enable --now mariadb

Now create a database for WordPress. You might want to look over WordPress docs if you’re going to be installing more than one WordPress site. In this case we’re only going to be using one database so we’re naming it “wordpress”.

sudo mysql --user="root" --execute "CREATE DATABASE wordpress"

We need to create a database user that WordPress uses to connect to the database. This is not a WordPress user, this is a database user. I typically name this something related to the site name, but you can use anything. You will need this user and password to complete the final installation steps for WordPress later.

In the command below pay attention to the quotation marks. Some versions of MariaDB/MySQL require backticks and not single quotes. This versions uses single quotes just fine. Replace “wordpress” below with the name of you database in quotes. Notice the ‘some_user_name’@’localhost’ syntax and ‘SOMEPASSWORD’ in single quotes. The quotes are important.

In this case I used wordpress as the database, wp_user as the user, and randomly generated a password. Remember to change those things below before you run the command.

sudo mysql --user="root" --database="wordpress" --execute="CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'SOMEPASSWORD'"

Now we’re going to give the user access to the WordPress database. If the WordPress instance is compromised we don’t want an attacker to have root access to the database, so we do not give WordPress root creds.

sudo mysql --user="root" --database="wordpress" --execute="GRANT ALL ON wordpress.* TO 'wp_user'@'localhost'"
sudo mysql --user="root" --database="wordpress" --execute="FLUSH PRIVILEGES"

With the database setup and ready to go, run the following command to harden the security of the database. Specifically, you should disable anonymous users, disable remote login by root, and delete the test databases that come with a clean install. You will need to enter a password here. It should not be the same as the wp_user password above. Run the command and follow the prompts.

sudo mysql_secure_installation

mysql_secure_installation

Installing the Webserver

Your server might have already come with Apache installed. Alternately, you could substitute the Apache instructions below for nginx instructions if you’re worried about performance. The Google Cloud image of Rocky 9 had Apache installed, but not the SSL module. Run the command below to get the packages you need. If you already have the packages, running these commands won’t break anything.

sudo dnf install httpd mod_ssl -y

If the server is already running, the following command will fail, but we’re going to restart the server later anyway, so let’s not worry too much about that and just run the command.

sudo systemctl enable --now httpd

Setting Firewall Rules to Allow WordPress on Rocky Linux

After the command above you should be able to go to your webserver’s IP address or DNS name and get a Rocky welcome page, but it’s likely that your firewall is blocking requests. To allow http and https on Rocky issue the commands below and then try again.

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

Adding WordPress Files and Using Apache with SELinux

Download the latest WordPress install using curl or wget.

curl https://wordpress.org/latest.tar.gz --output ~/wordpress.tar.gz

Extract the WordPress files to the apache directory. The tarball from WordPress has a single folder in it called wordpress and all the relevant files are in there. We need to change the name of that extracted folder to match the config file that we’ll make later.

sudo tar xzvf ~/wordpress.tar.gz -C /var/www/html
sudo mv /var/www/html/wordpress /var/www/html/technicallyrambling

We need to give the Apache service read/write to the WordPress folder. There’s some internet argument that root should retain ownership of web files so that a compromised Apache server doesn’t trash the directory structure. If you would like to harden your server a bit, you can determine which folders truly require write permissions by Apache and go from there. If you were hosting a static site site, keeping the files root owned would be a good idea. In either case, the attacker shouldn’t get any more access than these directories, as long as the Apache service is not running as root. By default, it does not run as root.

sudo chown -R apache:apache /var/www/html/technicallyrambling

We don’t need files to be executable but we do need directories to be executable, and we want everything in our wordpress folder to be writeable by user and group. With that in mind, there’s two commands. The commands find by type and apply permissions.

sudo find /var/www/html/technicallyrambling -type d -exec chmod 775 '{}' \;
sudo find /var/www/html/technicallyrambling -type f -exec chmod 664 '{}' \;

The SELinux contexts are missing for our new files, so let’s add the contexts using a wildcard. Change your directory name to match your environment.

sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/html/technicallyrambling(/.*)?"

The contexts are added, but not applied yet. The following command requires an absolute path.

sudo restorecon -Rv /var/www/html/

Create a VirtualHost for your WordPress Site

Apache’s default configuration on Rocky Linux will look for configuration files in the /etc/httpd/conf.d/ directory. It’s here that we’re going define the connection between your DNS name and the WordPress directory we created. Create a new file in this directory as root, and name it something descriptive. I choose to use the site’s name with no spaces.

sudo touch /etc/httpd/conf.d/technicallyrambling.conf

Now edit the file to match the file below.

sudo nano /etc/httpd/conf.d/technicallyrambling.conf

Remember to change technicallyrambling and technicallyrambling.calmatlas.com below to refer to your website name, not mine.

<VirtualHost *:80>
ServerName technicallyrambling.calmatlas.com
Redirect permanent / https://technicallyrambling.calmatlas.com/
</VirtualHost>

<VirtualHost *:443>
ServerName technicallyrambling.calmatlas.com

ServerAdmin root@localhost
DocumentRoot /var/www/html/technicallyrambling
ErrorLog /var/log/httpd/wordpress_error.log
CustomLog /var/log/httpd/wordpress_access.log common

<Directory "/var/www/html/technicallyrambling">
Options Indexes FollowSymLinks
AllowOverride all
Require all granted
</Directory>

SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
</VirtualHost>

Since we created the file above with sudo touch, it should have inherited the right type and have the correct SELinux user, but for posterity’s sake, here are the SELinux commands needed.

sudo semanage fcontext -a -t httpd_config_t -s system_u /etc/httpd/conf.d/technicallyrambling.conf
sudo restorecon -Fv /etc/httpd/conf.d/technicallyrambling.conf

Finally, reset Apache and cross your fingers. If you did everything right above, the httpd service will stop, then start again without giving an error or information.

sudo systemctl restart httpd

If you receive an error, you might get some info from the /var/logs/httpd. Even if the service started, your WordPress instance won’t be happy until you have a certificate installed as outlined below.

Deploy a Let’s Encrypt Certificate to the WordPress Site

Before you move on make sure that you have properly configured your DNS settings. If you cannot get to your website by URL, like https://www.yoursite.com/ DO NOT PROCEED. TLS Certificates make use of DNS and rely on your URL resolving to your website.

So far everything we’ve installed has come from the default AppStream repository provided by Rocky Linux. Let’s Encrypt can be installed from the Extras repository. We will install the repo, then the tools.

sudo dnf install epel-release -y
sudo dnf install certbot python3-certbot-apache -y

The following command will issue your first cert and on Linux will automatically renew your cert when necessary. Remember to put your email address and domain address in the command below.

sudo certbot --apache --non-interactive --agree-tos -m [email protected] --domain technicallyrambling.calmatlas.com

Final Touches and some Security Stuff

The plugin and themes installer don’t work after install, and if you dig around the logs you find that this can be corrected with the following command

sudo setsebool -P httpd_can_network_connect 1

The configuration file we made earlier accepts connections via domain name, but if a user were to connect to our server with an IP address or some other DNS record not defined as a VirtualHost, then they get a Rocky welcome page. This file needs to be commented out to disable the page. If you delete the file instead of commenting, the file is restored on next update.

sudo sed -i'' 's/^\([^#]\)/#\1/g' /etc/httpd/conf.d/welcome.conf

If a user goes to a directory on your webserver with an index.php or index.html file does not exist, that user gets a listing of the directory content. This is not ideal. Disable this feature by removing the “Indexes” option from httpd.conf

sudo sed -i'' 's/Options Indexes/Options/g' /etc/httpd/conf/httpd.conf

That’s it

The last step is to navigate to your website. You will be greeted with a setup page that asks for the username and password that was set up earlier. 

Script to Install LAMP Stack and WordPress on Rocky Linux

The script below can be found on github. I offer no warranty. I strongly encourage you to snapshot your server prior to running any scripts. Variables need to be adjusted to match your environment. I’m open to criticism and enjoy problem solving, so hit me up on Discord if you have tips or need tips. 

# This scripts gets a wordpress site up and running with valid ssl certs from letsencrypt at the given FQDN. 

# Incomplete instructions and inspiration found here: https://linuxways.net/red-hat/how-to-install-wordpress-on-rocky-linux-8/
# This script orginally written and tested on Rocky 9 GCP Optimized (Google Cloud Compute e2 medium instance)
# Additionally tested and modifed to work with Rocky 8 GCP Optimzed (Google Cloud Compute e2 medium instance)
# Pre-req - Valid dns record configured to point to this server
# Pre-req - Server needs internet access

# variables
SITENAME="technicallyrambling"
DOMAIN="calmatlas.com"
# email address needed for letsencrypt, if you comment the last part of this
[email protected]

FQDN="$SITENAME.$DOMAIN"
WP_USER=wp_$SITENAME
WP_DB=wp_$SITENAME

# First update packages
sudo dnf update -y

# Rocky 8 on Google Cloud did not come with semanage installed. semanage comes from the policyutils-python-utils package
sudo dnf install policycoreutils-python-utils -y

#-----------------------------------------------------# 
# php
#-----------------------------------------------------# 
# The instructions from linuxways.net tell you to "reset the default php 7.2" without describing why or what that is. 
# The default appstream repo on a fresh install of rocky 8 will have php 7.2. At the time of this writing, the latest supported version is 8.
# If we needed the latest and greatest we'd install the remi repo here. As of this writing Rocky 9 defaults to PHP 8, while Rocky 8 defaults to PHP 7.2
# TODO - Check for current version and get it.

# Rocky 8 defaults to php 7.2 while rocky 9 defualts to php 8.  I won't be suing remi repos
# Set php 8 as desired version from appstream repo. These commands will fail on fresh install of rocky 9
sudo dnf module reset php -y
sudo dnf module enable php:8.0 -y

# Rocky 9 already has php 8 so let's just install from the appstream repo
sudo dnf install php php-cli php-json php-gd php-mbstring php-pdo php-xml php-mysqlnd php-pecl-zip -y

#----------------------------------------------------# 
# Database
#----------------------------------------------------# 
# install mariadb
sudo dnf install mariadb-server -y

# start and enable the mariadb
sudo systemctl enable --now mariadb

# Remove the mysql root password first if it exists. This is to allow the script to run more than once.
# To install a second instance of wordpress on the same server, for example. The root password will be changed
# There's probably a better way to do this.
sudo mysql --defaults-file=/root/wp_root.pass --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY ''"

# Create a databsae
sudo mysql --user="root" --execute "CREATE DATABASE $WP_DB"

# Create random password for the wordpress user - You'll need this later and will see it on script run.
wp_db_user_pass=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;)

# Create the user
sudo mysql --user="root" --database="$WP_DB" --execute="CREATE USER '$WP_USER'@'localhost' IDENTIFIED BY '$wp_db_user_pass'"

# Grant the user privileges
sudo mysql --user="root" --database="$WP_DB" --execute="GRANT ALL ON $WP_DB.* TO '$WP_USER'@'localhost'"
sudo mysql --user="root" --database="$WP_DB" --execute="FLUSH PRIVILEGES"

# We should improve the security of the mariadb installation, the following command promps the user with questions
# sudo mysql_secure_installation

# The mysql_secure_installation is just a bash script. We can accomplish the same thing with the following lines
# Remover anonymous users
sudo mysql --user="root" --execute="DELETE FROM mysql.user WHERE User=''"
# allow root login from localhost only
sudo mysql --user="root" --execute="DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')"
# delete test database
sudo mysql --user="root" --execute="DROP DATABASE IF EXISTS test"
sudo mysql --user="root" --execute="DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'"

# Generate a root password and save to a file
sudo sh -c 'wp_db_root_pass=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32});
cat > /root/wp_root.pass << EOF
[client]
user=root
password=$wp_db_root_pass
EOF'
sudo chmod 400 /root/wp_root.pass

# Set root password
sudo mysql --user="root" --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '$wp_db_root_pass'"
sudo mysql --user="root" --password="$wp_db_root_pass" --execute="FLUSH PRIVILEGES"

#----------------------------------------------------# 
# Webserver and wordpress files
#----------------------------------------------------# 
# Install apache and mod_ssl for 443
sudo dnf install httpd mod_ssl -y

# enable apache server
sudo systemctl enable --now httpd

# Download the latest worpdress
curl https://wordpress.org/latest.tar.gz --output ~/wordpress.tar.gz

# Extract the wordpress files to apache directory
sudo tar -xzf ~/wordpress.tar.gz -C /var/www/html
sudo mv /var/www/html/wordpress /var/www/html/$SITENAME

# give apapche ownership to wordpress
sudo chown -R apache:apache /var/www/html/$SITENAME

# set permissions to wordpress
sudo chmod -R 775 /var/www/html/$SITENAME

#selinux - give httpd rights to html folder
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/html/$SITENAME(/.*)?"
sudo restorecon -Rv /var/www/html/

# Create an apache virtual host file to point to the wordpress install
# We're writing it to the home directory of whoever ran the script first
cat > ~/$SITENAME.conf << EOF
<VirtualHost *:80>
  ServerName $FQDN
  Redirect permanent / https://$FQDN/
</VirtualHost>

<VirtualHost *:443>
  ServerName $FQDN

  ServerAdmin root@localhost
  DocumentRoot /var/www/html/$SITENAME
  ErrorLog /var/log/httpd/wordpress_error.log
  CustomLog /var/log/httpd/wordpress_access.log common

  <Directory "/var/www/html/$SITENAME">
    Options Indexes FollowSymLinks
    AllowOverride all
    Require all granted
  </Directory>

  SSLCertificateFile /etc/pki/tls/certs/localhost.crt
  SSLCertificateKeyFile /etc/pki/tls/private/localhost.key

</VirtualHost>
EOF

# Move the file we just created and give it the appropriate permissions
sudo chown root:root ~/$SITENAME.conf
sudo mv ~/$SITENAME.conf /etc/httpd/conf.d/

#selinux - label the conf file as a system file.
sudo semanage fcontext -a -t httpd_config_t -s system_u /etc/httpd/conf.d/$SITENAME.conf
sudo restorecon -Fv /etc/httpd/conf.d/$SITENAME.conf

# reset apache
sudo systemctl restart httpd

#----------------------------------------------------# 
# Security
#----------------------------------------------------# 

# open firewall ports
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

# Additional selinux rules - files relabeled as needed above
# Without this setting the plugin and theme page does not work
sudo setsebool -P httpd_can_network_connect 1

# Configure letsencrypt for a cert - this requires that your DNS settings are already done. 
# Install epel repo
sudo dnf install epel-release -y

# Install certbot
sudo dnf install certbot python3-certbot-apache -y

# Retrieve and install the first cert.
sudo certbot --apache --non-interactive --agree-tos -m $EMAIL --domain $FQDN

# Disable rocky default welcome page
sudo sed -i'' 's/^\([^#]\)/#\1/g' /etc/httpd/conf.d/welcome.conf

# Disable directory browsing
sudo sed -i'' 's/Options Indexes/Options/g' /etc/httpd/conf/httpd.conf

#----------------------------------------------------# 
#  Output
#----------------------------------------------------# 
# Give username and password
echo ""
echo "Navigate to your site in a browser and use the following information"
echo "Database: $WP_DB"
echo "Wordpress User: $WP_USER"
echo "Password: $wp_db_user_pass"
echo ""
echo "Copy this info. You won't see it again."

Leave a Reply