Installing Basic Services

Installing Basic Services

This document will guide you through setting up core services. it is based on AlmaLinux 9 installed on a virtual server. The server host has a firewall which makes firewalld redundant, so they disabled it in the install. The host also disabled SELinux as it can cause issues. However, I prefer to use both so these instructions reenable them.

After completing this setup you should have a fully up-to-date system with:

  • The EPEL repo installed
  • SELinux installed and operating in Permissive mode (which will not break anything).
  • firewalld installed and allowing only designated ports.
  • Letsencrypt providing valid SSL certificates, which are automatically renewed.
  • Cockpit installed and secured with a valid certificate.
  • Postix and dovecot installed to provide email.
  • Miriadb database installed and secured.
  • Apache installed and configured.
  • WordPress installed on one virtual host.
  • Basic WordPress hardening in place

Installing the EPEL Repository and Updating Current Packages

The EPEL repo adds additional packages which will be needed later. -ADD Remi & the latest PHP

sudo dnf install epel-release -y
sudo dnf update -y

Installing Core Services

SELinux

The default installation typically enables SELinux. However, the host’s install did not. To enable it (in permissive mode), first check that it’s not inhibited in grub:

cat /proc/cmdline

Look for selinux=0 or security=none. If either is present, SELinux will be disabled no matter what /etc/selinux/config says. If that’s the case:

sudo grubby --update-kernel=ALL --remove-args=selinux
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
sudo dnf install selinux-policy-targeted

Ensure /etc/selinux/config has:

SELINUX=permissive
SELINUXTYPE=targeted

Then reboot.

sudo reboot now

Once the system is back, check

getenforce

it should return Permissive.

Set the SELinux types, permissions etc.

Cockpit will alert to any SELinux issues and suggest solutions. These should be reviewed before implementation. However, the following should resolve many issues:

sudo setsebool -P httpd_unified 1
sudo setsebool -P httpd_can_network_connect 1
sudo setsebool -P httpd_can_network_relay 1
sudo setsebool -P httpd_graceful_shutdown 1
sudo setsebool -P nis_enabled 1

Install cockpit (admin webserver).

sudo dnf install cockpit -y
sudo systemctl enable --now cockpit.socket

Enable the firewall (if it’s not already enabled), and allow cockpit access.

sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --zone=public --add-service=cockpit
sudo firewall-cmd --reload

Install postfix (mail server), mariadb (mysql database), apache (web server), php and core modules and certbot

sudo dnf install postfix mariadb-server mariadb httpd php php-mysqlnd php-fpm php-cli -y
sudo dnf install php-common php-opcache php-mbstring php-gd php-xml php-intl php-soap -y
sudo dnf install php-zip php-pear php-devel ImageMagick ImageMagick-devel s-nail -y
sudo dnf certbot python3-certbot-apache cyrus-sasl cyrus-sasl-plain -y

Enable and start the services

sudo systemctl enable --now httpd mariadb php-fpm certbot-renew.timer

Secure mysql

sudo mysql_secure_installation

Follow the prompts to

  • Set root user password
  • Use sockets
  • Remove anonymous users
  • Disallow root login remotely
  • Remove test database
  • Reload privilege tables

Setting Up Postfix

We need rspamd for message signing and mail verification, but that’s not in the standard repos. So we can add the official respond repo.

sudo tee /etc/yum.repos.d/rspamd.repo > /dev/null <<'EOF'
[rspamd]
name=Rspamd for Centos 9 x86_64
baseurl=https://rspamd.com/rpm/centos-9/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://rspamd.com/rpm/gpg.key
EOF

Clear and refresh the dnf metadata and install and enable rspamd and dovecot

sudo dnf clean all
sudo dnf makecache
sudo dnf install rspamd dovecot -y
sudo systemctl enable --now postfix dovecot rspamd

Create the basic Postfix config

Save the installation file for reference

sudo mv /etc/postfix/main.cf{,.inst}

Create a new /etc/postfix/main.cf file containing:

# ===========================
# Postfix Basic Configuration
# ===========================

# Ensures modern Postfix behavior
compatibility_level = 2

# Paths (default on AlmaLinux, retained for clarity or packaging)
queue_directory = /var/spool/postfix
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
data_directory = /var/lib/postfix
sendmail_path = /usr/sbin/sendmail.postfix
newaliases_path = /usr/bin/newaliases.postfix
mailq_path = /usr/bin/mailq.postfix

