Enterprise-grade VM security hardening with SOC2-aligned controls and zero-configuration deployment.
Transform your Ubuntu server into a security-hardened VM in minutes. Automated setup with intelligent defaults, comprehensive monitoring, and built-in safeguards against lockouts.
Problem: Manual security hardening is complex, time-consuming, and error-prone. One misconfiguration can lock you out permanently.
Solution: Automated, battle-tested security setup with:
- β Smart Anti-Lockout: Automatic IP whitelisting, dynamic range support, pre-flight validation
- β SOC2 Ready: Audit logging, file integrity monitoring, 365-day log retention
- β Zero Config: Sensible defaults, interactive prompts, validate-before-apply
- SSH Hardening - Key-only authentication, modern ciphers (ChaCha20, AES-GCM), root login disabled
- Intrusion Prevention - fail2ban with configurable thresholds and intelligent IP banning
- Firewall Management - UFW with default-deny policy and rate limiting
- Docker Security - Prevents Docker from bypassing UFW rules (common misconfiguration)
- Audit Logging (auditd) - Track system calls, file modifications, user actions
- File Integrity (AIDE) - Daily checks for unauthorized system changes
- Log Retention - 365-day retention for SOC2/ISO compliance
- Time Synchronization - chrony for accurate audit timestamps
- Real-time Status Dashboard - Security health scores, active threats, exposed services
- Interactive Log Viewer - Browse SSH failures, bans, firewall blocks, audit events
- Whitelist Manager - Dynamic IP helper for changing IPs
- One-Command Updates - Safe reapplication of security policies
- OS: Ubuntu 24.04 LTS (22.04 compatible)
- Access: Root/sudo privileges
- SSH Key: Generate with
ssh-keygen -t ed25519on your local machine - Backup Access: Console access via VPS control panel (emergency use)
# Download the script
curl -fsSL https://raw.githubusercontent.com/2kjm/vm-security/main/vm-security.sh -o vm-security.sh
# Make executable
chmod +x vm-security.sh
# Install system-wide (recommended)
sudo ./vm-security.sh install
# Run interactive setup
sudo vm-security setupThe interactive setup will guide you through:
- Admin User Creation - Create non-root admin with sudo privileges
- SSH Key Configuration - Paste your public key (
cat ~/.ssh/id_ed25519.pub) - Whitelist Strategy - Choose IP whitelisting approach:
- Single IP (
β οΈ risky for dynamic IPs) - /24 network range (β recommended - covers 256 IPs)
- /16 network range (broader coverage)
- Custom ranges
- Single IP (
ALWAYS test SSH in a new terminal before closing your current session:
# In a NEW terminal window
ssh -p 22 your_username@your_vm_ip
# Once logged in, test sudo
sudo whoamiDO NOT logout until verified! Keep your original session open as backup.
# Quick security overview
vm-security status
# Detailed analysis with attack metrics
sudo vm-security status --detailed
# Shorter aliases (after install)
security-status
vm-security-statusStatus Output Includes:
- Service health (SSH, fail2ban, UFW, auditd, chrony)
- Active bans and attack statistics
- Exposed services and ports
- Docker firewall bypass detection
- Security health score (0-100)
# Interactive log viewer
vm-security logsAvailable Logs:
- Recent SSH Failed Logins (50)
- Currently Banned IPs
- Recent UFW Blocks (50)
- Audit Activity (50)
- AIDE Integrity Reports
- Setup Log
- Exit (with confirmation)
Safe to run multiple times - backs up configs before changes:
sudo vm-security reapply# Unban specific IP
sudo vm-security unban 203.0.113.45
# Unban all IPs
sudo vm-security unban all
# Add IP to whitelist (dynamic IP helper)
sudo vm-security whitelistvm-security helpEdit variables at the top of vm-security.sh before running setup:
NEW_USER="" # Admin username (leave empty for interactive prompt)
SSH_PUBLIC_KEY="" # SSH public key (leave empty for prompt)
SSH_PORT=22 # SSH port (change only if needed)
CHANGE_SSH_PORT=false # Set true to change from default port 22
# fail2ban Settings
FAIL2BAN_MAXRETRY=5 # Failed attempts before ban
FAIL2BAN_BANTIME=3600 # Ban duration in seconds (1 hour)
FAIL2BAN_FINDTIME=600 # Time window for failed attempts (10 min)
FAIL2BAN_IGNOREIP="127.0.0.1/8" # Whitelisted IPs/ranges (space-separated)
# Additional Settings
ALLOWED_HTTP_PORTS="80,443" # HTTP/HTTPS ports (comma-separated)
ENABLE_AUTO_UPDATES=true # Automatic security updatesMost ISPs assign dynamic IPs that change on router restart or lease renewal. Whitelist your network range:
# Find your current IP
curl ifconfig.me
# Example output: 203.0.113.45
# Option 1: /24 range (Recommended)
# Whitelists 203.0.113.0 - 203.0.113.255 (256 IPs)
FAIL2BAN_IGNOREIP="127.0.0.1/8 203.0.113.0/24"
# Option 2: /16 range (Broader)
# Whitelists 203.0.0.0 - 203.0.255.255 (65,536 IPs)
FAIL2BAN_IGNOREIP="127.0.0.1/8 203.0.0.0/16"
# Option 3: Multiple ranges/IPs
FAIL2BAN_IGNOREIP="127.0.0.1/8 203.0.113.0/24 198.51.100.0/24 192.0.2.10"The setup wizard validates IP/CIDR notation and detects your current IP automatically.
# Create new user with sudo privileges
sudo adduser alice
sudo usermod -aG sudo alice
# Set up their SSH key
sudo mkdir -p /home/alice/.ssh
echo "ssh-ed25519 AAAAC3Nz...alice@laptop" | sudo tee /home/alice/.ssh/authorized_keys
sudo chmod 700 /home/alice/.ssh
sudo chmod 600 /home/alice/.ssh/authorized_keys
sudo chown -R alice:alice /home/alice/.ssh
# Generate password for sudo/console access
sudo passwd alice# Allow specific port
sudo ufw allow 8080/tcp
# Allow port range
sudo ufw allow 8000:8100/tcp
# Allow from specific IP
sudo ufw allow from 203.0.113.45 to any port 5432
# View all rules
sudo ufw status numbered
# Delete rule by number
sudo ufw delete 3# Method 1: Interactive helper (recommended)
sudo vm-security whitelist
# Method 2: Manual edit
sudo nano /etc/fail2ban/jail.local
# Update: ignoreip = 127.0.0.1/8 203.0.113.0/24 198.51.100.0/24
sudo systemctl restart fail2ban# Bind only to localhost (recommended)
docker run -p 127.0.0.1:8080:80 nginx
# Then expose via reverse proxy (e.g., nginx, caddy)
sudo ufw allow 80/tcp # Only allow proxy portSymptom: SSH connection refused or "Connection closed by remote host"
Solution (via console access):
# Check if banned
sudo fail2ban-client status sshd
# Unban your IP
sudo fail2ban-client unban 203.0.113.45
# Or unban everyone
sudo fail2ban-client unban --all
# Add your IP to whitelist permanently
sudo vm-security whitelistSymptom: "Permission denied (publickey)" or connection timeout
Diagnosis (via console):
# Check SSH service status
sudo systemctl status sshd
# Test configuration validity
sudo sshd -T
# Check if SSH is listening
sudo ss -tlnp | grep :22
# View recent SSH logs
sudo tail -50 /var/log/auth.logFix - SSH Key Issues:
# Verify key file permissions
ls -la /home/YOUR_USER/.ssh/
# Fix permissions
sudo chmod 700 /home/YOUR_USER/.ssh
sudo chmod 600 /home/YOUR_USER/.ssh/authorized_keys
sudo chown -R YOUR_USER:YOUR_USER /home/YOUR_USER/.ssh
# Verify key format
cat /home/YOUR_USER/.ssh/authorized_keys
# Should start with: ssh-ed25519, ssh-rsa, ecdsa-sha2-nistp256, etc.Emergency: Temporarily Disable Hardening:
sudo mv /etc/ssh/sshd_config.d/99-hardening.conf /root/99-hardening.conf.backup
sudo systemctl restart sshd
# Test SSH, then restore: sudo mv /root/99-hardening.conf.backup /etc/ssh/sshd_config.d/99-hardening.conf# Check current rules
sudo ufw status numbered
# Check logs for blocks
sudo tail -100 /var/log/ufw.log
# Temporarily disable (for testing only)
sudo ufw disable
# Re-enable after fixing rules
sudo ufw enable# Check if AIDE database exists
ls -lh /var/lib/aide/aide.db
# Manually run check
sudo /etc/cron.daily/aide-check
# View report
sudo ls -lh /var/log/aide/
sudo cat /var/log/aide/aide-check-*.log | tail -50# List all sudo users (via console)
getent group sudo
# Or check all users
cat /etc/passwd | grep -v nologin | grep -v false | cut -d: -f1# Check service resource usage
systemctl status auditd fail2ban
# View top processes
htop
# If AIDE is running (daily scan)
ps aux | grep aide
# This is normal - scan takes 2-10 minutes
# Reduce auditd rules if needed
sudo nano /etc/audit/rules.d/soc2-compliance.rules
sudo systemctl restart auditd| Component | Configuration | Impact |
|---|---|---|
| SSH | β’ No root login β’ Key-only authentication (password auth disabled) β’ Modern ciphers (ChaCha20, AES-GCM) β’ MaxAuthTries: 3 β’ Verbose logging |
High |
| fail2ban | β’ 5 attempts β 1hr ban β’ 10min time window β’ Current IP auto-whitelisted β’ SSH jail enabled |
High |
| UFW Firewall | β’ Default deny incoming β’ Default allow outgoing β’ SSH + HTTP/HTTPS allowed β’ Rate limiting on SSH |
High |
| Docker | β’ Cannot bypass UFW β’ Inter-container communication disabled β’ No new privileges β’ Logging enabled |
High |
| auditd | β’ System call logging β’ File access monitoring β’ User/group changes tracked β’ 8192-entry buffer |
Medium |
| AIDE | β’ Daily integrity checks β’ Critical file monitoring β’ Change reports to /var/log/aide/ |
Low |
| chrony | β’ NTP time sync β’ Multiple pool sources β’ Clock drift tracking |
Low |
| Password Policy | β’ 14 character minimum β’ Complexity requirements β’ 90-day expiration β’ User uniqueness check |
Medium |
| Kernel | β’ SYN cookies enabled β’ IP forwarding restricted β’ ICMP redirects blocked β’ Martian packet logging |
Medium |
| Log Retention | β’ 365 days for auth/syslog β’ Compressed rotation β’ SOC2 compliant |
Low |
| Service | CPU | Memory | Disk I/O | Notes |
|---|---|---|---|---|
| fail2ban | ~1-2% | 30-50 MB | Low | Increases with attack volume |
| auditd | ~2-5% | 20-40 MB | Medium | Depends on audit rules |
| UFW | <0.1% | 10-20 MB | Minimal | iptables is very efficient |
| AIDE | 10-30% | 50-100 MB | High | Only during daily scan (2-10 min) |
| Other | <1% | 50-100 MB | Minimal | Combined overhead |
| Total | ~5-8% | ~200 MB | Low | Continuous (except AIDE) |
Recommendation: For production workloads, allocate at least 2 vCPU and 2GB RAM.
| File | Purpose |
|---|---|
/etc/ssh/sshd_config.d/99-hardening.conf |
SSH security settings |
/etc/fail2ban/jail.local |
fail2ban rules and whitelist |
/etc/ufw/after.rules |
Docker-UFW integration |
/etc/audit/rules.d/soc2-compliance.rules |
Audit rules |
/etc/aide/aide.conf |
File integrity monitoring config |
/etc/sysctl.d/99-soc2-hardening.conf |
Kernel security parameters |
| Log | Path | Retention |
|---|---|---|
| Setup Log | /root/security-setup-YYYYMMDD-HHMMSS.log |
Manual |
| SSH Authentication | /var/log/auth.log |
365 days |
| fail2ban | /var/log/fail2ban.log |
365 days |
| Audit Logs | /var/log/audit/audit.log |
Permanent |
| AIDE Reports | /var/log/aide/aide-check-YYYYMMDD.log |
Manual |
| Firewall | /var/log/ufw.log |
365 days |
| System Log | /var/log/syslog |
365 days |
# Interactive viewer
vm-security logs
# Direct access
sudo tail -50 /var/log/auth.log
sudo journalctl -u sshd -n 50
sudo fail2ban-client status sshdThis tool includes multiple safeguards to prevent accidental lockouts:
- β Automatic IP Detection - Detects your connection IP via multiple methods
- β Pre-Whitelist - Your IP is whitelisted before fail2ban starts
- β Network Range Support - Whitelist entire subnets for dynamic IPs
- β SSH Key Validation - Keys are tested before applying SSH hardening
- β
Configuration Testing -
sshd -Tvalidation before restart - β Increased Retry Limit - 5 attempts (vs default 3) reduces false bans
- β Localhost Exemption - Local connections never banned
- β Interactive Prompts - Confirm before applying critical changes
- β Multiple Warnings - Repeated reminders to test before logout
- β Backup Configs - Original configs saved before modification
- β Console Fallback - Password auth still works via console
# 1. Check service status
vm-security status
# 2. Verify firewall rules
sudo ufw status verbose
# 3. Test from external machine
nmap -p 1-1000 YOUR_VM_IP
# Should only show allowed ports (SSH, 80, 443)
# 4. Test fail2ban (from another IP)
ssh wrong_user@YOUR_VM_IP
# Enter wrong password 5+ times
# 5. Verify ban
sudo fail2ban-client status sshd
# Should show the attacking IP in banned list
# 6. Check audit logs
sudo ausearch -i --start recent | head -20
# 7. Test SSH key auth
ssh -v -p 22 your_user@YOUR_VM_IP
# Should login without password prompt# SSH configuration syntax
sudo sshd -T
# fail2ban status
sudo fail2ban-client ping
sudo fail2ban-client status
# Audit daemon
sudo auditctl -l
# AIDE database
sudo aide --config=/etc/aide/aide.conf --check
# System logs
sudo journalctl -xe | tail -50- Quick setup for testing environments
- Automatic security without manual config
- Easy port management for dev servers
- SOC2/ISO compliance ready
- 365-day audit trail
- Real-time intrusion detection
- Secure build environments
- Audit logging for compliance
- Prevents lateral movement
- Multi-tenant security isolation
- Comprehensive logging for disputes
- Professional security posture
Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/improvement) - Test thoroughly on Ubuntu 24.04
- Submit pull request with clear description
- Fresh Ubuntu 24.04 VM
- Setup completes without errors
- SSH access works after setup
- fail2ban bans working
- All status checks pass
- Logs viewer functional
- Reapply works without issues
MIT License - See LICENSE file for details.
- Access via Console (VPS control panel, not SSH)
- Follow Troubleshooting (see above)
- Open GitHub Issue with:
- OS version (
lsb_release -a) - Command that failed
- Error messages
- Last 50 lines of
/root/security-setup-*.log
- OS version (
Open an issue with:
- Environment: OS version, RAM, vCPU
- Command: Exact command run
- Expected: What should happen
- Actual: What actually happened
- Logs: Relevant log excerpts
Suggest improvements via GitHub Issues. Include:
- Use case / problem statement
- Proposed solution
- Alternative approaches considered
Built with guidance from:
- CIS Ubuntu Benchmarks
- NIST Cybersecurity Framework
- DevSec Hardening Framework
- fail2ban Documentation
- SSH Key Management Best Practices
- Understanding fail2ban
- UFW Firewall Guide
- Linux Audit Framework
- Docker Security Best Practices
β‘ Secure your VM in 5 minutes. Test in 1. Deploy with confidence. β‘
Get Started β’ Documentation β’ Support