Skip to content

3. System Security

TL;DR: Create non-root user, secure SSH per CIS Benchmark, configure firewall, fail2ban, auditing, and integrity monitoring.

Estimated time: 30-40 minutes

Required level: Intermediate

Prerequisites

  • [ ] VPS created with Ubuntu 24.04 LTS
  • [ ] Root SSH access working
  • [ ] Ed25519 SSH key generated locally

Objectives

By the end of this section you will have:

  • Dedicated openclaw user without root privileges
  • Complete SSH hardening per CIS Benchmark Ubuntu 24.04 L1
  • UFW firewall with deny-all policy
  • Fail2ban protecting against brute-force
  • Automatic security updates
  • Critical event auditing (auditd)
  • File integrity monitoring (AIDE)

Quick setup (automated script)

Skip the manual steps

If you provisioned with cloud-init (Section 2) and want to automate Sections 3 and 4, you can use the hardening script. It performs all steps below plus Tailscale installation and SSH lockdown in a single command.

Before running the script:

  1. Ensure cloud-init has finished: cloud-init status --wait
  2. Change your temporary password: sudo passwd openclaw
  3. Configure the Tailscale ACLs in the admin panel (see Section 4 for the JSON)

Run the script:

# Download and review first (recommended)
curl -fsSL -o /tmp/harden.sh \
  https://raw.githubusercontent.com/corbat-tech/corbat-openclaw-hardening/main/scripts/harden.sh
less /tmp/harden.sh
sudo bash /tmp/harden.sh

The script will pause to show a Tailscale authentication URL — open it in your browser to authorize.

After the script completes:

  1. From your Mac, connect via Tailscale: ssh openclaw@<TAILSCALE_IP>
  2. Remove the inbound SSH rule (port 22) from the Hetzner Cloud Firewall
  3. Disable key expiry for the VPS in Tailscale admin (... menu → Disable key expiry)
  4. Run the verification script: curl -fsSL https://raw.githubusercontent.com/corbat-tech/corbat-openclaw-hardening/main/scripts/verify-hardening.sh | sudo bash

If you prefer to understand each step, continue with the manual instructions below.


Create dedicated user

Never run OpenClaw as root

An AI agent with root access can completely compromise the system.

# Create 'openclaw' user with home directory
adduser openclaw

It will ask for:

  • Password: use a strong one (minimum 16 characters)
  • Full name, etc.: you can leave blank (Enter)
# Give sudo permissions (needed for initial setup)
usermod -aG sudo openclaw

Verify creation

# Verify user exists
id openclaw

Expected output:

uid=1001(openclaw) gid=1001(openclaw) groups=1001(openclaw),27(sudo)


Configure SSH for the new user

Copy SSH key to user

# Create .ssh directory for user
mkdir -p /home/openclaw/.ssh

# Copy authorized keys
cp /root/.ssh/authorized_keys /home/openclaw/.ssh/

# Adjust permissions (CRITICAL for security)
chown -R openclaw:openclaw /home/openclaw/.ssh
chmod 700 /home/openclaw/.ssh
chmod 600 /home/openclaw/.ssh/authorized_keys

Verify access before continuing

Don't proceed without testing this

Open a new terminal (without closing the current one) and test:

# From your local machine, in a NEW terminal
ssh openclaw@<YOUR_PUBLIC_IP>

If it works, continue. If not, review permissions before proceeding.


Secure SSH (CIS Benchmark 5.2 complete)

This configuration complies with CIS Benchmark Ubuntu 24.04 LTS Level 1 for SSH.

Create backup of original configuration

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup.$(date +%Y%m%d)

Create hardened configuration

Avoid copy-paste issues with long config files

The SSH hardening config contains long lines that can break when pasted into a terminal. Use one of these methods:

Method 1 — Download directly from this repository (recommended):

sudo curl -o /etc/ssh/sshd_config.d/99-openclaw-hardening.conf \
  https://raw.githubusercontent.com/corbat-tech/corbat-openclaw-hardening/main/scripts/99-openclaw-hardening.conf

Method 2 — Use nano to edit manually:

sudo nano /etc/ssh/sshd_config.d/99-openclaw-hardening.conf
Then copy the content from the config file in the repository.

The configuration file (scripts/99-openclaw-hardening.conf) contains all CIS Benchmark 5.2 controls:

Click to see the full SSH hardening configuration
# --- 5.2.4 - Secure ciphers ---
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr

# --- 5.2.5 - Secure MACs ---
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256

# --- 5.2.6 - Secure Key Exchange (post-quantum) ---
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256

# --- 5.2.7-23 - Authentication and access ---
Banner /etc/issue.net
LogLevel VERBOSE
X11Forwarding no
MaxAuthTries 4
IgnoreRhosts yes
HostbasedAuthentication no
PermitRootLogin no
PermitEmptyPasswords no
PermitUserEnvironment no
ClientAliveInterval 300
ClientAliveCountMax 3
LoginGraceTime 60
AllowUsers openclaw
MaxStartups 10:30:60
MaxSessions 10
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowTcpForwarding local
AllowAgentForwarding no
PermitTunnel no
UsePAM yes
StrictModes yes
sudo tee /etc/issue.net << 'EOF'
***************************************************************************
                            PRIVATE SYSTEM

  Unauthorized access is prohibited. All activities are monitored and
  logged. Use of this system implies acceptance of security policies.
***************************************************************************
EOF

Apply CIS permissions

# 5.2.1 - sshd_config permissions
sudo chmod 600 /etc/ssh/sshd_config
sudo chown root:root /etc/ssh/sshd_config

# 5.2.2 - Host private key permissions
sudo chmod 600 /etc/ssh/ssh_host_*_key
sudo chown root:root /etc/ssh/ssh_host_*_key

# 5.2.3 - Host public key permissions
sudo chmod 644 /etc/ssh/ssh_host_*_key.pub
sudo chown root:root /etc/ssh/ssh_host_*_key.pub

# Hardening config file permissions
sudo chmod 600 /etc/ssh/sshd_config.d/99-openclaw-hardening.conf
sudo chown root:root /etc/ssh/sshd_config.d/99-openclaw-hardening.conf

Verify configuration before applying

CRITICAL: Verify syntax before restarting

A syntax error can lock you out of SSH.

# Verify syntax
sudo sshd -t

Expected output: no output (silence = success)

If there are errors, fix them before continuing.

# If no errors, restart SSH
sudo systemctl restart ssh

# Verify it's running
sudo systemctl status ssh

Verify applied controls

# CIS SSH verification script
echo "=== CIS SSH Verification ==="
echo -n "PasswordAuthentication: "
sudo sshd -T | grep -i "^passwordauthentication"
echo -n "PermitRootLogin: "
sudo sshd -T | grep -i "^permitrootlogin"
echo -n "MaxAuthTries: "
sudo sshd -T | grep -i "^maxauthtries"
echo -n "X11Forwarding: "
sudo sshd -T | grep -i "^x11forwarding"
echo -n "LogLevel: "
sudo sshd -T | grep -i "^loglevel"
echo -n "AllowUsers: "
sudo sshd -T | grep -i "^allowusers"
echo -n "Ciphers: "
sudo sshd -T | grep -i "^ciphers"

Expected output:

=== CIS SSH Verification ===
PasswordAuthentication: passwordauthentication no
PermitRootLogin: permitrootlogin no
MaxAuthTries: maxauthtries 4
X11Forwarding: x11forwarding no
LogLevel: loglevel VERBOSE
AllowUsers: allowusers openclaw
Ciphers: ciphers chacha20-poly1305@openssh.com,...

Verify access with new configuration

Keep your current session open

Don't close your session until you verify new access.

In a new terminal:

# This should work
ssh openclaw@<YOUR_PUBLIC_IP>

# This should FAIL (root disabled)
ssh root@<YOUR_PUBLIC_IP>
# Expected: Permission denied (publickey)

Configure firewall (UFW)

# Configure default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (temporary, will be removed after Tailscale)
sudo ufw allow ssh

# Enable firewall
sudo ufw enable
# Confirm with 'y'

Verify firewall

sudo ufw status verbose

Expected output:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)


Fail2ban (brute-force protection)

Fail2ban bans IPs that attempt multiple failed logins.

Verified version

This configuration was tested with fail2ban 1.0.2 on Ubuntu 24.04 LTS. Check your version with: fail2ban-client --version

# Install
sudo apt install -y fail2ban

# Create local configuration (won't be overwritten in updates)
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
# Ban for 1 hour
bantime = 3600
# Time window to count failures
findtime = 600
# Maximum failures before ban
maxretry = 5
# Use systemd for logs
backend = systemd

[sshd]
enabled = true
port = ssh
filter = sshd
# No logpath needed: systemd backend (set in [DEFAULT]) reads from journald
maxretry = 3
bantime = 86400
EOF

# Enable and start
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Verify fail2ban

sudo fail2ban-client status sshd

Expected output:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:


Alternative: CrowdSec (collaborative protection)

CrowdSec vs Fail2Ban

CrowdSec is a modern alternative to Fail2Ban with community threat intelligence.

Feature Fail2Ban CrowdSec
Resources Very low Moderate
Community intel No Yes (shared blocklists)
Detection Reactive (log-based) Proactive (behavioral)
Best for Simple VPS, low traffic Production, multi-server
nftables integration Via actions Native

For a personal VPS with OpenClaw, Fail2Ban is sufficient. Consider CrowdSec if you plan to scale or need proactive protection.

Install CrowdSec (optional, alternative to Fail2Ban)

# Download and inspect the install script first
curl -sO https://install.crowdsec.net/install.sh
less install.sh  # Review the script
sudo bash install.sh
rm install.sh

# Install CrowdSec
sudo apt install -y crowdsec crowdsec-firewall-bouncer-nftables

# Verify installation
sudo cscli version

# View active decisions (blocked IPs)
sudo cscli decisions list

# View alerts
sudo cscli alerts list

Do not use Fail2Ban and CrowdSec simultaneously for SSH

Choose one or the other to avoid conflicts. If you install CrowdSec, disable the SSH jail in Fail2Ban.


Automatic security updates

# Install
sudo apt install -y unattended-upgrades

# Configure for automatic security updates
sudo dpkg-reconfigure -plow unattended-upgrades
# Select "Yes"

Verify configuration

cat /etc/apt/apt.conf.d/20auto-upgrades

Expected output:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";


Configure auditing (auditd)

The audit system records critical security events.

Verified version

This configuration was tested with auditd 3.1.2 on Ubuntu 24.04 LTS. Check your version with: auditctl -v

# Install auditd
sudo apt install -y auditd audispd-plugins

# Enable service
sudo systemctl enable auditd
sudo systemctl start auditd

Create audit rules

sudo tee /etc/audit/rules.d/openclaw.rules << 'EOF'
# ============================================================
# Audit Rules for OpenClaw VPS
# ============================================================

# Remove previous rules
-D

# Audit buffer
-b 8192

# What to do if buffer fills (0=silence, 1=printk, 2=panic)
-f 1

# --- Monitor critical system files ---
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/ssh/sshd_config.d -p wa -k sshd_config
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/group -p wa -k group_changes
-w /etc/sudoers -p wa -k sudoers_changes
-w /etc/sudoers.d -p wa -k sudoers_changes

# --- Monitor SSH keys ---
-w /home/openclaw/.ssh -p wa -k ssh_keys
-w /root/.ssh -p wa -k ssh_keys

# --- Monitor OpenClaw ---
-w /home/openclaw/openclaw -p wa -k openclaw_changes
-w /home/openclaw/.openclaw/.env -p r -k env_access

# --- Monitor sudo commands ---
-w /var/log/sudo.log -p wa -k sudo_log

# --- Monitor service changes ---
-w /etc/systemd/system -p wa -k systemd_changes
-w /lib/systemd/system -p wa -k systemd_changes

# --- Monitor logins ---
-w /var/log/lastlog -p wa -k logins
-w /var/log/faillog -p wa -k logins

# --- Make rules immutable (requires reboot to change) ---
-e 2
EOF

Load rules

# Reload rules
sudo augenrules --load

# Restart service
sudo systemctl restart auditd

Verify auditing

# View active rules
sudo auditctl -l

Expected output: List of -w rules configured

# View recent events (may be empty initially)
sudo ausearch -ts recent

Integrity monitoring (AIDE)

AIDE (Advanced Intrusion Detection Environment) detects unauthorized file changes.

Verified version

This configuration was tested with AIDE 0.18.6 on Ubuntu 24.04 LTS. Check your version with: aide --version

# Install AIDE
sudo apt install -y aide aide-common

Configure paths to monitor

sudo tee /etc/aide/aide.conf.d/99-openclaw << 'EOF'
# Critical OpenClaw files
/home/openclaw/openclaw/config CONTENT_EX
/home/openclaw/.openclaw/.env PERMS
/home/openclaw/.ssh CONTENT_EX

# Critical system files
/etc/ssh CONTENT_EX
/etc/passwd CONTENT_EX
/etc/shadow PERMS
/etc/sudoers CONTENT_EX
EOF

Initialize database

This process takes several minutes

AIDE scans the entire system to create the initial database.

# Initialize database (may take 5-10 minutes)
sudo aideinit
# Move database to active location
sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Verify integrity

# Run verification (run periodically or via cron)
sudo aide --check

Expected output (if no changes):

AIDE found NO differences between database and filesystem. Looks okay!!

Schedule automatic verification

# Create cron job for daily verification
sudo tee /etc/cron.daily/aide-check << 'EOF'
#!/bin/bash
/usr/bin/aide --check > /var/log/aide-check.log 2>&1
if [ $? -ne 0 ]; then
    echo "AIDE detected changes - review /var/log/aide-check.log" | logger -t aide
fi
EOF

sudo chmod +x /etc/cron.daily/aide-check

Kernel hardening (sysctl)

Configure kernel parameters for enhanced network security.

Create security configuration

sudo tee /etc/sysctl.d/99-security-hardening.conf << 'EOF'
# ============================================================
# Kernel Security Hardening
# Reference: CIS Benchmark Ubuntu 24.04 - Section 3.2
# ============================================================

# --- Disable IP forwarding (3.2.1) ---
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0

# --- Disable sending redirects (3.2.2) ---
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# --- Don't accept source routing (3.2.3) ---
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

# --- Don't accept ICMP redirects (3.2.4) ---
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

# --- Don't accept secure ICMP redirects (3.2.5) ---
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0

# --- Log suspicious packets (3.2.6) ---
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1

# --- Ignore broadcast ICMP (3.2.7) ---
net.ipv4.icmp_echo_ignore_broadcasts = 1

# --- Ignore bogus ICMP responses (3.2.8) ---
net.ipv4.icmp_ignore_bogus_error_responses = 1

# --- Enable reverse path filtering (3.2.9) ---
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# --- Enable TCP SYN Cookies (3.2.10) ---
net.ipv4.tcp_syncookies = 1

# --- Don't accept IPv6 router advertisements (3.2.11) ---
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
EOF

Tailscale compatibility

If you plan to use this VPS as a Tailscale subnet router or exit node, change ip_forward to 1. For a standard OpenClaw deployment (the scope of this guide), 0 is correct.

Apply configuration

# Apply immediately
sudo sysctl -p /etc/sysctl.d/99-security-hardening.conf

# Verify it was applied
sudo sysctl net.ipv4.ip_forward
sudo sysctl net.ipv4.conf.all.send_redirects

Expected output:

net.ipv4.ip_forward = 0
net.ipv4.conf.all.send_redirects = 0

Final security verification

Run this script to verify all controls:

echo "============================================"
echo "SYSTEM SECURITY VERIFICATION"
echo "============================================"
echo ""

echo "--- openclaw user ---"
id openclaw && echo "✅ User exists" || echo "❌ User does NOT exist"
echo ""

echo "--- SSH Hardening ---"
sudo sshd -T 2>/dev/null | grep -q "passwordauthentication no" && echo "✅ Password auth disabled" || echo "❌ Password auth ACTIVE"
sudo sshd -T 2>/dev/null | grep -q "permitrootlogin no" && echo "✅ Root login disabled" || echo "❌ Root login ACTIVE"
sudo sshd -T 2>/dev/null | grep -q "allowusers openclaw" && echo "✅ AllowUsers configured" || echo "❌ AllowUsers NOT configured"
echo ""

