Last active
April 19, 2026 00:21
-
-
Save nomo3919/aabe761f8994d4879b8ec21fd234f13b to your computer and use it in GitHub Desktop.
debian-init-script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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