#!/usr/bin/env bash set -euo pipefail # ============================================================ # Debian 13 VPS Initial Setup Script # # Includes: # 1. Base update + useful tools # 2. Chrony time sync # 3. Fail2ban # 4. Automatic security updates # 5. Kernel/network tuning # 6. Journald log limits # 7. Locale # 8. Reboot-required check # ============================================================ export DEBIAN_FRONTEND=noninteractive # ----------------------------- # Config # ----------------------------- TIMEZONE="UTC" # Change this if you prefer en_AU.UTF-8 or en_NZ.UTF-8 LOCALE_NAME="en_US.UTF-8" # Journald limits JOURNAL_SYSTEM_MAX_USE="500M" JOURNAL_RUNTIME_MAX_USE="100M" JOURNAL_MAX_RETENTION="1month" # Fail2ban SSH settings FAIL2BAN_MAXRETRY="5" FAIL2BAN_FINDTIME="10m" FAIL2BAN_BANTIME="1h" # ----------------------------- # Helpers # ----------------------------- log() { echo echo "============================================================" echo "$1" echo "============================================================" } warn() { echo echo "WARNING: $1" } require_root() { if [ "$(id -u)" -ne 0 ]; then echo "This script must be run as root." exit 1 fi } detect_debian() { if [ ! -f /etc/os-release ]; then warn "Cannot detect OS because /etc/os-release is missing." return fi . /etc/os-release echo "Detected OS: ${PRETTY_NAME:-unknown}" if [ "${ID:-}" != "debian" ]; then warn "This script is intended for Debian. Continuing anyway." fi if [ "${VERSION_ID:-}" != "13" ]; then warn "This script is intended for Debian 13. Detected VERSION_ID=${VERSION_ID:-unknown}. Continuing anyway." fi } safe_systemctl_enable_now() { local service="$1" if systemctl list-unit-files "$service" >/dev/null 2>&1; then systemctl enable --now "$service" else warn "Service $service not found, skipping enable/start." fi } # ----------------------------- # Start # ----------------------------- require_root detect_debian log "Step 1/8: Updating APT package lists and upgrading system" apt-get update apt-get upgrade -y log "Step 1/8: Installing base useful packages" apt-get install -y \ apt-transport-https \ bash-completion \ btop \ ca-certificates \ curl \ dnsutils \ git \ gnupg \ htop \ iftop \ iotop \ iproute2 \ jq \ less \ lsb-release \ lsof \ nano \ ncdu \ net-tools \ openssh-client \ openssh-server \ rsync \ screen \ strace \ sudo \ tar \ tcpdump \ tmux \ tree \ unzip \ vim \ wget \ zip log "Step 2/8: Installing and enabling Chrony time sync" apt-get install -y chrony timedatectl set-timezone "$TIMEZONE" || warn "Could not set timezone to $TIMEZONE" safe_systemctl_enable_now chrony.service echo echo "Current time status:" timedatectl || true log "Step 3/8: Installing and configuring Fail2ban" apt-get install -y fail2ban mkdir -p /etc/fail2ban/jail.d cat >/etc/fail2ban/jail.d/sshd.local </etc/apt/apt.conf.d/20auto-upgrades <<'EOF' APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; APT::Periodic::AutocleanInterval "7"; APT::Periodic::Verbose "1"; EOF cat >/etc/apt/apt.conf.d/51unattended-upgrades-local <<'EOF' Unattended-Upgrade::Origins-Pattern { "origin=Debian,codename=${distro_codename}-security,label=Debian-Security"; "origin=Debian,codename=${distro_codename},label=Debian"; }; Unattended-Upgrade::Package-Blacklist { }; Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; Unattended-Upgrade::Remove-Unused-Dependencies "false"; Unattended-Upgrade::Automatic-Reboot "false"; Unattended-Upgrade::SyslogEnable "true"; EOF systemctl restart unattended-upgrades || true safe_systemctl_enable_now unattended-upgrades.service echo echo "Testing unattended-upgrades config:" unattended-upgrade --dry-run --debug || warn "unattended-upgrades dry run returned a warning/error. Review output above." log "Step 5/8: Applying kernel and network tuning" cat >/etc/sysctl.d/99-vps-tuning.conf <<'EOF' # ============================================================ # VPS kernel/network tuning # ============================================================ # Use fair queueing. Recommended with BBR. net.core.default_qdisc = fq # Enable BBR congestion control. net.ipv4.tcp_congestion_control = bbr # Enable TCP Fast Open. net.ipv4.tcp_fastopen = 3 # Do not act as a router by default. net.ipv4.ip_forward = 0 net.ipv6.conf.all.forwarding = 0 # Basic anti-spoofing / safer IPv4 behaviour. net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 # Ignore ICMP redirects. 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 # Do not send ICMP redirects. net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 # Ignore source-routed packets. 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.ipv6.conf.default.accept_source_route = 0 # Log suspicious martian packets. net.ipv4.conf.all.log_martians = 1 net.ipv4.conf.default.log_martians = 1 # Reasonable file handle limit. fs.file-max = 2097152 # Reasonable memory behaviour for small VPSes. vm.swappiness = 10 vm.vfs_cache_pressure = 50 EOF sysctl --system echo echo "Current TCP congestion control:" sysctl net.ipv4.tcp_congestion_control || true echo echo "Available TCP congestion controls:" sysctl net.ipv4.tcp_available_congestion_control || true log "Step 6/8: Setting journald log size limits" mkdir -p /etc/systemd/journald.conf.d cat >/etc/systemd/journald.conf.d/99-vps-limits.conf <> /etc/locale.gen fi locale-gen update-locale LANG="$LOCALE_NAME" echo echo "Configured locale:" localectl status || true log "Cleaning up packages" apt-get autoremove -y apt-get autoclean -y log "Step 8/8: Reboot-required check" REBOOT_REQUIRED="no" if [ -f /var/run/reboot-required ]; then REBOOT_REQUIRED="yes" fi if command -v needrestart >/dev/null 2>&1; then echo echo "needrestart summary:" needrestart -b || true fi echo echo "============================================================" echo "Debian 13 VPS initial setup complete." echo "============================================================" echo "Timezone: $TIMEZONE" echo "Locale: $LOCALE_NAME" echo "Reboot required: $REBOOT_REQUIRED" echo if [ "$REBOOT_REQUIRED" = "yes" ]; then echo "A reboot is recommended:" echo " reboot" else echo "No reboot-required flag detected." fi echo echo "Useful checks:" echo " systemctl status chrony" echo " systemctl status fail2ban" echo " fail2ban-client status sshd" echo " systemctl status unattended-upgrades" echo " journalctl --disk-usage" echo " sysctl net.ipv4.tcp_congestion_control" echo