SSH Hardening Script
Automated script to harden your Linux server's SSH configuration. Applies security best practices in a single command.
Description
This script automates OpenSSH configuration hardening on a Linux server. It applies security best practices recommended by the CIS Benchmark and ANSSI.
Features
- Automatic backup of current configuration
- Root login disabled
- Key-only authentication enforced
- Strong cipher algorithm configuration
- Connection attempt limiting
- Enhanced logging
- Configuration validation before applying
Usage
# Make executable
chmod +x ssh_hardening.sh
# Run as root
sudo ./ssh_hardening.sh
# Dry-run mode (shows changes without applying)
sudo ./ssh_hardening.sh --dry-run
# Restore previous configuration
sudo ./ssh_hardening.sh --restore
Source Code
#!/usr/bin/env bash
#
# ssh_hardening.sh — Automated SSH configuration hardening
# Usage: sudo ./ssh_hardening.sh [--dry-run|--restore]
#
# ⚠️ IMPORTANT: Make sure you have console access before running this script
# in case the SSH configuration locks you out.
#
set -euo pipefail
# ═══════════════════════════════════════
# Configuration
# ═══════════════════════════════════════
SSHD_CONFIG="/etc/ssh/sshd_config"
BACKUP_DIR="/root/.ssh_backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/sshd_config.backup.${TIMESTAMP}"
SSH_PORT="${SSH_PORT:-2222}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
NC='\033[0m'
# ═══════════════════════════════════════
# Utility functions
# ═══════════════════════════════════════
log_info() { echo -e "${CYAN}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
check_root() {
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root"
exit 1
fi
}
# ═══════════════════════════════════════
# Backup
# ═══════════════════════════════════════
backup_config() {
mkdir -p "$BACKUP_DIR"
cp "$SSHD_CONFIG" "$BACKUP_FILE"
log_ok "Configuration backed up: ${BACKUP_FILE}"
}
# ═══════════════════════════════════════
# Restore
# ═══════════════════════════════════════
restore_config() {
local latest
latest=$(ls -t "${BACKUP_DIR}"/sshd_config.backup.* 2>/dev/null | head -1)
if [[ -z "$latest" ]]; then
log_error "No backup found in ${BACKUP_DIR}"
exit 1
fi
cp "$latest" "$SSHD_CONFIG"
log_ok "Configuration restored from: ${latest}"
if sshd -t; then
systemctl restart sshd
log_ok "SSH service restarted with restored configuration"
else
log_error "The restored configuration is invalid!"
exit 1
fi
}
# ═══════════════════════════════════════
# Hardened SSH configuration
# ═══════════════════════════════════════
generate_hardened_config() {
cat << 'SSHEOF'
# ═══════════════════════════════════════════════════════
# Hardened SSH Configuration — Generated by ssh_hardening.sh
# Date: __TIMESTAMP__
# Reference: CIS Benchmark + ANSSI
# ═══════════════════════════════════════════════════════
# ── Network Settings ───────────────────────────────────
Port __SSH_PORT__
AddressFamily inet
ListenAddress 0.0.0.0
Protocol 2
# ── Authentication ─────────────────────────────────────
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
AuthenticationMethods publickey
MaxAuthTries 3
MaxSessions 3
LoginGraceTime 30
# ── Strong Cipher Algorithms Only ──────────────────────
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers [email protected],[email protected],[email protected],aes256-ctr
MACs [email protected],[email protected],hmac-sha2-512,hmac-sha2-256
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
# ── Logging ────────────────────────────────────────────
SyslogFacility AUTH
LogLevel VERBOSE
# ── Restrictions ───────────────────────────────────────
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
GatewayPorts no
PermitUserEnvironment no
DisableForwarding yes
# ── Timeout and Keepalive ──────────────────────────────
ClientAliveInterval 300
ClientAliveCountMax 2
TCPKeepAlive no
# ── Banner ─────────────────────────────────────────────
Banner /etc/ssh/banner
PrintMotd no
PrintLastLog yes
# ── SFTP ───────────────────────────────────────────────
Subsystem sftp /usr/lib/openssh/sftp-server -f AUTHPRIV -l INFO
SSHEOF
}
# ═══════════════════════════════════════
# SSH Banner
# ═══════════════════════════════════════
create_banner() {
cat > /etc/ssh/banner << 'BANNEREOF'
╔══════════════════════════════════════════════════════════╗
║ ⚠️ WARNING ⚠️ ║
║ ║
║ This system is restricted to authorized users only. ║
║ All activity is monitored and logged. ║
║ Unauthorized access will be prosecuted. ║
║ ║
╚══════════════════════════════════════════════════════════╝
BANNEREOF
log_ok "SSH banner created"
}
# ═══════════════════════════════════════
# Apply
# ═══════════════════════════════════════
apply_config() {
local config
config=$(generate_hardened_config)
config="${config//__TIMESTAMP__/$TIMESTAMP}"
config="${config//__SSH_PORT__/$SSH_PORT}"
echo "$config" > "$SSHD_CONFIG"
log_ok "Hardened SSH configuration applied"
# Create the banner
create_banner
# Validate syntax
if sshd -t; then
log_ok "Syntax check: OK"
else
log_error "Configuration syntax error!"
log_warn "Restoring backup..."
cp "$BACKUP_FILE" "$SSHD_CONFIG"
exit 1
fi
# Restart SSH
systemctl restart sshd
log_ok "SSH service restarted"
}
# ═══════════════════════════════════════
# Report
# ═══════════════════════════════════════
print_report() {
echo ""
echo -e "${CYAN}╔══════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ 🔒 SSH Hardening — Report ║${NC}"
echo -e "${CYAN}╠══════════════════════════════════════════════╣${NC}"
echo -e "${CYAN}║${NC} SSH Port : ${GREEN}${SSH_PORT}${NC}"
echo -e "${CYAN}║${NC} Root login : ${RED}Disabled${NC}"
echo -e "${CYAN}║${NC} Password auth : ${RED}Disabled${NC}"
echo -e "${CYAN}║${NC} Key auth : ${GREEN}Enabled${NC}"
echo -e "${CYAN}║${NC} X11 Forwarding : ${RED}Disabled${NC}"
echo -e "${CYAN}║${NC} Max attempts : ${GREEN}3${NC}"
echo -e "${CYAN}║${NC} Timeout : ${GREEN}300s${NC}"
echo -e "${CYAN}║${NC} Banner : ${GREEN}Enabled${NC}"
echo -e "${CYAN}║${NC} Backup : ${GREEN}${BACKUP_FILE}${NC}"
echo -e "${CYAN}╠══════════════════════════════════════════════╣${NC}"
echo -e "${CYAN}║${NC} ${YELLOW}⚠️ Make sure you have an SSH key${NC}"
echo -e "${CYAN}║${NC} ${YELLOW} configured before closing this session!${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════╝${NC}"
}
# ═══════════════════════════════════════
# Main
# ═══════════════════════════════════════
main() {
echo -e "${CYAN}╔══════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ 🔒 SSH Hardening Script v1.0 ║${NC}"
echo -e "${CYAN}╚══════════════════════════════════════════════╝${NC}"
echo ""
check_root
case "${1:-}" in
--restore)
restore_config
exit 0
;;
--dry-run)
log_info "Dry-run mode — displaying configuration:"
echo ""
generate_hardened_config | sed "s/__TIMESTAMP__/$TIMESTAMP/g" | sed "s/__SSH_PORT__/$SSH_PORT/g"
exit 0
;;
esac
log_info "Backing up current configuration..."
backup_config
log_info "Applying hardened configuration..."
apply_config
print_report
}
main "$@"
Customizable Parameters
You can change the SSH port via an environment variable:
# Use a custom port
SSH_PORT=2222 sudo ./ssh_hardening.sh
# Or modify it directly in the script
After Running
- Verify that you can still connect via SSH
- Test with
ssh -v user@server -p 2222 - If something goes wrong, use
--restoreor access via console
⚠️ Warning
This script significantly modifies the SSH configuration. Make sure you have console access (KVM, IPMI, or physical access) before running it, in case the new configuration locks you out of SSH.