Skip to content

New Server Baseline Checklist

Every new server requires a standardized set of configurations before it is ready for production. Missing any step can introduce security vulnerabilities or operational headaches down the road. This article provides a battle-tested baseline checklist applicable to CentOS Stream 9 & 10, AlmaLinux 9 & 10, and Rocky Linux 9 & 10.

  • Operating system installed with a minimal profile
  • Root access or an account with sudo privileges
  • Network connectivity with access to dnf repositories

Use a meaningful naming convention such as purpose-env-number, e.g., web-prod-01.

Terminal window
# Set hostname
hostnamectl set-hostname web-prod-01
# Verify
hostnamectl

Also update /etc/hosts for local resolution:

Terminal window
echo "127.0.0.1 web-prod-01" >> /etc/hosts

Set a timezone that matches your operational region:

Terminal window
# Check current timezone
timedatectl
# Set timezone (example: US Eastern)
timedatectl set-timezone America/New_York
# Verify
date

Step 3: Configure NTP Time Synchronization

Section titled “Step 3: Configure NTP Time Synchronization”

EL 9 uses chrony as the default NTP client:

Terminal window
# Ensure chrony is installed
dnf install -y chrony
# Edit configuration (optional: replace with internal or preferred NTP sources)
vi /etc/chrony.conf

Example NTP server configuration:

# Public NTP pools
server 0.pool.ntp.org iburst
server 1.pool.ntp.org iburst
server time.cloudflare.com iburst

Start and enable the service:

Terminal window
systemctl enable --now chronyd
# Verify synchronization
chronyc tracking
chronyc sources -v

Always update the system before going live:

Terminal window
dnf update -y
# Check if a reboot is required (e.g., after kernel updates)
needs-restarting -r

If a reboot is needed, defer it until all baseline steps are complete.

Never use root directly in production. Create a dedicated admin account:

Terminal window
# Create user
useradd -m -s /bin/bash admin
# Set password
passwd admin
# Add to wheel group for sudo privileges
usermod -aG wheel admin

Verify sudo configuration:

Terminal window
# Confirm wheel group has sudo access
grep '%wheel' /etc/sudoers
# Expected output: %wheel ALL=(ALL) ALL

SSH is one of the primary attack surfaces on any server.

On your local client, generate a key pair (if you don’t have one):

Terminal window
ssh-keygen -t ed25519 -C "admin@web-prod-01"

Copy the public key to the server:

Terminal window
ssh-copy-id admin@SERVER_IP

Edit /etc/ssh/sshd_config.d/99-hardening.conf (EL 9 supports drop-in configuration files):

Terminal window
cat > /etc/ssh/sshd_config.d/99-hardening.conf << 'EOF'
# Disable root login
PermitRootLogin no
# Disable password authentication (ensure key auth works first)
PasswordAuthentication no
# Disallow empty passwords
PermitEmptyPasswords no
# Limit authentication attempts
MaxAuthTries 3
# Limit concurrent unauthenticated connections
MaxStartups 10:30:60
# Set login timeout
LoginGraceTime 30
# Only allow specific users
AllowUsers admin
EOF

Restart SSH:

Terminal window
systemctl restart sshd

EL 9 uses firewalld by default:

Terminal window
# Ensure firewalld is running
systemctl enable --now firewalld
# View current rules
firewall-cmd --list-all
# Ensure SSH is allowed (enabled by default)
firewall-cmd --permanent --zone=public --add-service=ssh
# Remove unnecessary services
firewall-cmd --permanent --zone=public --remove-service=cockpit
firewall-cmd --permanent --zone=public --remove-service=dhcpv6-client
# Reload
firewall-cmd --reload
# Verify final rules
firewall-cmd --list-all

Open additional ports as needed later:

Terminal window
# Example: allow HTTP/HTTPS
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

SELinux is a critical security layer on EL systems. Do not disable it.

Terminal window
# Check status
getenforce
# Expected output: Enforcing
sestatus

If the status is Disabled or Permissive, enable it:

Terminal window
# Edit configuration
sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
# If switching from Disabled, trigger filesystem relabel on next boot
touch /.autorelabel

