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.
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 loginSteps (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 sshdVerification :
# Test sudo with the new account
sudo -l
# Attempt root login (should be denied)
ssh root@<code>SERVER_IP</code>
# Expected: Permission deniedItem 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 sshdVerification :
# 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 availableItem 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 adminSteps (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.defsVerification :
# Try setting a weak password (should be rejected)
sudo passwd admin
# View current policy
sudo chage -l adminItem 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-journaldVerification :
# 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 appearsItems 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-allUbuntu 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 numberedVerification :
# 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 -pVerification :
# 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 = 1Item 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 65535Items 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 # UbuntuItem 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 fail2banVerification :
sudo fail2ban-client status
sudo fail2ban-client status sshd
# Simulate failed logins from another host and confirm IP is bannedItem 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 auditdItem 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 entryItems 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 noItem 18 – Restrict Allowed SSH Users and Groups
# Edit /etc/ssh/sshd_config
AllowUsers admin deploy
AllowGroups sudo admin
PermitRootLogin noItem 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 --checkItem 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 -30Post‑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.defsConclusion
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).
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
