Overview
Use this checklist before deploying any server to production. Each item includes a brief rationale and verification command where applicable.
This checklist aligns with established hardening standards. Do not treat it as a substitute for the source — verify against the published version your organization is audited against.
| Standard | Scope |
|---|---|
| CIS Benchmarks (Linux distributions, Windows Server, hypervisors) | Section-by-section configuration baseline; the de-facto starting point |
| DISA STIGs | Required for US federal / DoD environments; stricter than CIS in many areas |
| NIST SP 800-123 | Server-security guidelines (older but foundational) |
| NIST SP 800-53 Rev 5 | Control families AC, AU, CM, IA, SC apply directly |
| CIS Controls v8.1 (2024) | Maps each item below to an enterprise control |
| dev-sec/linux-baseline + ansible-hardening | Open-source automation that codifies most CIS Linux controls |
| OpenSCAP / CIS-CAT Pro | Automated assessment against CIS / STIG profiles |
Configuration as code, not click-ops. Every item below should ultimately be enforced by an idempotent automation tool (Ansible, Chef, Puppet, image baking) rather than a one-time manual run. A hardened server that drifts is worse than an unhardened one because monitoring assumes the controls are in place.
Operating System Configuration
-
Update all packages — Apply latest security patches
# Ubuntu/Debian sudo apt update && sudo apt upgrade -y # RHEL/CentOS sudo dnf update -y -
Remove unnecessary packages — Reduce attack surface
sudo apt autoremove -y dpkg --list | grep -i "telnet\|ftp\|rsh" -
Disable unused services — Only run what's needed
systemctl list-unit-files --type=service --state=enabled sudo systemctl disable <service-name> -
Configure automatic security updates — Unattended upgrades for critical patches
sudo apt install unattended-upgrades sudo dpkg-reconfigure -plow unattended-upgrades -
Set hostname and timezone — Consistent identification and log correlation
hostnamectl set-hostname prod-web-01 timedatectl set-timezone UTC -
Configure NTP synchronization — Accurate timestamps for logs and certificates
timedatectl status | grep "NTP synchronized"
User Access Controls
-
Disable root SSH login — Force use of named accounts
# /etc/ssh/sshd_config PermitRootLogin no -
Enforce SSH key-only authentication — No password login over SSH
PasswordAuthentication no PubkeyAuthentication yes -
Prefer SSH certificate authentication for fleets >10 hosts — Certificates with short TTLs (e.g., 4-8 hours) and a central CA eliminate the
authorized_keyssprawl that creates orphaned access. HashiCorp Vault, Smallstep, or Teleport can issue short-lived host + user certs.# /etc/ssh/sshd_config — trust user certs signed by your CA TrustedUserCAKeys /etc/ssh/ca_user_key.pub # Issue a short-lived user cert (Smallstep example) step ssh certificate alice@example.com alice_id_ecdsa --not-after 8h -
Install fail2ban for brute-force ban automation — pam_faillock locks an account; fail2ban bans the source IP at the firewall. Run both.
sudo apt install fail2ban # /etc/fail2ban/jail.local [sshd] enabled = true maxretry = 5 bantime = 3600 -
Configure SSH on non-standard port — Reduce automated scanning noise
Port 2222 # or your chosen port -
Set SSH idle timeout — Auto-disconnect inactive sessions
ClientAliveInterval 300 ClientAliveCountMax 2 -
Create individual admin accounts — No shared credentials
useradd -m -G sudo -s /bin/bash admin-dylan -
Enforce password complexity — Minimum length and character requirements
# /etc/security/pwquality.conf minlen = 14 minclass = 3 -
Configure sudo logging — Track privileged operations
# /etc/sudoers.d/logging Defaults logfile="/var/log/sudo.log" -
Set account lockout policy — Brute force protection
# /etc/pam.d/common-auth auth required pam_faillock.so deny=5 unlock_time=900
Network Security
-
Configure firewall (UFW/iptables) — Default deny, explicit allow
sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 2222/tcp # SSH sudo ufw allow 443/tcp # HTTPS sudo ufw enable -
Disable unused network protocols — Remove IPv6 if not needed
# /etc/sysctl.conf net.ipv6.conf.all.disable_ipv6 = 1 -
Enable SYN flood protection — Kernel-level DDoS mitigation
net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 2048 -
Disable ICMP redirects — Prevent route manipulation
net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 -
Enable IP spoofing protection — Reverse path filtering
net.ipv4.conf.all.rp_filter = 1 -
Disable IP forwarding — Unless acting as a router
net.ipv4.ip_forward = 0 -
Apply kernel hardening sysctls — Restrict information leaks and unprivileged kernel features
# /etc/sysctl.d/99-hardening.conf kernel.kptr_restrict = 2 # hide kernel pointers in /proc kernel.dmesg_restrict = 1 # only root can read dmesg kernel.unprivileged_bpf_disabled = 1 net.core.bpf_jit_harden = 2 kernel.yama.ptrace_scope = 1 # restrict ptrace to parent processes fs.protected_hardlinks = 1 fs.protected_symlinks = 1
File System Security
-
Set proper file permissions — Restrict sensitive files
chmod 600 /etc/shadow chmod 644 /etc/passwd chmod 700 /root -
Configure /tmp with noexec — Prevent execution from temp directories
# /etc/fstab tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev 0 0 -
Enable file integrity monitoring — Detect unauthorized changes
sudo apt install aide sudo aideinit -
Set sticky bit on world-writable dirs — Prevent file deletion by non-owners
chmod +t /tmp /var/tmp -
Disable core dumps — Prevent memory exposure
# /etc/security/limits.conf * hard core 0 -
Encrypt the root volume — Servers leave premises (decommission, RMA, theft) with their disks; encryption-at-rest prevents data exposure when they do.
# Linux: LUKS on a fresh install cryptsetup luksFormat /dev/sdb1 cryptsetup luksOpen /dev/sdb1 cryptdata mkfs.ext4 /dev/mapper/cryptdata # Verify status cryptsetup status cryptdata# Windows Server: BitLocker on the OS volume Enable-BitLocker -MountPoint "C:" -EncryptionMethod XtsAes256 -RecoveryPasswordProtector manage-bde -status C: -
Separate critical mountpoints —
/var,/var/log,/home, and/tmpshould be on distinct partitions/LVs so a runaway process or attacker cannot fill the root volume. Configurenodev,nosuid,noexecwhere the workload allows.
Logging & Monitoring
-
Configure centralized logging — Forward to SIEM/log aggregator
# /etc/rsyslog.d/50-remote.conf *.* @@siem.internal:514 -
Enable audit logging — Track system calls and file access
sudo apt install auditd sudo systemctl enable auditd -
Monitor authentication logs — Watch for brute force attempts
tail -f /var/log/auth.log -
Set log rotation — Prevent disk exhaustion
# /etc/logrotate.d/syslog /var/log/syslog { weekly, rotate 12, compress } -
Configure disk space alerts — Early warning for storage issues
df -h | awk '$5 > 80 {print $0}'
Windows Server-Specific Controls
The Linux-flavoured items above translate to PowerShell + Group Policy on Windows Server. The list below covers the controls that are uniquely Windows-shaped.
-
Prefer Server Core over Desktop Experience — Server Core has roughly 60% less attack surface and reduced patching cadence. Use Desktop Experience only for roles that genuinely require it.
-
Enable LSA Protection (RunAsPPL) — Protects LSASS from credential-theft tooling like Mimikatz. Required for Credential Guard.
# HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL = 1 (or 2 for UEFI lock) Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "RunAsPPL" -Value 1 -
Enable Credential Guard (where supported) — Virtualization-based isolation of derived credentials; mandatory on domain controllers.
# Verify VBS + Credential Guard status Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard | Select-Object SecurityServicesConfigured, SecurityServicesRunning -
Deploy Windows LAPS for local administrator passwords — The replacement for legacy LAPS, included natively in Windows 11/Server 2019+. Rotates the local admin password and escrows it in AD or Entra ID.
# Configure via Intune or GPO; verify policy on the host Get-LapsAADPassword -DeviceIds (Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=corp,DC=local").ObjectGUID -
Apply AppLocker or Windows Defender Application Control (WDAC) — Servers run a known, narrow set of binaries; deny-by-default execution catches unauthorized payloads.
-
Configure Just Enough Administration (JEA) for delegated tasks — Helpdesk and ops roles get role-capability files instead of unconstrained PowerShell.
-
Enable PowerShell logging — Module logging, script-block logging (Event ID 4104), and transcription. The single biggest detection blind spot on Windows servers.
# Via GPO: Computer Configuration > Policies > Administrative Templates > Windows Components > Windows PowerShell # Verify locally Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" | Select-Object EnableScriptBlockLogging -
Configure Windows Defender Firewall with default deny inbound — Three profiles (Domain, Private, Public); document exceptions in change management.
Set-NetFirewallProfile -Profile Domain,Public,Private -DefaultInboundAction Block -DefaultOutboundAction Allow -Enabled True -
Disable SMBv1, enforce SMB signing, require SMB encryption for sensitive shares
Set-SmbServerConfiguration -EnableSMB1Protocol $false -RequireSecuritySignature $true -EncryptData $true -Force -
Restrict NTLM and prefer Kerberos — Audit then block NTLM in domain environments. Domain controllers should be set to refuse NTLM where possible.
-
Set audit policy via
auditpol— Match the Microsoft Security Compliance Toolkit baseline; verify logon, privilege use, object access, and process tracking events flow to your SIEM.auditpol /set /subcategory:"Logon" /success:enable /failure:enable auditpol /set /subcategory:"Special Logon" /success:enable /failure:enable auditpol /set /subcategory:"Sensitive Privilege Use" /success:enable /failure:enable -
Set BIOS/UEFI password and verify Secure Boot — Hardware-level controls that block boot-order changes and unsigned bootloaders.
-
Disable LLMNR, NetBIOS-over-TCP, and WPAD on servers — Same name-resolution-poisoning surface as endpoints.
-
Decide on FIPS mode — Required for FedRAMP / DoD / some healthcare workloads. Sets cryptographic module enforcement.
# Enable FIPS-compliant algorithms (per Microsoft policy) Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy" -Name "Enabled" -Value 1
Application Security
-
Run services as non-root — Principle of least privilege
# systemd service file User=appuser Group=appgroup -
Enable AppArmor/SELinux — Mandatory access controls
sudo aa-status # AppArmor sestatus # SELinux -
Configure TLS 1.2+ only — Disable legacy protocols
# nginx ssl_protocols TLSv1.2 TLSv1.3; -
Set security headers — CSP, HSTS, X-Frame-Options
add_header Strict-Transport-Security "max-age=63072000" always; add_header X-Content-Type-Options "nosniff" always;
Automation & Continuous Compliance
Manual hardening drifts. Wrap every item above in idempotent automation and re-evaluate continuously.
-
Encode the baseline as code — Use
dev-sec/ansible-collection-hardening, the OpenSCAPscap-security-guideprofiles, or the CIS-published Build Kits. Re-run on every host every 24 hours; fail loud on drift.# OpenSCAP: assess against CIS profile oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_cis \ --results scan-results.xml \ /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml -
Run CIS-CAT Pro or OpenSCAP weekly — Automated configuration assessment against the CIS benchmark. Generate a delta report against last week's run; investigate every regression.
-
Bake hardened images, do not patch live servers — The "golden AMI / golden image" pattern: rebuild the image with all hardening + latest patches, deploy via blue-green. Live in-place patching produces snowflake servers.
-
Assert hardening in CI — InSpec, Goss, or Inspec-Win profiles validate that a built image meets the baseline before promotion. CI fails the build if any control regresses.
Verification
-
Run vulnerability scan — Automated assessment
sudo lynis audit system -
Test firewall rules — Verify only expected ports open
nmap -sS -p- localhost -
Verify SSH configuration — Check hardening applied
sshd -T | grep -E "permitrootlogin|passwordauthentication|port" -
Review running processes — Confirm no unauthorized services
ps aux | grep -v "\[" | sort -nrk 3 -
Document changes — Record all modifications for audit trail
Quick Reference
| Area | Key Actions | Priority |
|---|---|---|
| OS Updates | Patch + auto-update | Critical |
| SSH | Key-only (or short-lived certs), no root, non-standard port | Critical |
| Firewall | Default deny, explicit allow | Critical |
| Users | Individual accounts, sudo logging, fail2ban | High |
| Encryption at rest | LUKS / BitLocker on every server | Critical |
| Logging | Centralized + audit + PowerShell logging on Windows | High |
| TLS | 1.2+ only, strong ciphers | High |
| File System | Permissions, integrity monitoring, separated mountpoints | Medium |
| Windows-specific | LSA Protection, Credential Guard, LAPS, JEA, AppLocker/WDAC | Critical |
| Automation | Hardening as code, weekly CIS-CAT/OpenSCAP scans, immutable images | High |