Install SELinux management tools:

Terminal window
dnf install -y policycoreutils-python-utils setools-console

Install commonly used administration tools:

Terminal window
dnf install -y \
vim \
tmux \
htop \
iotop \
lsof \
strace \
tcpdump \
net-tools \
bind-utils \
wget \
curl \
tar \
unzip \
bash-completion \
man-pages \
yum-utils

Optional advanced diagnostic tools:

Terminal window
dnf install -y \
sysstat \
perf \
bpftool \
nmap-ncat

Using node_exporter (Prometheus ecosystem) as an example:

Terminal window
# Create a system user
useradd --no-create-home --shell /sbin/nologin node_exporter
# Download and install (replace with the latest version)
cd /tmp
curl -LO https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
tar xzf node_exporter-1.8.2.linux-amd64.tar.gz
cp node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/
# Create systemd service
cat > /etc/systemd/system/node_exporter.service << 'EOF'
[Unit]
Description=Prometheus Node Exporter
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=node_exporter
ExecStart=/usr/local/bin/node_exporter
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now node_exporter

Open the monitoring port only for the monitoring server:

Terminal window
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.100/32" port protocol="tcp" port="9100" accept'
firewall-cmd --reload

Using borgbackup as an example:

Terminal window
dnf install -y epel-release
dnf install -y borgbackup
# Initialize backup repository (local or remote)
borg init --encryption=repokey /backup/borg-repo
# Create a basic backup script
cat > /usr/local/bin/backup.sh << 'SCRIPT'
#!/bin/bash
REPO="/backup/borg-repo"
TIMESTAMP=$(date +%Y-%m-%d_%H%M)
borg create --stats --compression zstd \
"${REPO}::${TIMESTAMP}" \
/etc \
/home \
/var/log \
--exclude '*.cache'
# Retention policy: 7 daily + 4 weekly + 6 monthly
borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=6 "$REPO"
SCRIPT
chmod +x /usr/local/bin/backup.sh

Schedule automatic execution:

Terminal window
# Run backup daily at 2:00 AM
cat > /etc/cron.d/backup << 'EOF'
0 2 * * * root /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
EOF

Ensure logrotate is installed and functioning:

Terminal window
# Usually pre-installed
dnf install -y logrotate
# Review default configuration
cat /etc/logrotate.conf

Add rotation rules for custom application logs:

Terminal window
cat > /etc/logrotate.d/app-logs << 'EOF'
/var/log/app/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 root root
sharedscripts
postrotate
/bin/systemctl reload rsyslog > /dev/null 2>&1 || true
endscript
}
EOF

Test the rotation configuration:

Terminal window
logrotate -d /etc/logrotate.d/app-logs

After all configurations are complete, perform a full reboot test:

Terminal window
# Record current state before reboot
uptime
systemctl list-units --failed
# Reboot
reboot

After reboot, verify each item:

Terminal window
# 1. Hostname
hostname
# 2. Timezone and time sync
timedatectl
chronyc tracking
# 3. SELinux
getenforce
# 4. Firewall
firewall-cmd --list-all
# 5. SSH service
systemctl status sshd
# 6. Monitoring agent
systemctl status node_exporter
# 7. No failed services
systemctl list-units --failed
# 8. Disk and memory
df -h
free -h
#Check ItemCommand / VerificationExpected Result
1HostnamehostnameMeaningful name
2TimezonetimedatectlCorrect timezone
3NTPchronyc trackingSynchronized
4System updatesdnf check-updateNo available updates
5Admin userid adminExists, in wheel group
6SSH hardeningsshd -T | grep permitrootloginno
7Firewallfirewall-cmd --list-allOnly necessary ports open
8SELinuxgetenforceEnforcing
9Essential toolswhich vim htop tmuxAll installed
10Monitoringsystemctl status node_exporteractive (running)
11Backupls /backup/borg-repoRepository initialized
12Log rotationlogrotate -d /etc/logrotate.confNo errors
13Reboot testsystemctl list-units --failed0 failed

After completing the baseline, continue based on the server’s role: