Basic server security init script
Context
This note captures a single script used for basic server security setup.
It is opinionated and interactive: it expects root, creates a sudo user, prompts for an SSH public key, hardens SSH, enables UFW, configures Fail2ban, enables unattended upgrades, and appends a small sysctl hardening block.
Goal
- create a usable first-pass server hardening script
- disable password auth and root login over SSH
- enable a minimal firewall and SSH brute-force protection
- turn on automatic security updates
Environment
- Debian or Ubuntu-style system using
apt openssh-server,ufw,fail2ban,unattended-upgrades- interactive shell with root privileges
- script expects a target username and a pasted SSH public key
The Final State
If the script runs successfully, it should leave the system in this state:
- SSH moved to the configured port with password auth disabled
- root login disabled
- target user created with sudo access
- SSH public key installed
- UFW enabled and allowing the configured SSH port
- Fail2ban enabled for SSH
- unattended upgrades enabled
- extra sysctl hardening values appended and loaded
Implementation
#!/bin/bash
set -e
# --- Color Definitions ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# --- Logging Functions ---
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
}
error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}" >&2
exit 1
}
# --- Sanity Checks ---
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root."
fi
# --- Configuration ---
SSH_PORT=22
USERNAME="admin"
if [[ -z "$USERNAME" ]]; then
error "Username is a required variable."
fi
# --- Script Start ---
log "Starting server security setup..."
# --- Package Management ---
log "Updating system packages..."
apt-get update -y && apt-get upgrade -y
log "Installing security packages..."
apt-get install -y openssh-server unattended-upgrades fail2ban ufw curl vim sudo
# --- SSH Configuration ---
log "Configuring SSH security settings..."
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
cat > /etc/ssh/sshd_config << EOF
# SSH Security Configuration
Port $SSH_PORT
Protocol 2
# Authentication settings
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
UsePAM yes
# Connection settings
MaxAuthTries 2
MaxSessions 2
LoginGraceTime 120
# Security settings
X11Forwarding no
PrintMotd no
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
EOF
log "SSH configured on port $SSH_PORT. Password authentication and root login disabled."
# --- User and SSH Key Setup ---
log "Setting up user and SSH key authentication..."
if ! id "$USERNAME" &>/dev/null; then
useradd -m -s /bin/bash "$USERNAME"
usermod -aG sudo "$USERNAME"
log "Created user: $USERNAME and added to sudo group."
fi
USER_HOME="/home/$USERNAME"
mkdir -p "$USER_HOME/.ssh"
chmod 700 "$USER_HOME/.ssh"
# --- Interactive SSH Key Input ---
echo ""
warn "Please paste the entire SSH public key below and press Enter:"
read -r SSH_PUB_KEY
echo ""
if [[ -z "$SSH_PUB_KEY" ]]; then
error "No SSH public key provided. Exiting."
fi
echo "$SSH_PUB_KEY" > "$USER_HOME/.ssh/authorized_keys"
if [[ ! -s "$USER_HOME/.ssh/authorized_keys" ]]; then
error "Failed to write SSH key."
fi
chmod 600 "$USER_HOME/.ssh/authorized_keys"
chown -R "$USERNAME:$USERNAME" "$USER_HOME/.ssh"
log "SSH public key configured for user: $USERNAME"
# --- Firewall Configuration (UFW) ---
log "Configuring UFW firewall..."
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow $SSH_PORT/tcp
ufw --force enable
systemctl enable ufw.service
log "UFW configured: Port $SSH_PORT/tcp allowed."
# --- Intrusion Prevention (Fail2ban) ---
log "Configuring Fail2ban..."
cat > /etc/fail2ban/jail.local << EOF
[DEFAULT]
bantime = 3600
bantime.increment = true
findtime = 600
maxretry = 3
ignoreip = 127.0.0.1/8
[sshd]
enabled = true
port = $SSH_PORT
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 36000
findtime = 600
EOF
systemctl enable fail2ban
systemctl start fail2ban
log "Fail2ban configured for SSH protection."
# --- Automatic Security Updates ---
log "Configuring automatic security updates..."
echo "unattended-upgrades unattended-upgrades/enable_auto_updates boolean true" | debconf-set-selections
dpkg-reconfigure -f noninteractive unattended-upgrades
cat > /etc/apt/apt.conf.d/20auto-upgrades << EOF
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
EOF
log "Automatic security updates configured."
# --- Kernel Hardening (sysctl) ---
log "Applying kernel hardening settings..."
cat >> /etc/sysctl.conf << EOF
# Additional security settings
net.ipv4.conf.default.rp_filter=1
net.ipv4.conf.all.rp_filter=1
net.ipv4.tcp_syncookies=1
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv4.conf.all.secure_redirects=0
net.ipv4.conf.default.secure_redirects=0
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.icmp_ignore_bogus_error_responses=1
EOF
sysctl -p
# --- Restart Services ---
log "Restarting services to apply changes..."
systemctl restart sshd
systemctl restart fail2ban
systemctl restart ufw
# --- Final Summary ---
log "Basic server setup completed!"
echo ""
echo -e "${YELLOW}--- Summary of Configuration ---${NC}"
echo " - SSH Port: $SSH_PORT"
echo " - User with sudo access: '$USERNAME'"
echo " - SSH key authentication: ENABLED"
echo " - Firewall (UFW) rule: Allow $SSH_PORT/tcp"
echo ""
log "Service Status:"
systemctl is-active --quiet sshd && echo -e " - SSH: ${GREEN}Active${NC}" || echo -e " - SSH: ${RED}Inactive${NC}"
systemctl is-active --quiet fail2ban && echo -e " - Fail2ban: ${GREEN}Active${NC}" || echo -e " - Fail2ban: ${RED}Inactive${NC}"
systemctl is-active --quiet ufw && echo -e " - UFW: ${GREEN}Active${NC}" || echo -e " - UFW: ${RED}Inactive${NC}"
systemctl is-active --quiet unattended-upgrades && echo -e " - Unattended-upgrades: ${GREEN}Active${NC}" || echo -e " - Unattended-upgrades: ${RED}Inactive${NC}"
echo ""
log "Verifying root accounts:"
awk -F: '($3 == "0") {print " - " $1}' /etc/passwd
echo ""
log "Security setup script completed!"
Verification
Expected proof after running:
- SSH listens on the configured port and rejects password auth
ufw statusshows the chosen SSH port allowedsystemctl status fail2banshows the jail activesystemctl status unattended-upgradesindicates the service is enabled or running as expectedsysctl -ashows the appended hardening values applied
Failure Modes Worth Caring About
- running this remotely and locking yourself out by changing SSH config before validating the key
- blindly overwriting
sshd_configinstead of merging with distro-specific defaults - appending duplicate sysctl settings on repeated runs
- assuming
/var/log/auth.logis the correct Fail2ban log path on every target distro