Skip to content

Instantly share code, notes, and snippets.

@ilKhr
Created February 12, 2026 21:04
Show Gist options
  • Select an option

  • Save ilKhr/ecbacc4b87e0a1fb66687e6a533d8090 to your computer and use it in GitHub Desktop.

Select an option

Save ilKhr/ecbacc4b87e0a1fb66687e6a533d8090 to your computer and use it in GitHub Desktop.
Скрипт для автоматической настройки VPS после покупки

✅ Безопасность:

Проверка запуска от root

Резервное копирование конфигураций

Тестирование SSH конфигурации перед применением

Проверка валидности SSH ключей

✅ Удобство:

Интерактивные запросы с понятными подсказками

Сохранение состояния (можно прервать и продолжить позже)

Цветной вывод для лучшей читаемости

Логирование всех действий

✅ Защита от ошибок:

Проверка существующих пользователей

Тест SSH подключения перед отключением root

Возможность откатить изменения через бекапы

Валидация вводимых портов

✅ Дополнительные возможности:

Установка fail2ban с конфигурацией

Настройка rsyslog

Опциональная установка полезных утилит

Финальная проверка всех настроек

Скрипт будет спрашивать вас на каждом важном шаге и никогда не применит критическое изменение без вашего подтверждения и проверки.

