Server Initial Security Hardening Script
A freshly provisioned cloud server usually runs but is not secure by default: root can log in over SSH, password authentication is on, the firewall is off, and there is no brute-force protection. Many intrusions happen in the first few days after a server goes live. The script below chains the common hardening steps into a single run and targets Debian-based systems such as Ubuntu 22.04/24.04.
Why are the "first few days" the most dangerous? Cloud scanners start probing a new IP within minutes of it going online, and automated dictionary attacks against port 22 are routine. A server with password auth enabled and root login allowed can be compromised within hours. This is not scaremongering — a honeypot server can easily collect thousands of SSH brute-force attempts in 24 hours. The point of hardening is not "fix it later" but "finish it on day one". The script below chains five tasks — user creation, SSH lockdown, firewall, Fail2Ban, and auto-updates — into a single run you can finish in five minutes.
The Full Hardening Script
#!/bin/bash
# server-security.sh — Run on fresh Ubuntu 22.04/24.04
set -euo pipefail
# === Configuration ===
NEW_USER="deploy"
SSH_PORT="22" # Change to non-standard for security
# === 1. System Update ===
apt update && apt upgrade -y && apt autoremove -y
# === 2. Create Non-Root User ===
if ! id "$NEW_USER" &>/dev/null; then
useradd -m -s /bin/bash "$NEW_USER"
usermod -aG sudo "$NEW_USER"
# Copy root SSH keys to new user
mkdir -p /home/$NEW_USER/.ssh
cp ~/.ssh/authorized_keys /home/$NEW_USER/.ssh/ 2>/dev/null || true
chown -R $NEW_USER:$NEW_USER /home/$NEW_USER/.ssh
chmod 700 /home/$NEW_USER/.ssh && chmod 600 /home/$NEW_USER/.ssh/authorized_keys
fi
# === 3. Harden SSH ===
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#\?Port.*/Port '"$SSH_PORT"'/' /etc/ssh/sshd_config
sed -i 's/^#\?MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config
systemctl restart sshd
# === 4. Configure UFW Firewall ===
ufw default deny incoming
ufw default allow outgoing
ufw allow $SSH_PORT/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw --force enable
# === 5. Install & Configure Fail2Ban ===
apt install fail2ban -y
cat > /etc/fail2ban/jail.local << 'EOF'
[sshd]
enabled = true
port = $SSH_PORT
maxretry = 3
bantime = 3600
findtime = 600
EOF
systemctl enable --now fail2ban
# === 6. Auto-Security-Updates ===
apt install unattended-upgrades -y
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
echo "=== Security hardening complete! ==="
echo "User: $NEW_USER | SSH port: $SSH_PORT"
echo "Test new SSH session before closing this one."
What Each Step Does
| Step | Purpose |
|---|---|
| Create a non-root user | Principle of least privilege for daily work |
| Disable root login | Remove the most-targeted brute-force account |
| Disable password auth | Key-only login stops credential stuffing at the source |
| UFW firewall | Default deny inbound, allow only SSH/HTTP/HTTPS |
| Fail2Ban | Ban a source IP for an hour after 3 failed attempts |
| Auto-upgrades | Install security patches unattended |
Understanding the intent behind each step matters more than memorizing the script. Creating a non-root user follows the principle of least privilege: deploy with the deploy account and sudo only for system-level work, so even if the web app is compromised the attacker does not get root. Disabling root login and password auth are two sides of the same coin: root is the number-one dictionary-attack target and passwords are guessable weak credentials — with both off, SSH brute-forcing is essentially neutralized. Fail2Ban is the safety net, auto-banning IPs that keep trying even after the rest is in place. Auto-updates solve the "known but unpatched vulnerability" problem, since many intrusions exploit vulnerabilities disclosed but never patched.
Two Pitfalls to Watch
The first pitfall is ordering: copy your SSH public key and confirm key login works before restarting sshd. If you disable password login before the key is in place, you can lock yourself out. The second is the port: the default 22 is the favorite of scanners. Moving to a high port (such as 2222) reduces noise, but remember to update the firewall rule and tell your team. Note that changing the SSH port is not a security measure — it only reduces log noise. Real security comes from key-based login and Fail2Ban; do not expect a different port to stop attackers.
Verifying the Hardening Took Effect
# Show effective SSH settings
sshd -T | grep -E "permitrootlogin|passwordauthentication"
# Show firewall rules
ufw status verbose
# Check Fail2Ban status
fail2ban-client status sshd
# List listening ports
ss -tlnp
Hardening must be verified, not assumed to have worked because the script exited cleanly. sshd -T prints the effective sshd config, confirming PermitRootLogin no and PasswordAuthentication no actually took; ss -tlnp reveals accidentally exposed ports, such as a forgotten debug endpoint. A useful habit is to test port reachability from outside with nc -vz <IP> 22 afterward, confirming only the expected ports are reachable.
Real-World Scenario
For a cluster of servers, commit the script to a configuration management tool and run it uniformly instead of running it manually on each box. With Ansible, break the script into playbook tasks and run them across an inventory, which also guarantees consistent configuration across machines — no more "this box was hardened, that one was not" gaps. For a deeper baseline, see the SSH hardening guide and the CIS benchmark hardening; for day-to-day checks, combine it with the server operations tips.
Frequently Asked Questions
- Do I really need all this for a temporary test box? At minimum complete user creation, the firewall, and SSH hardening — the cost is low and it protects the shared gateway and resources.
- What if I lock myself out after disabling password login? Log in through the cloud provider's VNC or serial console, temporarily set
PasswordAuthentication yes, fix the key, then re-disable it. - Should I run this on a fresh server only? It works on any Debian-based box; on existing servers, run the relevant sections during a maintenance window.
- What if I lose my key file? Keep an alternate login method (VNC or a second key) on the server, and back up the private key in an encrypted password manager; without any fallback after losing the key, a reinstall is the only option.
References
Reference: Ubuntu server security documentation https://ubuntu.com/server/docs/security-hardening
Reference: OpenSSH manual https://man.openbsd.org/sshd_config