Overview
A new VPS is typically reachable over SSH as root, which makes it a target for automated credential scanners as soon as its IP is live. Do every step here before installing the app; it takes about ten minutes. The wider deployment sequence is in hostinger-vps.
Create a non-root user and prove it works first
adduser deploy && usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh && cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keysOpen a second terminal and SSH in as deploy before changing anything else. Keep the original session open until the new login and sudo work.
Disable root and password SSH, then check the effective values
Put the settings in a drop-in so cloud images cannot override them. OpenSSH uses the first value it reads, and /etc/ssh/sshd_config.d/*.conf files are included before the main file’s own settings; a provider drop-in with PasswordAuthentication yes silently wins over an edit to sshd_config.
cat >/etc/ssh/sshd_config.d/00-hardening.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
EOF
sshd -t && systemctl reload ssh
sshd -T | grep -Ei 'permitrootlogin|passwordauthentication|kbdinteractive'sshd -T prints the values in effect; trust it over the file. Leave UsePAM yes; disabling it skips PAM account and session handling. KbdInteractiveAuthentication is the current name for the older ChallengeResponseAuthentication.
Default-deny with UFW, allow SSH before enabling
ufw default deny incoming && ufw default allow outgoing
ufw allow OpenSSH && ufw allow 80/tcp && ufw allow 443/tcp
ufw enable && ufw status verboseAdd the SSH rule before enable or you lock yourself out. Audit with ufw status numbered after each new service and remove stale rules with ufw delete <n>. If you also use Hostinger’s dashboard firewall, mirror the same ports there.
Install fail2ban and override in jail.local
apt install -y fail2ban && systemctl enable --now fail2ban
fail2ban-client status sshdThe SSH jail is on by default on Debian and Ubuntu. Put overrides in /etc/fail2ban/jail.local, which survives package upgrades:
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1hThat bans an IP for one hour after five failures in ten minutes.
Enable unattended security upgrades
apt install -y unattended-upgrades && dpkg-reconfigure --priority=low unattended-upgradesIn /etc/apt/apt.conf.d/50unattended-upgrades, confirm the security origin ("${distro_id}:${distro_codename}-security";) is allowed. Kernel and core-library fixes need a reboot, which the tool does not do by default; schedule one in a quiet window:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Skip automatic reboot if a restart would interrupt a single-instance service you cannot tolerate losing; reboot manually after patch notices instead.
Verify before going live
ssh root@<ip> # must be refused
ssh -o PreferredAuthentications=password deploy@<ip> # must be refused
ufw status verbose # only 22, 80, 443
fail2ban-client status # sshd jail active
unattended-upgrade --dry-run --debug 2>&1 | head -20Record the date and result, and repeat after major package or config changes.