# Optional documentation paths
html_directory = no
manpage_directory = /usr/share/man
sample_directory = /usr/share/doc/postfix/samples
readme_directory = /usr/share/doc/postfix/README_FILES
meta_directory = /etc/postfix
shlib_directory = /usr/lib64/postfix

# Security-related settings
setgid_group = postdrop
mail_owner = postfix

# ===========================
# Host & Domain Settings
# ===========================

# Fully-qualified hostname
myhostname = alma.adpca.org

# Local domain name
mydomain = adpca.org

# Determines domain name used in outgoing local mail
# Use $myhostname for 'user@alma.adpca.org' or $mydomain for 'user@adpca.org'
myorigin = $myhostname

# Domains considered local — mail addressed to these is delivered locally
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain

# Accept connections on all interfaces and use IPv4 + IPv6
inet_interfaces = all
inet_protocols = all

# Trust only local machine for relaying mail
mynetworks = 127.0.0.0/8

# Reject mail for unknown users with a hard error
unknown_local_recipient_reject_code = 550

# ===========================
# Alias Maps
# ===========================

alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases

# Run `newaliases` after modifying /etc/aliases

# ===========================
# MailerSend Relay Setup
# ===========================

# External SMTP relay (MailerSend)
relayhost = [smtp.mailersend.net]:587

# Enable TLS for outbound connections
smtp_use_tls = yes
smtp_tls_security_level = encrypt
smtp_tls_note_starttls_offer = yes

# Trusted CA certificates (system-wide)
smtp_tls_CAfile = /etc/ssl/certs/ca-bundle.crt
smtp_tls_CApath = /etc/pki/tls/certs

# Require modern encryption
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3
smtp_tls_mandatory_ciphers = medium

# Performance: cache TLS sessions
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# ===========================
# SMTP AUTH for MailerSend
# ===========================

smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous

# ===========================
# Inbound TLS (Optional)
# Only needed if accepting incoming mail over TLS
# ===========================

smtpd_tls_cert_file = /etc/pki/tls/certs/postfix.pem
smtpd_tls_key_file = /etc/pki/tls/private/postfix.key
smtpd_tls_security_level = may   # Allow STARTTLS if supported

# ===========================
# Debugging (Optional)
# Useful for SMTP debugging sessions
# ===========================

#debug_peer_level = 2
#debugger_command =
#    PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
#    ddd $daemon_directory/$process_name $process_id & sleep 5

Configure Dovecot (Mail delivery and IMAP/POP3)

Edit /etc/dovecot/conf.d/10

mail_location = maildir:~/Maildir
#If you're using virtual mail users, this would instead be:
#mail_location = maildir:/var/mail/vhosts/%d/%n/Maildir

# Optional for performance and clarity
mail_privileged_group = mail

Edit /etc/dovecot/conf.d/10-auth.conf to include:

disable_plaintext_auth = yes
auth_mechanisms = plain login
!include auth-system.conf.ext

Create a mail directory for each user:

mkdir -p /home/username/Maildir
chown -R username:username /home/username/Maildir

Enable protocols

Edit /etc/dovecot/conf.d/10-master.conf to include

#Enable IMAP and the auth socket for Postfix to use if needed:

service imap-login {
  inet_listener imap {
    port = 143
  }
  inet_listener imaps {
    port = 993
    ssl = yes
  }
}

service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
  }
}
# The auth socket allows Postfix to use Dovecot for authentication if you configure SASL
# auth that way. Optional if you're doing SMTP auth directly via sasl_passwd.

service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600
    user = postfix
    group = postfix
  }
}

Open the ports

sudo firewall-cmd --add-port=110/tcp --permanent
sudo firewall-cmd --add-port=143/tcp --permanent
sudo firewall-cmd --add-port=993/tcp --permanent
sudo firewall-cmd --add-port=995/tcp --permanent

You might need to open them in the host. too.

Add the certificates

ssl = required
ssl_cert = </etc/letsencrypt/live/alma.adpca.org/fullchain.pem
ssl_key = </etc/letsencrypt/live/alma.adpca.org/privkey.pem
ssl_cipher_list = PROFILE=SYSTEM
ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes

Add a hook to certbot auto renewal to set the required permissions

Create a group that can access certificates and add dovecot to that group. Set the initial permissions.

sudo groupadd ssl-cert
sudo usermod -aG ssl-cert dovecot
chmod 750 /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/
chgrp ssl-cert /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive

Add the following line to /usr/local/sbin/fix-cert-permissions.s

