Skip to content

Instantly share code, notes, and snippets.

@nomo3919
Last active April 19, 2026 00:21
Show Gist options
  • Select an option

  • Save nomo3919/aabe761f8994d4879b8ec21fd234f13b to your computer and use it in GitHub Desktop.

Select an option

Save nomo3919/aabe761f8994d4879b8ec21fd234f13b to your computer and use it in GitHub Desktop.
debian-init-script
#!/bin/bash
# =============================================
# universal debian 13 server initial setup script
# run as root. ssh port change happens last
# =============================================
set -euo pipefail
# --- Root check ---
if [[ $EUID -ne 0 ]]; then
echo "Error: must be run as root" >&2
exit 1
fi
# =============================================
# User Input
# =============================================
echo "=== Configuration ==="
read -rp "Enter new username: " NEW_USER
[[ -z "$NEW_USER" ]] && { echo "Error: username cannot be empty"; exit 1; }
id "$NEW_USER" &>/dev/null && { echo "Error: user '$NEW_USER' already exists"; exit 1; }
while true; do
read -rsp "Enter password for $NEW_USER (min 10 chars): " NEW_PASS; echo
read -rsp "Confirm password: " NEW_PASS2; echo
if [[ "$NEW_PASS" != "$NEW_PASS2" ]]; then
echo "Passwords do not match, try again"
continue
fi
if [[ ${#NEW_PASS} -lt 10 ]]; then
echo "Password too short (min 10 chars)"
continue
fi
break
done
echo "Paste your SSH public key (ssh-ed25519 / ssh-rsa / ecdsa-sha2-*):"
read -rp "> " PUB_KEY
if [[ ! "$PUB_KEY" =~ ^(ssh-ed25519|ssh-rsa|ecdsa-sha2-|sk-ssh-ed25519) ]]; then
echo "Error: invalid SSH public key format"
exit 1
fi
read -rp "Enter new SSH port (default 21994): " SSH_PORT
SSH_PORT=${SSH_PORT:-21994}
if ! [[ "$SSH_PORT" =~ ^[0-9]+$ ]] || (( SSH_PORT < 1024 || SSH_PORT > 65535 )); then
echo "Error: invalid port"; exit 1
fi
read -rp "Whitelist IPs for fail2ban (comma-separated, empty for none): " WHITELIST_RAW
WHITELIST_IPS=""
if [[ -n "$WHITELIST_RAW" ]]; then
WHITELIST_IPS=$(echo "$WHITELIST_RAW" | tr ',' ' ' | tr -s ' ' | sed 's/^ //;s/ $//')
fi
read -rp "Create swap file? (y/N): " CREATE_SWAP
SWAP_SIZE=""
if [[ "$CREATE_SWAP" =~ ^[Yy]$ ]]; then
read -rp "Swap size in GB (default 2): " SWAP_SIZE
SWAP_SIZE=${SWAP_SIZE:-2}
if ! [[ "$SWAP_SIZE" =~ ^[0-9]+$ ]] || (( SWAP_SIZE < 1 )); then
echo "Error: invalid swap size"; exit 1
fi
fi
# =============================================
# System Update & Packages
# =============================================
echo "=== Updating system ==="
apt update && apt full-upgrade -y
echo "=== Installing packages ==="
apt install -y \
sudo ufw curl nano less htop fail2ban \
jq ripgrep fd-find \
unattended-upgrades systemd-timesyncd
ln -sf /usr/bin/fdfind /usr/local/bin/fd 2>/dev/null || true
# =============================================
# User & SSH key
# =============================================
echo "=== Creating user $NEW_USER ==="
useradd -m -s /bin/bash "$NEW_USER"
echo "$NEW_USER:$NEW_PASS" | chpasswd
usermod -aG sudo "$NEW_USER"
echo "=== Installing SSH public key ==="
SSH_DIR="/home/$NEW_USER/.ssh"
mkdir -p "$SSH_DIR"
chmod 700 "$SSH_DIR"
echo "$PUB_KEY" > "$SSH_DIR/authorized_keys"
chmod 600 "$SSH_DIR/authorized_keys"
chown -R "$NEW_USER:$NEW_USER" "$SSH_DIR"
# =============================================
# SSH hardening (WITHOUT port change)
# =============================================
echo "=== Backing up sshd_config ==="
cp /etc/ssh/sshd_config "/etc/ssh/sshd_config.bak.$(date +%Y%m%d_%H%M%S)"
echo "=== Applying SSH hardening (port change deferred) ==="
cat > /etc/ssh/sshd_config.d/99-hardening.conf <<EOF
# SSH Hardening
PermitRootLogin no
PasswordAuthentication yes
PubkeyAuthentication yes
PermitEmptyPasswords no
MaxAuthTries 4
ClientAliveInterval 300
ClientAliveCountMax 5
LoginGraceTime 60
AllowUsers $NEW_USER
EOF
sshd -t
systemctl restart ssh
# =============================================
# UFW (port 22 open for now)
# =============================================
echo "=== Configuring UFW ==="
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'SSH temporary (port 22)'
ufw --force enable
# =============================================
# fail2ban
# =============================================
echo "=== Configuring fail2ban ==="
cat > /etc/fail2ban/jail.local <<EOF
[DEFAULT]
backend = systemd
bantime = 2h
findtime = 15m
maxretry = 4
ignoreip = 127.0.0.1/8 ::1 $WHITELIST_IPS
banaction = ufw
banaction_allports = ufw
[sshd]
enabled = true
port = 22,$SSH_PORT
filter = sshd
maxretry = 4
bantime = 4h
EOF
systemctl enable --now fail2ban
systemctl restart fail2ban
# =============================================
# sysctl hardening
# =============================================
echo "=== Applying sysctl hardening ==="
cat > /etc/sysctl.d/99-hardening.conf <<'EOF'
# Network
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
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.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
# Kernel
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.yama.ptrace_scope = 1
# FS
fs.protected_symlinks = 1
fs.protected_hardlinks = 1
fs.protected_fifos = 2
fs.protected_regular = 2
EOF
sysctl --system >/dev/null
# =============================================
# Swap (optional)
# =============================================
if [[ -n "$SWAP_SIZE" ]]; then
if swapon --show | grep -q .; then
echo "=== Swap already exists, skipping ==="
else
echo "=== Creating ${SWAP_SIZE}G swapfile ==="
fallocate -l "${SWAP_SIZE}G" /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
cat > /etc/sysctl.d/99-swappiness.conf <<'EOF'
vm.swappiness = 10
vm.vfs_cache_pressure = 50
EOF
sysctl -p /etc/sysctl.d/99-swappiness.conf >/dev/null
fi
fi
# =============================================
# Time sync
# =============================================
systemctl enable --now systemd-timesyncd
timedatectl set-ntp true
# =============================================
# FINAL STEP: SSH port change
# =============================================
echo "=== Changing SSH port to $SSH_PORT ==="
cat > /etc/ssh/sshd_config.d/98-port.conf <<EOF
Port $SSH_PORT
EOF
sshd -t
ufw allow "$SSH_PORT"/tcp comment 'SSH custom port'
# Optional: ask to close port 22
read -rp "Close old SSH port 22 now? (y/N): " CLOSE_22
if [[ "$CLOSE_22" =~ ^[Yy]$ ]]; then
ufw delete allow 22/tcp
echo "Port 22 has been removed from UFW."
fi
systemctl restart ssh
echo ""
echo "=================================================="
echo "Setup completed!"
echo ""
echo "User : $NEW_USER"
echo "SSH port : $SSH_PORT (port 22 still OPEN as safety net)"
echo ""
echo "Test the new port NOW from another terminal:"
echo " ssh -p $SSH_PORT $NEW_USER@YOUR_SERVER_IP"
echo ""
echo "Once verified working, close port 22 manually:"
echo " sudo ufw delete allow 22/tcp"
echo ""
echo "Status checks:"
echo " sudo ufw status verbose"
echo " sudo fail2ban-client status sshd"
echo " sudo systemctl status ssh"
echo ""
echo "reboot recommended: sudo reboot"
echo "=================================================="
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment