A practical, opinionated guide to running an autonomous AI assistant on a dedicated home lab server — with proper isolation, network control, and custom tooling.
Goals
You want a dedicated, always-on Linux machine that runs an AI agent (OpenClaw) with the following properties:
Isolated execution environment — The agent runs under a dedicated Linux user (
agent-openclaw) with strict permission boundaries. It cannot escalate to root, cannot access other users’ data, and operates within well-defined filesystem and process boundaries.Custom CLI tool integration — Your own GoLang tools (Sky, PowerCtl) are available to the agent, authenticated via a mix of environment variables and Bitwarden CLI-fetched secrets, injected at runtime without persisting tokens on disk in plaintext.
Network-level control via Tailscale — The agent machine joins your tailnet but can only reach specific services (n8n, Bitwarden, Obsidian vault shares) and specific external API domains. All other network traffic is denied by default. You have full visibility and control over what the agent can access.
Hardened OS — The Ubuntu Server 24.04 LTS installation follows security best practices: minimal attack surface, automatic security updates, audit logging, and strict firewall rules.
OpenClaw as the runtime — The agent uses OpenClaw’s Gateway, channels, and skills platform. It runs as a systemd user service, restarts on failure, and exposes its Gateway only over Tailscale.
Steps Overview
- Base Ubuntu Server Installation & Hardening
- User Accounts & Isolation Model
- Tailscale Network Control
- Secrets Management
- Installing Custom CLI Tools
- Installing OpenClaw
- systemd Service Configuration
- AppArmor Profile (Optional but Recommended)
- Operational Checklist
Part 1: Base Ubuntu Server Installation & Hardening
1.1 — Minimal Installation
Start with a Ubuntu Server 24.04 LTS minimal installation. During install:
- Choose “Ubuntu Server (minimized)” if offered
- Set up LVM with encryption (LUKS) for the root partition — this protects data at rest if the physical machine is stolen
- Create a single admin user (e.g.,
kristoffer) — this is your human operator account, not the agent - Enable OpenSSH server during install
After first boot, update everything:
sudo apt update && sudo apt upgrade -y
sudo apt install -y unattended-upgrades apt-listchanges
1.2 — Enable Automatic Security Updates
Configure unattended-upgrades to automatically apply security patches:
sudo dpkg-reconfigure -plow unattended-upgrades
Verify the configuration:
cat /etc/apt/apt.conf.d/20auto-upgrades
Should contain:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
Edit /etc/apt/apt.conf.d/50unattended-upgrades to enable automatic reboots if needed (for kernel updates):
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
1.3 — SSH Hardening
Edit /etc/ssh/sshd_config:
sudo tee /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
# Disable password auth — keys only
PasswordAuthentication no
ChallengeResponseAuthentication no
# Disable root login
PermitRootLogin no
# Limit to your admin user only
AllowUsers kristoffer
# Reduce attack surface
X11Forwarding no
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
EOF
sudo systemctl restart sshd
Make sure your SSH key is in ~kristoffer/.ssh/authorized_keys before disabling password auth.
1.4 — Firewall with UFW
Set up a strict firewall. The agent machine should only be reachable via Tailscale, not on the LAN directly:
sudo ufw default deny incoming
sudo ufw default deny outgoing
# Allow SSH from LAN as fallback (restrict to your subnet)
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
# Allow Tailscale interface (all traffic over tailnet is controlled by ACLs)
sudo ufw allow in on tailscale0
sudo ufw allow out on tailscale0
# Allow DNS resolution
sudo ufw allow out 53/udp
sudo ufw allow out 53/tcp
# Allow HTTPS out (needed for API calls, package management)
sudo ufw allow out 443/tcp
# Allow HTTP out (some package repos)
sudo ufw allow out 80/tcp
# Allow Tailscale's own UDP traffic (WireGuard)
sudo ufw allow out 41641/udp
sudo ufw enable
Note: We allow outgoing HTTPS/HTTP here at the OS level but will use Tailscale ACLs to control which hosts the agent can actually reach. UFW is the coarse filter; Tailscale ACLs are the fine-grained one.
1.5 — Install Essential Packages
sudo apt install -y \
build-essential \
curl \
git \
jq \
tmux \
htop \
auditd \
fail2ban \
apparmor \
apparmor-utils \
acl \
rsync
1.6 — Enable Audit Logging
The audit daemon records system calls and can track what the agent user does:
sudo systemctl enable auditd
sudo systemctl start auditd
# Add rules to monitor the agent user's activities
sudo tee /etc/audit/rules.d/agent-openclaw.rules << 'EOF'
# Monitor all commands executed by the agent user (UID will be set after user creation)
# We'll update this after creating the agent user
-a always,exit -F arch=b64 -S execve -F uid=2001 -k agent-exec
-a always,exit -F arch=b64 -S openat -F uid=2001 -F dir=/etc -k agent-etc-access
-a always,exit -F arch=b64 -S openat -F uid=2001 -F dir=/home/kristoffer -k agent-home-snoop
EOF
1.7 — Configure fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Default config protects SSH. Good enough for a home lab.
Part 2: User Accounts & Isolation Model
The core principle: the agent runs as a dedicated, unprivileged Linux user with no sudo access and carefully controlled filesystem permissions.
2.1 — Create the Agent User
# Create a system group for agent users
sudo groupadd --gid 2000 agents
# Create the dedicated agent user
sudo useradd \
--uid 2001 \
--gid agents \
--create-home \
--home-dir /home/agent-openclaw \
--shell /bin/bash \
--comment "OpenClaw AI Agent" \
agent-openclaw
# Lock the password — no direct login, only su/sudo from admin
sudo passwd -l agent-openclaw
Now update the audit rules with the correct UID:
sudo augenrules --load
2.2 — Directory Structure
Create a clean workspace layout for the agent:
# Agent home structure
sudo -u agent-openclaw mkdir -p /home/agent-openclaw/{.config,.local/bin,workspace,tools,secrets}
# The .openclaw directory (OpenClaw's data)
sudo -u agent-openclaw mkdir -p /home/agent-openclaw/.openclaw/{workspace/skills,credentials}
# Lock down permissions — agent owns their home, nobody else reads it
sudo chmod 750 /home/agent-openclaw
sudo chmod 700 /home/agent-openclaw/secrets
2.3 — Restrict Agent Filesystem Access
Use filesystem permissions and ACLs to ensure the agent user cannot wander:
# Agent cannot read other users' homes
sudo chmod 750 /home/kristoffer
# Agent cannot write to system directories (already the case by default, but verify)
# Agent cannot access /root
sudo chmod 700 /root
# Create a shared directory for files you want the agent to access
sudo mkdir -p /srv/agent-shared
sudo chown kristoffer:agents /srv/agent-shared
sudo chmod 2770 /srv/agent-shared # setgid so new files inherit group
2.4 — Restrict Process Visibility
Prevent the agent user from seeing other users’ processes:
# Mount /proc with hidepid
echo "proc /proc proc defaults,hidepid=2,gid=$(getent group agents | cut -d: -f3) 0 0" | \
sudo tee -a /etc/fstab
# Apply now
sudo mount -o remount,hidepid=2,gid=2000 /proc
With hidepid=2, the agent-openclaw user can only see its own processes, not yours or other services'.
2.5 — Resource Limits with systemd and cgroups
Prevent the agent from consuming all system resources. We’ll configure this later when we set up the systemd service, but the key limits will be:
- Memory: Capped at e.g. 4GB (adjust for your hardware)
- CPU: Limited to specific cores or percentage
- Max open files: Reasonable limit
- No ability to create new user namespaces (prevents container escapes)
Part 3: Tailscale Network Control
This is where you get surgical control over what the agent machine can talk to.
3.1 — Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
# Start tailscale and authenticate
sudo tailscale up --ssh
3.2 — Tag the Agent Machine
In your Tailscale admin console (https://login.tailscale.com/admin/acls), you’ll tag this machine. Tags are the foundation of ACL control.
First, register the tag in your ACL policy:
{
"tagOwners": {
"tag:agent": ["autogroup:admin"],
"tag:homelab": ["autogroup:admin"],
"tag:services": ["autogroup:admin"],
},
}
Apply the tag to the agent machine:
sudo tailscale up --advertise-tags=tag:agent
Tag your other machines appropriately:
- Your n8n server →
tag:services - Your Bitwarden/Vaultwarden instance →
tag:services - Your NAS/Obsidian file server →
tag:homelab - Your personal machines → no tag (they’re your user identity)
3.3 — Tailscale ACL Policy
This is the core of your network control. The ACL policy defines exactly what the agent machine can reach:
{
"tagOwners": {
"tag:agent": ["autogroup:admin"],
"tag:homelab": ["autogroup:admin"],
"tag:services": ["autogroup:admin"],
},
"acls": [
// Your personal machines can reach everything
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["*:*"],
},
// Agent can reach specific services on your tailnet
{
"action": "accept",
"src": ["tag:agent"],
"dst": [
"tag:services:8080", // n8n webhook port
"tag:services:443", // Vaultwarden/Bitwarden HTTPS
"tag:homelab:445", // SMB for Obsidian vault
"tag:homelab:22", // SSH/rsync for file access
],
},
// Agent can reach the OpenClaw Gateway on itself (loopback via tailscale)
{
"action": "accept",
"src": ["tag:agent"],
"dst": ["tag:agent:18789"],
},
// Services can talk back to agent (for webhooks from n8n, etc.)
{
"action": "accept",
"src": ["tag:services"],
"dst": ["tag:agent:18789"],
},
],
// DNS configuration — control what domains the agent can resolve
"dns": {
"nameservers": ["100.100.100.100"], // Tailscale's MagicDNS
"domains": ["your-tailnet.ts.net"],
"magicDNS": true,
},
}
3.4 — Controlling Internet Access (Exit Node Pattern)
Tailscale ACLs control tailnet traffic, but what about the agent reaching external APIs (Anthropic, OpenAI, GitHub, etc.)? There are two approaches:
Option A: Allow outbound HTTPS at the OS level (simpler)
You already have UFW allowing outbound 443. The agent process can reach external APIs directly. This is the simplest approach and works well if you trust the agent not to exfiltrate data to random domains.
Option B: Proxy through an exit node with DNS filtering (more control)
Set up a machine on your tailnet as an exit node with DNS-level filtering (e.g., Pi-hole or NextDNS):
# On your DNS/proxy machine
sudo tailscale up --advertise-exit-node --advertise-tags=tag:services
# On the agent machine — route all traffic through the exit node
sudo tailscale up --exit-node=<exit-node-ip> --advertise-tags=tag:agent
Then configure Pi-hole/NextDNS to only allow specific domains:
api.anthropic.comapi.openai.comgithub.comregistry.npmjs.orgpypi.org- Your custom API domains
This guide assumes Option A as the starting point. You can layer on Option B later.
3.5 — Mount Obsidian Vault via Tailscale
Since the agent needs access to your Obsidian vault on another machine, set up an SMB or NFS mount over Tailscale:
# Install CIFS utilities
sudo apt install -y cifs-utils
# Create mount point
sudo mkdir -p /mnt/obsidian-vault
# Create a credentials file (readable only by root)
sudo tee /etc/samba/.obsidian-creds << 'EOF'
username=your-smb-user
password=your-smb-password
EOF
sudo chmod 600 /etc/samba/.obsidian-creds
# Add fstab entry — mounts over Tailscale IP, accessible by agent user
echo "//your-nas.your-tailnet.ts.net/obsidian /mnt/obsidian-vault cifs credentials=/etc/samba/.obsidian-creds,uid=agent-openclaw,gid=agents,file_mode=0640,dir_mode=0750,nofail 0 0" | \
sudo tee -a /etc/fstab
sudo mount -a
Now the agent can read the vault at /mnt/obsidian-vault but the mount credentials are stored securely.
Part 4: Secrets Management
Your GoLang tools (Sky, PowerCtl) use a mix of environment variables and Bitwarden CLI for authentication. Here’s how to set this up securely for the agent.
4.1 — Install Bitwarden CLI
# Install as the agent user
sudo -u agent-openclaw bash << 'SETUP'
cd /home/agent-openclaw
curl -fsSL "https://vault.bitwarden.com/download/?app=cli&platform=linux" -o bw.zip
unzip bw.zip -d .local/bin/
rm bw.zip
chmod +x .local/bin/bw
SETUP
4.2 — Bitwarden Session Management
The agent should never store the Bitwarden master password on disk. Instead, use a systemd-managed session that unlocks at service start via a pre-stored API key or session token.
Create a wrapper script that the agent’s tools will use to fetch secrets:
sudo -u agent-openclaw tee /home/agent-openclaw/tools/bw-get-secret.sh << 'SCRIPT'
#!/usr/bin/env bash
# Fetch a secret from Bitwarden by item name and field
# Usage: bw-get-secret.sh "item-name" [field-name]
# If field-name is omitted, returns the password field
set -euo pipefail
ITEM_NAME="$1"
FIELD_NAME="${2:-password}"
if [ -z "${BW_SESSION:-}" ]; then
echo "ERROR: BW_SESSION not set. Bitwarden is not unlocked." >&2
exit 1
fi
if [ "$FIELD_NAME" = "password" ]; then
bw get password "$ITEM_NAME" --session "$BW_SESSION"
elif [ "$FIELD_NAME" = "username" ]; then
bw get username "$ITEM_NAME" --session "$BW_SESSION"
else
bw get item "$ITEM_NAME" --session "$BW_SESSION" | jq -r ".fields[] | select(.name==\"$FIELD_NAME\") | .value"
fi
SCRIPT
sudo chmod 750 /home/agent-openclaw/tools/bw-get-secret.sh
4.3 — Environment File for Static Secrets
For environment variables that don’t change often, use a systemd EnvironmentFile that is readable only by root and the agent:
# Create the directory first
sudo mkdir -p /etc/openclaw
# Then create the environment file
sudo tee /etc/openclaw/agent.env << 'EOF'
# OpenClaw configuration
ANTHROPIC_API_KEY=sk-ant-xxxxx
OPENCLAW_GATEWAY_PORT=18789
# Static tool tokens (rotate periodically)
SKY_API_ENDPOINT=https://your-api.example.com
POWERCTL_CONFIG_PATH=/home/agent-openclaw/.config/powerctl/config.yaml
# Bitwarden API key for automatic unlock
BW_CLIENTID=user.xxxxx
BW_CLIENTSECRET=xxxxx
EOF
# Set secure permissions
sudo chmod 600 /etc/openclaw/agent.env
sudo chown root:agents /etc/openclaw/agent.env
sudo chmod 640 /etc/openclaw/agent.env
4.4 — Runtime Secret Injection Script
Create a startup script that unlocks Bitwarden and populates dynamic secrets before OpenClaw starts:
sudo -u agent-openclaw tee /home/agent-openclaw/tools/agent-startup.sh << 'SCRIPT'
#!/usr/bin/env bash
# Runs before OpenClaw Gateway starts
# Unlocks Bitwarden and exports dynamic secrets
set -euo pipefail
export PATH="/home/agent-openclaw/.local/bin:$PATH"
# Login to Bitwarden using API key (set via EnvironmentFile)
if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then
bw login --apikey 2>/dev/null || true
export BW_SESSION=$(bw unlock --passwordenv BW_MASTER_PASSWORD --raw 2>/dev/null || echo "")
if [ -z "$BW_SESSION" ]; then
echo "WARNING: Could not unlock Bitwarden vault. Dynamic secrets unavailable." >&2
else
echo "Bitwarden vault unlocked successfully."
# Fetch dynamic secrets and export them
export SKY_ACCESS_TOKEN=$(bw get password "Sky CLI Token" --session "$BW_SESSION" 2>/dev/null || echo "")
export POWERCTL_TOKEN=$(bw get password "PowerCtl API Token" --session "$BW_SESSION" 2>/dev/null || echo "")
fi
fi
# Execute the actual command (OpenClaw gateway)
exec "$@"
SCRIPT
sudo chmod 750 /home/agent-openclaw/tools/agent-startup.sh
Part 5: Installing Custom CLI Tools
5.1 — Install Your GoLang Tools
Since Sky and PowerCtl are your own GoLang binaries, install them into the agent’s local bin:
# Copy pre-built binaries (adjust paths to where you build/distribute them)
sudo cp /path/to/sky /home/agent-openclaw/.local/bin/sky
sudo cp /path/to/powerctl /home/agent-openclaw/.local/bin/powerctl
sudo chown agent-openclaw:agents /home/agent-openclaw/.local/bin/{sky,powerctl}
sudo chmod 750 /home/agent-openclaw/.local/bin/{sky,powerctl}
Alternatively, if you want the agent machine to pull them from a Git repo or your own release server:
# If you publish releases on GitHub
sudo -u agent-openclaw bash << 'INSTALL'
cd /home/agent-openclaw/.local/bin
curl -fsSL https://github.com/your-org/sky/releases/latest/download/sky-linux-amd64 -o sky
curl -fsSL https://github.com/your-org/powerctl/releases/latest/download/powerctl-linux-amd64 -o powerctl
chmod +x sky powerctl
INSTALL
5.2 — Configure Tool Access for OpenClaw
OpenClaw needs to know about these tools. Add them to the agent’s workspace:
sudo -u agent-openclaw tee /home/agent-openclaw/.openclaw/workspace/TOOLS.md << 'TOOLSDOC'
# Available CLI Tools
## Sky
Location: `~/.local/bin/sky`
Purpose: [Your description of what Sky does]
Usage: `sky [command] [flags]`
Authentication: Uses $SKY_ACCESS_TOKEN environment variable.
## PowerCtl
Location: `~/.local/bin/powerctl`
Purpose: [Your description of what PowerCtl does]
Usage: `powerctl [command] [flags]`
Authentication: Uses $POWERCTL_TOKEN environment variable and config at $POWERCTL_CONFIG_PATH.
## Bitwarden CLI
Location: `~/.local/bin/bw`
Purpose: Fetch secrets from your Bitwarden vault.
Usage: `bw get password "item-name"` (requires $BW_SESSION to be set)
Note: Session is established at agent startup. Use `bw-get-secret.sh` wrapper for convenience.
TOOLSDOC
5.3 — PATH Configuration
Ensure the agent’s PATH includes the local bin:
sudo -u agent-openclaw tee -a /home/agent-openclaw/.bashrc << 'BASHRC'
# Agent tool paths
export PATH="$HOME/.local/bin:$PATH"
# Node.js (installed in Part 6)
export PATH="$HOME/.local/share/nvm/versions/node/v22/bin:$PATH"
BASHRC
Part 6: Installing OpenClaw
6.1 — Install Node.js 22
OpenClaw requires Node.js >= 22:
# Install nvm as the agent user
sudo -u agent-openclaw bash << 'NVMSETUP'
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"
nvm install 22
nvm use 22
nvm alias default 22
# Install pnpm globally
npm install -g pnpm
NVMSETUP
6.2 — Install OpenClaw
sudo -u agent-openclaw bash << 'CLAWSETUP'
export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"
# Install OpenClaw globally
npm install -g openclaw@latest
# Or from source for more control:
# git clone https://github.com/openclaw/openclaw.git ~/openclaw-src
# cd ~/openclaw-src
# pnpm install
# pnpm ui:build
# pnpm build
CLAWSETUP
6.3 — Configure OpenClaw
Create the minimal configuration:
sudo -u agent-openclaw mkdir -p /home/agent-openclaw/.openclaw
sudo -u agent-openclaw tee /home/agent-openclaw/.openclaw/openclaw.json << 'CONFIG'
{
"agent": {
"model": "anthropic/claude-opus-4-5"
},
"gateway": {
"port": 18789,
"bind": "loopback",
"tailscale": {
"mode": "serve"
},
"auth": {
"mode": "password",
"password": "CHANGE_ME_TO_SOMETHING_STRONG"
}
},
"channels": {
"telegram": {
"botToken": "YOUR_TELEGRAM_BOT_TOKEN"
}
},
"browser": {
"enabled": false
}
}
CONFIG
Key decisions here:
bind: "loopback"— Gateway only listens on 127.0.0.1. External access is via Tailscale Serve only.tailscale.mode: "serve"— Exposes the Gateway dashboard over your tailnet with HTTPS, but not to the public internet.auth.mode: "password"— Adds a password requirement even for tailnet access.browser.enabled: false— Disabled by default on a headless server. Enable if you install a headless Chrome later.
6.4 — Run the Onboarding Wizard
sudo -iu agent-openclaw openclaw onboard
Follow the wizard to set up your preferred channels (Telegram, Discord, etc.), identity, and workspace.
Part 7: systemd Service Configuration
7.1 — Create the Service Unit
sudo tee /etc/systemd/system/openclaw-agent.service << 'UNIT'
[Unit]
Description=OpenClaw AI Agent Gateway
After=network-online.target tailscaled.service
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
User=agent-openclaw
Group=agents
# Environment files
EnvironmentFile=/etc/openclaw/agent.env
# Use the startup script to inject dynamic secrets
ExecStart=/home/agent-openclaw/tools/agent-startup.sh \
/home/agent-openclaw/.nvm/versions/node/v22/bin/node \
/home/agent-openclaw/.nvm/versions/node/v22/bin/openclaw \
gateway --port 18789 --verbose
# Restart policy
Restart=on-failure
RestartSec=10
# Working directory
WorkingDirectory=/home/agent-openclaw
# Resource limits (adjust for your hardware)
MemoryMax=4G
MemoryHigh=3G
CPUQuota=200%
TasksMax=256
LimitNOFILE=8192
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=tmpfs
BindPaths=/home/agent-openclaw
BindReadOnlyPaths=/mnt/obsidian-vault
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true
SystemCallArchitectures=native
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=openclaw-agent
[Install]
WantedBy=multi-user.target
UNIT
This is a heavily hardened service configuration. Key security properties:
NoNewPrivileges=true— The agent process cannot gain additional privilegesProtectSystem=strict— The entire filesystem is read-only except explicitly allowed pathsProtectHome=tmpfs— All home directories are hidden, then we bind-mount only the agent’s homeBindPaths— Only the agent’s home is writableBindReadOnlyPaths— Obsidian vault is mounted read-onlyRestrictNamespaces=true— Cannot create user/network namespaces (prevents container escape patterns)MemoryMax=4G— Hard memory cap
7.2 — Enable and Start
sudo systemctl daemon-reload
sudo systemctl enable openclaw-agent.service
sudo systemctl start openclaw-agent.service
# Check status
sudo systemctl status openclaw-agent.service
sudo journalctl -u openclaw-agent.service -f
7.3 — Log Monitoring
Set up structured log access:
# View agent logs
sudo journalctl -u openclaw-agent.service --since "1 hour ago"
# View audit logs for the agent user
sudo ausearch -ua 2001 --start today
# Follow live
sudo journalctl -u openclaw-agent.service -f
Part 8: AppArmor Profile (Optional but Recommended)
For an additional layer of mandatory access control, create an AppArmor profile for the OpenClaw process:
sudo tee /etc/apparmor.d/openclaw-agent << 'APPARMOR'
#include <tunables/global>
profile openclaw-agent /home/agent-openclaw/.nvm/versions/node/*/bin/node {
#include <abstractions/base>
#include <abstractions/nameservice>
# Node.js and OpenClaw
/home/agent-openclaw/.nvm/** rix,
/home/agent-openclaw/.local/bin/** rix,
# Agent workspace (read/write)
/home/agent-openclaw/ r,
/home/agent-openclaw/** rw,
/home/agent-openclaw/.openclaw/** rw,
# Obsidian vault (read-only)
/mnt/obsidian-vault/ r,
/mnt/obsidian-vault/** r,
# Shared directory
/srv/agent-shared/** rw,
# Deny sensitive paths
deny /etc/shadow r,
deny /etc/passwd w,
deny /home/kristoffer/** rw,
deny /root/** rw,
# Network
network inet stream,
network inet dgram,
network inet6 stream,
network inet6 dgram,
# Temp
/tmp/** rw,
/var/tmp/** rw,
}
APPARMOR
sudo apparmor_parser -r /etc/apparmor.d/openclaw-agent
Part 9: Operational Checklist
Verification Steps
After completing the setup, verify each layer:
# 1. User isolation — agent cannot read your home
sudo -u agent-openclaw ls /home/kristoffer
# Should: Permission denied
# 2. Process isolation — agent cannot see your processes
sudo -u agent-openclaw ps aux
# Should: Only see agent-openclaw's own processes
# 3. Tailscale connectivity — agent can reach allowed services
sudo -u agent-openclaw curl -s https://your-n8n.your-tailnet.ts.net/healthz
# Should: 200 OK
# 4. Tailscale isolation — agent cannot reach random tailnet machines
sudo -u agent-openclaw curl -s https://your-desktop.your-tailnet.ts.net:22
# Should: Timeout/refused (if not in ACL)
# 5. Tools work — Sky and PowerCtl are available
sudo -u agent-openclaw /home/agent-openclaw/.local/bin/sky --version
sudo -u agent-openclaw /home/agent-openclaw/.local/bin/powerctl --version
# 6. OpenClaw is running
sudo systemctl status openclaw-agent.service
curl -s http://127.0.0.1:18789/health
# 7. Audit trail is recording
sudo ausearch -ua 2001 --start today -i | head -20
Maintenance Tasks
| Task | Frequency | Command |
|---|---|---|
| OS security updates | Daily (auto) | unattended-upgrades handles this |
| OpenClaw updates | Weekly/as needed | sudo -iu agent-openclaw npm update -g openclaw |
| Rotate API keys | Monthly | Update /etc/openclaw/agent.env and Bitwarden |
| Review audit logs | Weekly | sudo ausearch -ua 2001 --start recent -i |
| Check Tailscale ACLs | After changes | Tailscale admin console |
| Backup agent workspace | Weekly | rsync to your NAS |
| Update Sky/PowerCtl | As released | Replace binaries in .local/bin/ |
Backup the Agent State
# Cron job to backup the agent's workspace and config
sudo tee /etc/cron.d/openclaw-backup << 'CRON'
0 3 * * * root rsync -az --delete /home/agent-openclaw/.openclaw/ /srv/backups/openclaw/ 2>&1 | logger -t openclaw-backup
CRON
Architecture Summary
┌─────────────────────────────────────────────────────────────────┐
│ Your Tailnet (WireGuard) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ Your Desktop │ │ NAS/Files │ │ Services (n8n, BW) │ │
│ │ (personal) │ │ tag:homelab │ │ tag:services │ │
│ └──────┬───────┘ └──────┬───────┘ └───────────┬───────────┘ │
│ │ │ │ │
│ │ Tailscale ACLs control all traffic │ │
│ │ │ │ │
│ ┌──────┴─────────────────┴───────────────────────┴──────────┐ │
│ │ Agent Machine (tag:agent) │ │
│ │ Ubuntu 24.04 LTS — Hardened │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ user: agent-openclaw (uid 2001) │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────┐ ┌────────┐ ┌──────────────────┐ │ │ │
│ │ │ │ OpenClaw │ │ Sky │ │ PowerCtl │ │ │ │
│ │ │ │ Gateway │ │ CLI │ │ CLI │ │ │ │
│ │ │ │ :18789 │ │ │ │ │ │ │ │
│ │ │ └──────┬───────┘ └───┬────┘ └────────┬─────────┘ │ │ │
│ │ │ │ │ │ │ │ │
│ │ │ └──────────────┴─────────────────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ Secrets: env vars + Bitwarden CLI runtime fetch │ │ │
│ │ │ Filesystem: home + /mnt/obsidian-vault (ro) │ │ │
│ │ │ Resources: 4GB RAM, 200% CPU, 256 tasks max │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Security layers: │ │
│ │ ✓ UFW firewall (coarse) │ │
│ │ ✓ Tailscale ACLs (fine-grained) │ │
│ │ ✓ systemd sandboxing (ProtectSystem, NoNewPrivileges) │ │
│ │ ✓ AppArmor MAC profile │ │
│ │ ✓ hidepid=2 (process isolation) │ │
│ │ ✓ auditd (full command logging) │ │
│ │ ✓ LUKS disk encryption │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ External APIs (Anthropic, OpenAI, etc.) │
│ ← Allowed via UFW outbound HTTPS │
│ ← Optional: DNS filtering via exit node │
└─────────────────────────────────────────────────────────────────┘
What’s Next
Once this foundation is running, you can extend it:
- Add more agent users — Create
agent-codex,agent-research, etc., each with their own UID, Tailscale tag, and ACL rules - Enable OpenClaw browser control — Install headless Chromium and set
browser.enabled: truefor web automation - Set up OpenClaw skills — Build custom skills that wrap your Sky and PowerCtl tools with natural language interfaces
- Add n8n webhooks — Use OpenClaw’s webhook surface to receive triggers from your n8n automations
- DNS filtering — Deploy a Pi-hole exit node for granular domain-level control over the agent’s internet access
- Prometheus/Grafana monitoring — Monitor the agent’s resource usage, API costs, and activity patterns
The key insight of this architecture is defense in depth: no single layer is responsible for security. The agent is constrained by Linux user permissions, systemd sandboxing, AppArmor mandatory access control, Tailscale network ACLs, and OS-level firewalling — all working together. If any one layer has a gap, the others compensate.