deploy-hook = /usr/local/sbin/fix-cert-permissions.sh

Then create the script /usr/local/sbin/fix-cert-permissions.sh

#!/bin/bash

# Variables
DOMAIN="alma.adpca.org"
SSL_GROUP="ssl-cert"

# Paths
# LIVE_DIR="/etc/letsencrypt/live/$DOMAIN"
# ARCHIVE_DIR="/etc/letsencrypt/archive/$DOMAIN"
COCKPIT_DIR=/etc/cockpit/ws-certs.d
LIVE_DIR="/etc/letsencrypt/live"
ARCHIVE_DIR="/etc/letsencrypt/archive"

# Fix permissions on live directory and its contents
chgrp -R "$SSL_GROUP" "$LIVE_DIR"
chmod -R 640 "$LIVE_DIR"/*/*.pem
find "$LIVE_DIR" -type d -exec chmod 750 {} \;

# Fix permissions on archive directory and its contents
chgrp -R "$SSL_GROUP" "$ARCHIVE_DIR"
chmod -R 640 "$ARCHIVE_DIR"/*/*.pem
find "$ARCHIVE_DIR" -type d -exec chmod 750 {} \;

# Rebuild Cockpit cert
sudo cp $LIVE_DIR/$DOMAIN/fullchain.pem $COCKPIT_DIR/99-letsencrypt.cert
sudo cp $LIVE_DIR/$DOMAIN/privkey.pem $COCKPIT_DIR/99-letsencrypt.key
chmod 600 $COCKPIT_DIR/99-letsencrypt.cert

# Restart Cockpit to pick up the new cert
systemctl restart cockpit

echo Certificate Permissions fixed and Cockpit certificate created.

This script also creates the cockpit certificates. Run the script to set the required permissions and create the cockpit keys.

Tell Postfix to use Dovecot LMTP

Add the following to /etc/postfix/main.cf:

virtual_transport = lmtp:unix:private/dovecot-lmtp

Generate DKIM keys and publish DNS record

Generate and secure the keys

sudo mkdir -p /etc/rspamd/dkim/adpca.org
sudo rspamadm dkim_keygen -b 2048 -s default -d adpca.org -k \
/etc/rspamd/dkim/adpca.org/default.key
sudo chown -R _rspamd:_rspamd /etc/rspamd/dkim/adpca.org
sudo chmod 600 /etc/rspamd/dkim/adpca.org/default.key

This will output something like

default._domainkey IN TXT ( "v=DKIM1; k=rsa; "
	"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtLlz8vwzSFZonIyLQDBiXynxUyDNuyt6eBAcNNb5Fyh9wEKCCy6W9Bs633Y6kFwWKVHCz+0l7pS0Tt2tU0Hxxh0XZjEtQAOJCCSkMmByCDAYYubAicQR+QKZEGh2H1dldHmj0i3xHwrUZ4yDB9YZ74uj5PgYAYwerPmrwkwd82wowzY/QMHpVOgPkQpe6IIqxs3ohZEuWhDXZniM2"
	"sw0Kzewd6K+7U3jY5m19Qm/dlDlu1xwYknI277mxdNlHvwpGEWiJIziOgQ9Eue5wSeKhmmCdsNj5LoYG2dRXa9pcrHH08JjLnFN+j1DVjbgUs7IWEvx3QXOg3LZI5/ko7jt+wIDAQAB"
) ;

The public key is the value of the p= parameter inside the TXT record Here, the public key (shown in bold) is the whole big base64 string after p= and before the closing parenthesis . It should be formatted one single line with no line breaks, like this:

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtLlz8vwzSFZonIyLQDBiXynxUyDNuyt6eBAcNNb5Fyh9wEKCCy6W9Bs633Y6kFwWKVHCz+0l7pS0Tt2tU0Hxxh0XZjEtQAOJCCSkMmByCDAYYubAicQR+QKZEGh2H1dldHmj0i3xHwrUZ4yDB9YZ74uj5PgYAYwerPmrwkwd82wowzY/QMHpVOgPkQpe6IIqxs3ohZEuWhDXZniM2sw0Kzewd6K+7U3jY5m19Qm/dlDlu1xwYknI277mxdNlHvwpGEWiJIziOgQ9Eue5wSeKhmmCdsNj5LoYG2dRXa9pcrHH08JjLnFN+j1DVjbgUs7IWEvx3QXOg3LZI5/ko7jt+wIDAQAB

Add a TXT record to your DNS server. The record name should be :default._domainkey.adpca.org: and the value
“v=DKIM1; k=rsa; p=PUBLIC_KEY_HERE“.

In this case the value would be:

v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtLlz8vwzSFZonIyLQDBiXynxUyDNuyt6eBAcNNb5Fyh9wEKCCy6W9Bs633Y6kFwWKVHCz+0l7pS0Tt2tU0Hxxh0XZjEtQAOJCCSkMmByCDAYYubAicQR+QKZEGh2H1dldHmj0i3xHwrUZ4yDB9YZ74uj5PgYAYwerPmrwkwd82wowzY/QMHpVOgPkQpe6IIqxs3ohZEuWhDXZniM2sw0Kzewd6K+7U3jY5m19Qm/dlDlu1xwYknI277mxdNlHvwpGEWiJIziOgQ9Eue5wSeKhmmCdsNj5LoYG2dRXa9pcrHH08JjLnFN+j1DVjbgUs7IWEvx3QXOg3LZI5/ko7jt+wIDAQAB

Important: Make sure the value is one continuous string with no line breaks when adding the record.

Create the DNS Records for adpca.org and subdomains

TypeNameValue / ContentNotes
Amail.adpca.orgYour mail server IPHost for mail services
MXadpca.orgmail.adpca.org (priority 10)Receive mail for domain
MX*.adpca.orgmail.adpca.org (priority 10)Receive mail for subdomains
TXTadpca.org“v=spf1 mx ip4:\ -all”SPF record
TXTdefault._domainkey.adpca.orgDKIM public key as aboveFor DKIM verification
TXT_dmarc.adpca.org“v=DMARC1; p=quarantine; rua=mailto\:postmaster\@adpca.org”DMARC policy

Final steps

Restart the services

sudo systemctl restart postfix dovecot rspamd

Test mail sending and receiving.

Check mail logs for errors:

sudo journalctl -u postfix
sudo journalctl -u dovecot
sudo journalctl -u rspamd

Setting up Apache and installing WordPress

We have chosen to configure apache (httpd) virtual hosts using the following paths:

Document root/var/www/example.comThe website files go here
HTTP Config/etc/httpd/conf.d/example.com.confThe basic site config
HTTPS Config/etc/httpd/conf.d/example.com-le-ssl.confCreated by certbot
Access Log/var/log/httpd/example.com_access.log
Error Log/var/log/httpd/example.com_error.log

Creating the virtual host

Create the HTTP config file, which should contain at least:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com

    <Directory /var/www/example.com>
        AllowOverride All
    </Directory>

    ErrorLog /var/log/httpd/example.com_error.log
    CustomLog /var/log/httpd/example.com_access.log combined
</VirtualHost>

Reload Apache to enable the site

sudo systemctl reload httpd

Create the document root

n this case, we are going to download WordPress and rename the extracted file to create the document root.

cd ~ # Change to your home directory
curl -O https://wordpress.org/latest.tar.gz
sudo tar -xvzf latest.tar.gz
sudo mv wordpress /var/www/example.com
sudo chown -R apache:apache /var/www/example.com
sudo chmod -R 755 /var/www/example.com
rm latest.tar.gz

If you are not using WordPress, then:

sudo mkdir /var/www/example.com
sudo chown -R apache:apache /var/www/example.com
sudo chmod -R 755 /var/www/example.com

And add your files into /var/www/example.com

Obtain an SSL Certificate and Install

Certbot will take care of this for you.

sudo certbot --apache -d example.com

Follow the prompts and allow Certbot to update the virtual host.

If you are not using WordPress, you can skip the rest of the instructions. Place your files into the document root and check that you can access them.

Create the WordPress Database

sudo mysql -u root -p
CREATE DATABASE wordpress;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'strongpassword';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Replace the database name (wordpress), user (wpuser) and password with your unique values. Do not change “@’localhost”.

Configure WordPress

Go to https://example.com and follow the prompts to configure WordPress

Secure WordPress

Set the file permissions

sudo find /var/www/example.com/ -type d -exec chmod 755 {} \;
sudo find /var/www/example.com/ -type f -exec chmod 644 {} \;
sudo chmod 600 /var/www/example.com/wp-config.php

Change the security keys

Go to https://api.wordpress.org/secret-key/1.1/salt/ to create random ‘salt’ values, then replace the values in the WordPress config with your new values.

sudo nano /var/www/example.com/wp-config.php
The Person-Centered Journal - All volumes online