#!/usr/bin/env bash
# ============================================================================
# VPS Hardening Script
# ============================================================================
# Description: Automates initial VPS security setup including non-root user,
# SSH hardening, firewall configuration, automatic security updates,
# and additional security tools installation.
#
# Usage: sudo bash vps-hardening.sh
# ============================================================================
set -euo pipefail # Exit on error, undefined variable, pipe failure
IFS=$'\n\t'
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Script configuration
SCRIPT_NAME="$(basename "$0")"
LOG_FILE="/var/log/vps-hardening-$(date +%Y%m%d-%H%M%S).log"
CONFIG_DIR="$HOME/.vps-hardening"
STATE_FILE="$CONFIG_DIR/setup-state.conf"
# ============================================================================
# Helper Functions
# ============================================================================
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}" | tee -a "$LOG_FILE"
}
warn() {
echo -e "${YELLOW}[WARNING] $1${NC}" | tee -a "$LOG_FILE"
}
error() {
echo -e "${RED}[ERROR] $1${NC}" | tee -a "$LOG_FILE"
exit 1
}
info() {
echo -e "${BLUE}[INFO] $1${NC}" | tee -a "$LOG_FILE"
}
confirm() {
local prompt="$1"
local default="${2:-n}"
local response
if [[ "$default" == "y" ]]; then
prompt="$prompt [Y/n]: "
else
prompt="$prompt [y/N]: "
fi
read -r -p "$prompt" response
response=${response:-$default}
[[ "$response" =~ ^[Yy]$ ]]
}
check_root() {
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root. Use: sudo bash $SCRIPT_NAME"
fi
}
check_os() {
if [[ -f /etc/os-release ]]; then
. /etc/os-release
if [[ "$ID" != "ubuntu" ]]; then
warn "This script is optimized for Ubuntu. Detected OS: $ID"
if ! confirm "Continue anyway?" "n"; then
exit 0
fi
fi
else
warn "Cannot detect OS version. Proceed with caution."
fi
}
save_state() {
mkdir -p "$CONFIG_DIR"
cat > "$STATE_FILE" << EOF
# VPS Hardening Setup State
# Last run: $(date)
SSH_USER="${SSH_USER:-}"
SSH_CONFIGURED="${SSH_CONFIGURED:-false}"
FIREWALL_CONFIGURED="${FIREWALL_CONFIGURED:-false}"
AUTO_UPDATES_CONFIGURED="${AUTO_UPDATES_CONFIGURED:-false}"
FAIL2BAN_INSTALLED="${FAIL2BAN_INSTALLED:-false}"
EOF
}
load_state() {
if [[ -f "$STATE_FILE" ]]; then
source "$STATE_FILE"
info "Found previous setup state from: $(grep "Last run:" "$STATE_FILE" | cut -d: -f2-)"
fi
}
# ============================================================================
# Main Setup Functions
# ============================================================================
create_nonroot_user() {
log "=== Step 1: Creating non-root user ==="
# Check if we already created a user
if [[ -n "${SSH_USER:-}" ]] && id "$SSH_USER" &>/dev/null; then
info "User $SSH_USER already exists. Skipping user creation."
return 0
fi
local username=""
while [[ -z "$username" ]]; do
read -r -p "Enter username for the new non-root user: " username
if [[ -z "$username" ]]; then
warn "Username cannot be empty"
elif id "$username" &>/dev/null; then
warn "User $username already exists. Choose another name or press Ctrl+C to exit."
username=""
fi
done
# Create user with home directory
adduser --gecos "" "$username"
# Add to sudo group
usermod -aG sudo "$username"
# Verify user creation
if id "$username" &>/dev/null; then
log "User $username created successfully and added to sudo group"
SSH_USER="$username"
save_state
else
error "Failed to create user $username"
fi
}
setup_ssh_for_user() {
log "=== Step 2: Setting up SSH for new user ==="
if [[ -z "${SSH_USER:-}" ]]; then
error "No SSH user defined. Please create a user first."
fi
# Check if SSH is already configured
if [[ "${SSH_CONFIGURED:-false}" == "true" ]]; then
if confirm "SSH appears to be already configured. Reconfigure?" "n"; then
SSH_CONFIGURED="false"
else
info "Skipping SSH configuration"
return 0
fi
fi
# Switch to new user context for SSH setup
local user_home
user_home=$(eval echo "~$SSH_USER")
# Create .ssh directory
sudo -u "$SSH_USER" mkdir -p "$user_home/.ssh"
# Setup SSH key
if [[ ! -f "$user_home/.ssh/authorized_keys" ]]; then
info "Please paste your public SSH key (from ~/.ssh/id_rsa.pub or equivalent)"
info "Enter the key and press Ctrl+D when done (multi-line paste supported):"
local pubkey=""
while IFS= read -r line; do
pubkey+="$line"$'\n'
done
if [[ -n "$pubkey" ]]; then
echo "$pubkey" | sudo -u "$SSH_USER" tee "$user_home/.ssh/authorized_keys" > /dev/null
else
error "No SSH key provided. Cannot continue without SSH key access."
fi
else
info "Authorized keys file already exists. Current keys:"
cat "$user_home/.ssh/authorized_keys"
if ! confirm "Replace existing SSH key(s)?" "n"; then
info "Keeping existing SSH keys"
else
info "Please paste your new public SSH key and press Ctrl+D when done:"
local newkey=""
while IFS= read -r line; do
newkey+="$line"$'\n'
done
echo "$newkey" | sudo -u "$SSH_USER" tee "$user_home/.ssh/authorized_keys" > /dev/null
fi
fi
# Set correct permissions
sudo -u "$SSH_USER" chmod 700 "$user_home/.ssh"
sudo -u "$SSH_USER" chmod 600 "$user_home/.ssh/authorized_keys"
# Test SSH configuration
info "Testing SSH configuration..."
local ip
ip=$(curl -s ifconfig.me || echo "your_server_ip")
warn "IMPORTANT: Before proceeding, test SSH access in a NEW terminal:"
echo -e "${YELLOW}ssh $SSH_USER@$ip${NC}"
echo ""
if ! confirm "Have you successfully connected via SSH in another terminal?" "n"; then
warn "Please test SSH connection before continuing. This is critical!"
if confirm "Continue without testing? (NOT RECOMMENDED)" "n"; then
warn "Proceeding at your own risk..."
else
info "Exiting. Please test SSH connection and run script again."
exit 0
fi
fi
SSH_CONFIGURED="true"
save_state
log "SSH configured successfully for user $SSH_USER"
}
disable_root_password_auth() {
log "=== Step 3: Disabling root login and password authentication ==="
local sshd_config="/etc/ssh/sshd_config"
local backup_file="${sshd_config}.backup-$(date +%Y%m%d-%H%M%S)"
# Backup SSH config
cp "$sshd_config" "$backup_file"
info "SSH configuration backed up to $backup_file"
# Disable root login
if grep -q "^PermitRootLogin" "$sshd_config"; then
sed -i 's/^PermitRootLogin.*/PermitRootLogin no/' "$sshd_config"
else
echo "PermitRootLogin no" >> "$sshd_config"
fi
# Disable password authentication
if grep -q "^PasswordAuthentication" "$sshd_config"; then
sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' "$sshd_config"
else
echo "PasswordAuthentication no" >> "$sshd_config"
fi
# Ensure challenge-response authentication is also disabled
if grep -q "^ChallengeResponseAuthentication" "$sshd_config"; then
sed -i 's/^ChallengeResponseAuthentication.*/ChallengeResponseAuthentication no/' "$sshd_config"
else
echo "ChallengeResponseAuthentication no" >> "$sshd_config"
fi
# Test SSH configuration before reloading
sshd -t || error "Invalid SSH configuration. Please check $sshd_config"
# Reload SSH service
systemctl reload sshd || systemctl reload ssh
log "Root login and password authentication disabled successfully"
}
configure_firewall() {
log "=== Step 4: Configuring UFW firewall ==="
if [[ "${FIREWALL_CONFIGURED:-false}" == "true" ]]; then
if ! confirm "Firewall appears to be configured. Reconfigure?" "n"; then
info "Skipping firewall configuration"
sudo ufw status verbose
return 0
fi
fi
# Check if UFW is installed
if ! command -v ufw &> /dev/null; then
info "Installing UFW..."
apt-get update -qq
apt-get install -y ufw
fi
# Reset UFW if requested
if sudo ufw status | grep -q "active"; then
warn "UFW is currently active"
if confirm "Reset UFW to default settings?" "n"; then
sudo ufw --force reset
fi
fi
# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow essential services
info "Allowing essential services:"
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
# Ask about additional ports
if confirm "Do you want to allow any additional ports?" "n"; then
while true; do
read -r -p "Enter port number to allow (or 'done' to finish): " port
if [[ "$port" == "done" ]]; then
break
elif [[ "$port" =~ ^[0-9]+$ ]] && [[ "$port" -ge 1 ]] && [[ "$port" -le 65535 ]]; then
read -r -p "TCP only? (y/n): " tcp_only
if [[ "$tcp_only" =~ ^[Yy]$ ]]; then
sudo ufw allow "$port"/tcp
else
sudo ufw allow "$port"
fi
else
warn "Invalid port number"
fi
done
fi
# Enable firewall
warn "About to enable firewall. This may close current SSH connection if SSH isn't allowed."
if confirm "Enable UFW now?" "y"; then
echo "y" | sudo ufw enable
sudo ufw status verbose
FIREWALL_CONFIGURED="true"
save_state
log "Firewall configured successfully"
else
warn "Firewall not enabled. Security compromised."
fi
}
setup_auto_updates() {
log "=== Step 5: Enabling automatic security updates ==="
if [[ "${AUTO_UPDATES_CONFIGURED:-false}" == "true" ]]; then
if ! confirm "Automatic updates already configured. Reconfigure?" "n"; then
info "Skipping automatic updates configuration"
apt-config dump APT::Periodic::Unattended-Upgrade
return 0
fi
fi
# Install unattended-upgrades if not present
if ! dpkg -l | grep -q unattended-upgrades; then
info "Installing unattended-upgrades..."
apt-get update -qq
apt-get install -y unattended-upgrades
fi
# Configure unattended-upgrades
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::DevRelease "false";
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-Time "02:00";
EOF
# Enable automatic updates
cat > /etc/apt/apt.conf.d/20auto-upgrades << 'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
EOF
# Apply configuration
dpkg-reconfigure -f noninteractive unattended-upgrades
# Verify configuration
if apt-config dump APT::Periodic::Unattended-Upgrade | grep -q "1"; then
AUTO_UPDATES_CONFIGURED="true"
save_state
log "Automatic security updates enabled successfully"
else
error "Failed to configure automatic updates"
fi
}
install_security_tools() {
log "=== Step 6: Installing additional security tools ==="
local tools_to_install=()
# Check and install fail2ban
if ! dpkg -l | grep -q fail2ban; then
if confirm "Install fail2ban for brute-force protection?" "y"; then
tools_to_install+=("fail2ban")
fi
else
info "fail2ban is already installed"
FAIL2BAN_INSTALLED="true"
fi
# Check and install rsyslog
if ! dpkg -l | grep -q rsyslog; then
if confirm "Install rsyslog for system logging?" "y"; then
tools_to_install+=("rsyslog")
fi
else
info "rsyslog is already installed"
fi
# Additional recommended tools
if confirm "Install additional useful tools (htop, curl, wget, git, vim)?" "n"; then
tools_to_install+=(htop curl wget git vim)
fi
if [[ ${#tools_to_install[@]} -gt 0 ]]; then
info "Installing: ${tools_to_install[*]}"
apt-get update -qq
apt-get install -y "${tools_to_install[@]}"
# Configure fail2ban if installed
if [[ " ${tools_to_install[*]} " =~ " fail2ban " ]] || [[ "${FAIL2BAN_INSTALLED:-false}" == "true" ]]; then
configure_fail2ban
FAIL2BAN_INSTALLED="true"
fi
log "Additional tools installed successfully"
else
info "No additional tools selected for installation"
fi
save_state
}
configure_fail2ban() {
log "Configuring fail2ban..."
# Create local configuration
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
destemail = root@localhost
action = %(action_mwl)s
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400
EOF
systemctl restart fail2ban
systemctl enable fail2ban
log "fail2ban configured and started"
}
verify_setup() {
log "=== Final Verification ==="
info "Running final checks..."
# Check SSH configuration
echo -e "\n${BLUE}1. SSH Configuration:${NC}"
sshd -T | grep -E "permitrootlogin|passwordauthentication|challengeresponseauthentication" | grep -v "^#" | sed 's/^/ /'
# Check firewall
echo -e "\n${BLUE}2. Firewall Status:${NC}"
if command -v ufw &> /dev/null; then
sudo ufw status | sed 's/^/ /'
fi
# Check automatic updates
echo -e "\n${BLUE}3. Automatic Updates:${NC}"
apt-config dump APT::Periodic::Unattended-Upgrade | sed 's/^/ /'
# Check fail2ban
echo -e "\n${BLUE}4. fail2ban Status:${NC}"
if command -v fail2ban-client &> /dev/null; then
fail2ban-client status sshd | sed 's/^/ /' 2>/dev/null || echo " fail2ban is installed but not active for sshd"
fi
# Summary
echo -e "\n${GREEN}=== Setup Summary ===${NC}"
echo -e " Non-root user: ${SSH_USER:-Not configured}"
echo -e " SSH key auth: Enabled"
echo -e " Root login: Disabled"
echo -e " Password auth: Disabled"
echo -e " Firewall: ${FIREWALL_CONFIGURED:-false}"
echo -e " Auto updates: ${AUTO_UPDATES_CONFIGURED:-false}"
echo -e " fail2ban: ${FAIL2BAN_INSTALLED:-false}"
echo -e "\n${YELLOW}Log file saved to: $LOG_FILE${NC}"
echo -e "${YELLOW}Configuration state saved to: $STATE_FILE${NC}"
}
# ============================================================================
# Main Script Execution
# ============================================================================
main() {
echo -e "${GREEN}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ VPS Hardening Setup ║"
echo "║ Automated Security Script ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
# Pre-flight checks
check_root
check_os
load_state
# Ensure system is updated
info "Updating package lists..."
apt-get update -qq
# Main setup sequence
create_nonroot_user
setup_ssh_for_user
disable_root_password_auth
configure_firewall
setup_auto_updates
install_security_tools
# Final verification
verify_setup
echo -e "\n${GREEN}✓ VPS hardening completed successfully!${NC}"
echo -e "${YELLOW}Please keep this session open and test SSH in a new terminal:${NC}"
echo -e " ssh ${SSH_USER}@$(curl -s ifconfig.me 2>/dev/null || echo 'your-server-ip')"
echo -e "\n${BLUE}Remember to:${NC}"
echo -e " 1. Save your SSH key in a safe place"
echo -e " 2. Regularly update your system: sudo apt update && sudo apt upgrade"
echo -e " 3. Monitor fail2ban status: sudo fail2ban-client status sshd"
echo -e " 4. Check firewall rules: sudo ufw status verbose"
}
# Run main function
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment