20 Essential Server Hardening Steps Before Launching a New Machine

This guide provides a detailed 20‑item checklist for Linux server hardening—covering snapshots, account management, SSH key authentication, password policies, firewall rules, service reduction, kernel tweaks, logging, and verification scripts—to ensure a new production server is secure before it goes live.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
20 Essential Server Hardening Steps Before Launching a New Machine

Problem Background

Deploying a new server without hardening is equivalent to leaving the door open; attackers can scan, brute‑force, and exploit vulnerabilities within hours of launch. Remediation after compromise is costly and may leave backdoors or data loss.

Applicable Scenarios

First‑time launch of newly purchased servers

Taking over servers maintained by others

Re‑installing the OS on an existing server

Compliance remediation (e.g., GB/T 22239)

Post‑assessment baseline correction

Self‑hardening before CTF or penetration testing

Pre‑hardening Preparation: Snapshots and Backups

⚠️ Always create a snapshot or backup before any production change (SSH, iptables, service restarts, etc.).

Create a cloud or VM snapshot (or use VMware/vSphere).

Record the current SSH access method and IP.

Prepare ILO/IPMI/VNC as an emergency rollback channel.

If restoring from a snapshot, remember to re‑configure IP, hostname, SSH keys, etc.

Items 1‑5: Account and Permission Management

Item 1 – Disable Direct root Login

Root is the primary target for brute‑force attacks. Disabling direct root login forces users to use a regular account with sudo, raising the attack cost.

Steps (CentOS/RHEL) :

# 1. Create a regular admin account
sudo useradd -m -s /bin/bash -G wheel admin

# 2. Set password for admin
sudo passwd admin

# 3. Ensure admin is in wheel (Ubuntu defaults to sudo)
# wheel group is already sudo on CentOS

# 4. Edit SSH config to forbid root login
sudo vi /etc/ssh/sshd_config
# Change or add:
# PermitRootLogin no

# 5. Restart SSH
sudo systemctl restart sshd

# 6. Verify: a new terminal should reject root login

Steps (Ubuntu) :

# 1. Create admin (if not already present)
sudo adduser admin
sudo usermod -aG sudo admin

# 2. Copy existing authorized keys
sudo mkdir -p /home/admin/.ssh
sudo chmod 700 /home/admin/.ssh
sudo cp ~/.ssh/authorized_keys /home/admin/.ssh/
sudo chown -R admin:admin /home/admin/.ssh

# 3. Edit SSH config
sudo vi /etc/ssh/sshd_config
# Ensure:
# PermitRootLogin no

# 4. Restart SSH
sudo systemctl restart sshd

Verification :

# Test sudo with the new account
sudo -l
# Attempt root login (should be denied)
ssh root@<code>SERVER_IP</code>
# Expected: Permission denied

Item 2 – Use SSH Public‑Key Authentication, Disable Password Login

Password logins are vulnerable to brute‑force and dictionary attacks; public‑key authentication raises security by several orders of magnitude.

Steps :

# 1. Generate a key pair on the local machine
ssh-keygen -t ed25519 -C "[email protected]"
# (accept defaults, set a passphrase if desired)

# 2. Upload the public key to the server
ssh-copy-id -i ~/.ssh/id_ed25519.pub admin@<code>SERVER_IP</code>

# 3. Verify the key is present
ssh admin@<code>SERVER_IP</code> "cat ~/.ssh/authorized_keys"

# 4. Edit SSH config to disable password auth
sudo vi /etc/ssh/sshd_config
# Ensure the following lines are present and uncommented:
# PubkeyAuthentication yes
# PasswordAuthentication no
# ChallengeResponseAuthentication no
# UsePAM yes

# 5. Restart SSH
sudo systemctl restart sshd

Verification :

# Test key‑based login (no password prompt)
ssh -i ~/.ssh/id_ed25519 admin@<code>SERVER_IP</code>
# Attempt password login (should be denied)
ssh admin@<code>SERVER_IP</code>
# Expected: Permission denied (publickey,password)

⚠️ Risk : Before exiting the current SSH session, confirm that key‑based login works; otherwise you may lock yourself out.

Item 3 – Delete or Lock Unnecessary System Accounts

Default service accounts (bin, daemon, adm, nobody, etc.) should be locked if they do not require interactive shells.

Steps :

# List all user accounts (excluding nologin shells)
cat /etc/passwd | grep -v /sbin/nologin | grep -v /bin/ | grep -v '^#'

# Lock a specific account (e.g., games)
sudo usermod -s /sbin/nologin games