echo "--- Firewall ---"
sudo ufw status | grep -q "Status: active" && echo "✅ UFW active" || echo "❌ UFW NOT active"
echo ""

echo "--- Fail2ban ---"
systemctl is-active fail2ban >/dev/null && echo "✅ Fail2ban active" || echo "❌ Fail2ban NOT active"
echo ""

echo "--- Unattended Upgrades ---"
systemctl is-active unattended-upgrades >/dev/null && echo "✅ Auto-updates active" || echo "❌ Auto-updates NOT active"
echo ""

echo "--- Auditd ---"
systemctl is-active auditd >/dev/null && echo "✅ Auditd active" || echo "❌ Auditd NOT active"
sudo auditctl -l | grep -q "openclaw" && echo "✅ OpenClaw rules loaded" || echo "❌ OpenClaw rules NOT loaded"
echo ""

echo "--- AIDE ---"
[ -f /var/lib/aide/aide.db ] && echo "✅ AIDE initialized" || echo "❌ AIDE NOT initialized"
echo ""

echo "============================================"
echo "Verification completed"
echo "============================================"

Troubleshooting

Error: "Permission denied (publickey)"

Cause: The SSH key is not correctly configured for the user.

Solution:

# Verify permissions
ls -la /home/openclaw/.ssh/
# authorized_keys must have permissions 600
# .ssh must have permissions 700

# Fix if necessary
sudo chmod 700 /home/openclaw/.ssh
sudo chmod 600 /home/openclaw/.ssh/authorized_keys
sudo chown -R openclaw:openclaw /home/openclaw/.ssh

Error: "Connection refused" after restarting SSH

Cause: Syntax error in SSH configuration.

Solution:

# If you have another session open, check the error
sudo journalctl -u sshd -n 50

# Restore backup
sudo cp /etc/ssh/sshd_config.backup.* /etc/ssh/sshd_config
sudo rm /etc/ssh/sshd_config.d/99-openclaw-hardening.conf
sudo systemctl restart ssh

Error: "Too many authentication failures"

Cause: You've been banned by fail2ban.

Solution:

# View banned IPs
sudo fail2ban-client status sshd

# Unban your IP
sudo fail2ban-client set sshd unbanip <YOUR_IP>


Summary of completed work

Configuration Expected status CIS Reference
User openclaw created ✅ -
SSH with public key ✅ 5.2.23
Password login disabled ✅ 5.2.22
Root login disabled ✅ 5.2.13
Secure ciphers ✅ 5.2.4
Secure MACs ✅ 5.2.5
MaxAuthTries = 4 ✅ 5.2.10
Banner configured ✅ 5.2.7
Firewall active (deny all) ✅ -
Fail2ban active ✅ -
Automatic updates ✅ -
Auditd configured ✅ -
AIDE initialized ✅ -
SSH allowed (temporary) ✅ -

Public SSH port is temporary

In the next step we'll configure Tailscale and remove public SSH access.


Remove sudo from openclaw user

Principle of least privilege

The openclaw user was added to the sudo group to perform the initial setup in sections 3-5. Once all setup is complete (including Tailscale in section 4 and OpenClaw in section 5), sudo access should be removed. An AI agent user with permanent sudo is a critical risk — any compromise of the agent grants full root access.

After completing all setup steps in sections 3 through 5, remove sudo access from the openclaw user:

# Run this from a separate root or admin session, NOT as the openclaw user
sudo deluser openclaw sudo

Verify the change:

id openclaw

Expected output (no sudo group):

uid=1001(openclaw) gid=1001(openclaw) groups=1001(openclaw)

Keep a recovery session

Before removing sudo, make sure you have another way to administer the system (e.g., root access via the VPS provider's console or another admin user with sudo). If you need to perform administrative tasks later, you can temporarily re-add sudo access from that recovery session.


Next: 4. Private Access (Tailscale) — Configure VPN and remove public access.