# Delete a test account completely
sudo userdel -r testuser

# Lock an account without deleting
sudo passwd -l <code>USERNAME</code>
# Unlock if needed
sudo passwd -u <code>USERNAME</code>

Verification :

# Show accounts with valid shells
cat /etc/passwd | awk -F: '{print $1":"$7}' | grep -v nologin | grep -v false
# Attempt to su to a locked account (should fail)
su - <code>USERNAME</code>
# Expected: This account is currently not available

Item 4 – Set Password Policy

Enforce complexity, length, expiration, and history.

Steps (CentOS/RHEL) :

# Install policy tools
sudo yum install -y libpwquality cracklib

# Edit /etc/security/pwquality.conf (example settings):
# minlen = 12
# dcredit = -1   # at least one digit
# ucredit = -1   # at least one uppercase
# lcredit = -1   # at least one lowercase
# ocredit = -1   # at least one special character
# difok = 3      # new password differs by at least 3 chars
# maxrepeat = 2

# Configure expiration in /etc/login.defs
# PASS_MAX_DAYS   90
# PASS_MIN_DAYS    7
# PASS_WARN_AGE    7

# Apply to existing user
sudo chage -M 90 admin
sudo chage -m 7 admin
sudo chage -W 7 admin

Steps (Ubuntu) :

# Install pwquality
sudo apt-get install -y libpwquality
# Same /etc/security/pwquality.conf edits as above
# Edit /etc/login.defs for expiration values
sudo vi /etc/login.defs

Verification :

# Try setting a weak password (should be rejected)
sudo passwd admin
# View current policy
sudo chage -l admin

Item 5 – Configure sudo Logging

Ensure sudo actions are recorded for audit.

Steps :

# Verify default logging (CentOS: /var/log/secure, Ubuntu: /var/log/auth.log)
# To create a dedicated sudo log, edit /etc/sudoers:
Defaults logfile=/var/log/sudo.log
# Ensure rsyslog or journald is running
sudo systemctl status rsyslog
sudo systemctl status systemd-journald

Verification :

# View recent sudo entries
sudo tail -20 /var/log/secure | grep sudo   # CentOS
sudo tail -20 /var/log/auth.log | grep sudo   # Ubuntu
# Execute a sudo command and confirm it appears

Items 6‑10: Firewall and Network Configuration

Item 6 – Configure iptables/firewalld (CentOS) or ufw (Ubuntu)

Firewalls provide the first line of defense inside the host.

CentOS firewalld :

# Start and enable firewalld
sudo systemctl start firewalld
sudo systemctl enable firewalld

# Add SSH port (ensure you can still connect)
sudo firewall-cmd --zone=public --add-port=22/tcp --permanent

# Optionally restrict SSH to specific IP ranges
# sudo firewall-cmd --zone=public --add-source=192.168.1.0/24 --permanent
# sudo firewall-cmd --zone=public --remove-port=22/tcp --permanent

# Allow essential services
sudo firewall-cmd --zone=public --add-service=ssh --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --zone=public --add-service=http --permanent

# Drop all other inbound traffic
sudo firewall-cmd --zone=public --add-rich-rule='rule protocol value=icmp drop' --permanent

# Reload and verify
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

Ubuntu ufw :

# Enable ufw
sudo ufw enable
# Default deny inbound, allow outbound
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (adjust port if changed later)
sudo ufw allow ssh
# Allow specific IP range for SSH
sudo ufw allow from 192.168.1.0/24 to any port 22
# Allow HTTP/HTTPS
sudo ufw allow http
sudo ufw allow https
# Verify
sudo ufw status numbered

Verification :

# Check firewall status
sudo firewall-cmd --state   # CentOS
sudo ufw status verbose   # Ubuntu
# Test from another host
nmap -sT -p 22,80,443 <code>SERVER_IP</code>

⚠️ Risk : Ensure you have an alternative console (ILO/VNC) before applying rules; confirm SSH port is open; if using fixed IPs, add them before blocking the default port.

Item 7 – Disable Unnecessary Services and Ports

Each running service expands the attack surface.

# List running services
sudo systemctl list-units --type=service --state=running
# List listening ports
sudo ss -tunapl | grep LISTEN
# Identify and stop unneeded services (examples)
sudo systemctl stop telnet.socket && sudo systemctl disable telnet.socket
sudo systemctl stop rsh.socket && sudo systemctl disable rsh.socket
sudo systemctl stop cups && sudo systemctl disable cups
sudo systemctl stop postfix && sudo systemctl disable postfix
sudo systemctl stop dovecot && sudo systemctl disable dovecot
# Verify no unwanted ports remain
sudo ss -tunapl | grep LISTEN
sudo systemctl is-enabled <code>SERVICE_NAME</code>   # should show "disabled"

Item 8 – Disable ICMP Echo Replies (Optional but Recommended)

# Using firewalld (CentOS)
sudo firewall-cmd --permanent --add-icmp-block=echo-reply
sudo firewall-cmd --permanent --add-icmp-block=echo-request
sudo firewall-cmd --reload

# Using ufw (Ubuntu)
sudo ufw insert 1 deny proto icmp from any to any icmp type echo-request

# Kernel method (both)
sudo vi /etc/sysctl.conf
# Add:
net.ipv4.icmp_echo_ignore_all = 1
# Apply
sudo sysctl -p

Verification :

# From another host
ping <code>SERVER_IP</code>
# Should time out
# Local ping still works
ping 127.0.0.1

⚠️ Risk : Monitoring tools that rely on ICMP (e.g., Zabbix, Nagios) will need alternative checks; traceroute may be affected.

Item 9 – Harden Kernel Network Parameters

# Edit /etc/sysctl.conf
sudo vi /etc/sysctl.conf
# Add the following (excerpt):
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_echo_ignore_all = 1
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# Apply
sudo sysctl -p
# Verify a sample
sudo sysctl net.ipv4.tcp_syncookies   # should output = 1

Item 10 – Limit File Descriptors and Process Counts (ulimit)

# View current limits
ulimit -a
# Edit /etc/security/limits.conf
sudo vi /etc/security/limits.conf
# Add (example):
*               soft    nofile          65535
*               hard    nofile          65535
*               soft    nproc            65535
*               hard    nproc            65535
root            soft    nofile          65535
root            hard    nofile          65535
# Ensure PAM loads limits (CentOS: /etc/pam.d/system-auth, Ubuntu: /etc/pam.d/common-session)
sudo vi /etc/pam.d/system-auth   # add: session required pam_limits.so
# System‑wide max files
sudo vi /etc/sysctl.conf
fs.file-max = 655360
sudo sysctl -p
# Verify after re‑login
ulimit -n   # should show 65535

Items 11‑15: System Security Configuration

Item 11 – Update System Packages

# CentOS/RHEL
sudo yum update -y   # or sudo dnf update -y
# Ubuntu
sudo apt update && sudo apt upgrade -y
# Reboot if kernel updated
sudo reboot
# Verify update history
rpm -qa --last | head -10   # CentOS
dpkg -l | tail -10          # Ubuntu

Item 12 – Install and Configure fail2ban

# Install
sudo yum install -y epel-release && sudo yum install -y fail2ban   # CentOS
sudo apt-get install -y fail2ban                                 # Ubuntu
# Configure /etc/fail2ban/jail.local (example)
[DEFAULT]
# bantime = 3600
# findtime = 600
# maxretry = 5
# banaction = iptables-multiport

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/secure   # CentOS
# logpath = /var/log/auth.log   # Ubuntu
maxretry = 3
# Enable and start
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Verification :

sudo fail2ban-client status
sudo fail2ban-client status sshd
# Simulate failed logins from another host and confirm IP is banned

Item 13 – Configure auditd Auditing

# Install
sudo yum install -y audit          # CentOS
sudo apt-get install -y auditd    # Ubuntu
# Enable and start
sudo systemctl enable auditd
sudo systemctl start auditd
# Create rules in /etc/audit/rules.d/audit.rules, e.g.:
-w /usr/bin/rm -p x -k delete
-w /usr/bin/mv -p x -k move
-w /usr/bin/chmod -p x -k perm_chmod
-w /usr/bin/chown -p x -k perm_chown
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/ssh/sshd_config -k sshd_config
# Load rules
sudo auditctl -R /etc/audit/rules.d/audit.rules
# Verify
sudo ausearch -k delete | head -20
sudo ausearch -i | tail -20
sudo systemctl status auditd

Item 14 – Disable Ctrl+Alt+Del Reboot

# Mask the target (both CentOS and Ubuntu)
sudo systemctl mask ctrl-alt-del.target
# Verify
sudo systemctl status ctrl-alt-del.target   # should show "masked"

Item 15 – Set GRUB Password

# Generate a PBKDF2 hash
grub2-mkpasswd-pbkdf2
# Edit /etc/grub.d/00_header (or /etc/grub.d/40_custom on Ubuntu)
cat <<EOF >> /etc/grub.d/00_header
set superusers="admin"
password_pbkdf2 admin <code>GENERATED_HASH</code>
EOF
# Regenerate GRUB config
# CentOS
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# UEFI
sudo grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
# Ubuntu
sudo update-grub
# Verify on next reboot by attempting to edit a menu entry

Items 16‑20: SSH and Remote Access Hardening

Item 16 – Change Default SSH Port

# Edit /etc/ssh/sshd_config
Port 2222
# Open the new port in the firewall
sudo firewall-cmd --permanent --add-port=2222/tcp   # CentOS
sudo ufw allow 2222/tcp                         # Ubuntu
sudo firewall-cmd --reload   # or sudo ufw reload
# Restart SSH
sudo systemctl restart sshd
# Verify
ssh -p 2222 admin@<code>SERVER_IP</code>
# Ensure port 22 is no longer listening (optional)

⚠️ Risk : Record the new port; forgetting it will leave you unable to connect.

Item 17 – Enforce Empty‑Password and Host‑Key Checks

# Edit /etc/ssh/sshd_config
PermitEmptyPasswords no
StrictHostKeyChecking ask
IgnoreRhosts yes
HostbasedAuthentication no

Item 18 – Restrict Allowed SSH Users and Groups

# Edit /etc/ssh/sshd_config
AllowUsers admin deploy
AllowGroups sudo admin
PermitRootLogin no

Item 19 – Install and Configure AIDE File‑Integrity Monitoring

# Install
sudo yum install -y aide          # CentOS
sudo apt-get install -y aide      # Ubuntu
# Initialize database
sudo aide --init
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
# Schedule regular checks (example cron entry)
0 3 * * * /usr/sbin/aide --check | /usr/bin/mail -s "AIDE report" [email protected]
# Manual check
sudo aide --check

Item 20 – Centralised Log Collection and Alerting (rsyslog)

# Edit /etc/rsyslog.conf
# Add remote server (UDP example)
*.* @log-server-ip:514
# Or TCP (more reliable)
*.* @@log-server-ip:514
# Enable and restart rsyslog
sudo systemctl enable rsyslog
sudo systemctl restart rsyslog
# Configure logrotate (excerpt)
/var/log/secure {
    daily
    rotate 30
    missingok
    notifempty
    compress
    sharedscripts
    postrotate
        /bin/kill -HUP `cat /var/run/syslogd.pid 2>/dev/null` || true
    endscript
}
# Verify remote receipt
logger "Test log from $(hostname)"
# Verify rotation configuration
sudo logrotate -d /etc/logrotate.conf | head -30

Post‑Hardening Verification Script

#!/bin/bash
# security_check.sh – self‑test after hardening

echo "===== Server Security Hardening Self‑Check ====="

echo "[1] Check if root login is disabled..."
grep "PermitRootLogin" /etc/ssh/sshd_config

echo "[2] Check SSH public‑key authentication settings..."
grep "PubkeyAuthentication" /etc/ssh/sshd_config
grep "PasswordAuthentication" /etc/ssh/sshd_config

echo "[3] Check firewall status..."
systemctl status firewalld 2>/dev/null | grep "Active" || ufw status 2>/dev/null | head -3

echo "[4] Check fail2ban status..."
systemctl status fail2ban 2>/dev/null | grep "Active"

echo "[5] Check unnecessary services..."
systemctl list-units --type=service --state=running | grep -E "telnet|rsh|vsftpd|cups|postfix"

echo "[6] Check file descriptor limit..."
ulimit -n

echo "[7] Check kernel parameters..."
sysctl net.ipv4.tcp_syncookies
sysctl net.ipv4.icmp_echo_ignore_all

echo "[8] Check auditd status..."
systemctl status auditd 2>/dev/null | grep "Active"

echo "[9] Check AIDE..."
aide --check 2>/dev/null | head -5

echo "[10] Check password policy..."
grep "minlen" /etc/security/pwquality.conf
grep "PASS_MAX_DAYS" /etc/login.defs

Conclusion

Server hardening follows the principle of defense‑in‑depth: each layer—account policies, SSH configuration, firewall rules, service minimisation, kernel tweaks, logging, and regular verification—adds protection. The 20 items are prioritised as high, medium, and low, and should be revisited regularly (quarterly account reviews, post‑update checks, audit log reviews, and updates for newly disclosed vulnerabilities).

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

firewalllinuxServer HardeningSSHPassword policyAuditdFail2banAIDE
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.