Hands-on workshop guide. Every command is ready to copy and paste into your terminal.
Use this voucher when ordering your Netcup VPS to get β¬5 off your first order:
Click the code to copy it. Enter it at checkout on netcup.com.
Get a server, lock it down, install Docker
We'll use Netcup β great performance, German hosting, and excellent value. The VPS 500 G12 gives you 2 vCPU, 4 GB RAM, and 128 GB NVMe for β¬4.69/month (minus β¬5 with the voucher above!).
36nc17756369199 at checkout for β¬5 offIf your Netcup account isn't ready, use Hetzner Cloud β no approval needed, servers deploy in seconds.
# Generate an Ed25519 SSH key pair (press Enter through all prompts) ssh-keygen -t ed25519 -C "admin-workshop" # Display your PUBLIC key β copy this into your VPS provider's dashboard cat ~/.ssh/id_ed25519.pub
Open your terminal and connect to the server. Replace the IP with your server's actual IP address.
# Connect to your server as root ssh root@YOUR_SERVER_IP
Type yes when asked about the host fingerprint. You should see a root shell prompt.
A fresh server on the internet gets brute-force SSH attempts within minutes. We'll lock it down in 6 steps.
First, bring all packages up to date. This patches known security vulnerabilities.
# Update package lists and upgrade all installed packages
apt update && apt upgrade -y
Running everything as root is dangerous β a single mistake or exploit has full system access. We'll create a dedicated user with sudo privileges instead.
# Create a new user called "admin" with no password prompt adduser --disabled-password --gecos "" admin # Grant sudo (admin) privileges to this user usermod -aG sudo admin # Allow sudo without password prompt (convenience for workshop) echo "admin ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/admin # Copy your SSH key so you can log in as this user too mkdir -p /home/admin/.ssh cp ~/.ssh/authorized_keys /home/admin/.ssh/ chown -R admin:admin /home/admin/.ssh chmod 700 /home/admin/.ssh chmod 600 /home/admin/.ssh/authorized_keys
We disable root login and password authentication entirely. Only SSH key login to the admin user will work after this.
# Disable root login over SSH sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config # Disable password-based login (SSH keys only) sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config # Ensure public key authentication is enabled sed -i 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config # Restart the SSH daemon to apply changes systemctl restart sshd
# Test login with the new user β must work before closing root! ssh admin@YOUR_SERVER_IP
admin user for everything. Close the root session.
UFW (Uncomplicated Firewall) blocks all incoming connections except the ones we explicitly allow. Right now we only need SSH.
# Allow SSH connections through the firewall sudo ufw allow OpenSSH # Enable the firewall (--force skips the confirmation prompt) sudo ufw --force enable # Verify: should show SSH as ALLOW sudo ufw status
Fail2ban monitors login attempts and automatically bans IP addresses that fail too many times. Essential protection against brute-force attacks.
# Install fail2ban sudo apt install fail2ban -y # Create a local config (so updates don't overwrite your settings) sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # Start fail2ban and enable it on boot sudo systemctl enable --now fail2ban # Check the SSH jail status β shows banned IPs if any sudo fail2ban-client status sshd
Unattended-upgrades automatically installs security patches so your server stays protected even if you forget to update manually.
# Install and configure automatic security updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
OpenClaw runs in Docker containers. This gives us isolation, easy updates, and consistent behavior across different servers.
# Download and run Docker's official install script curl -fsSL https://get.docker.com | sudo sh # Add your user to the docker group so you don't need sudo for docker sudo usermod -aG docker $USER # Apply the new group membership without logging out newgrp docker # Verify both Docker and Docker Compose are installed docker --version docker compose version
docker compose fails, try docker-compose (with hyphen β older syntax).
Install OpenClaw, then lock down the application itself
The setup script pulls the Docker image, generates auth tokens, and starts the gateway.
# Clone the OpenClaw repository git clone https://github.com/openclaw/openclaw.git cd openclaw # Run the interactive setup β it will ask for your LLM API keys ./scripts/docker/setup.sh
.env and starts the containers.
Allow access to the OpenClaw control panel through the firewall.
# Allow the OpenClaw web UI port through the firewall
sudo ufw allow 18789/tcp
# Check the health endpoint β should return OK curl -fsS http://127.0.0.1:18789/healthz # See running containers docker compose ps
Open your browser and navigate to the control panel:
http://YOUR_SERVER_IP:18789
You'll need your gateway token to access Settings. Find it with:
# Display the gateway token from your .env file
grep GATEWAY_TOKEN .env
The server is secure, but OpenClaw itself needs configuration to prevent misuse. We'll lock down API keys, restrict permissions, and control what the AI can execute.
Your .env file contains API keys worth real money. Restrict its permissions so only your user can read it.
# Set .env to be readable only by the owner (you) chmod 600 ~/openclaw/.env # Verify permissions β should show -rw------- ls -la ~/openclaw/.env
By default, OpenClaw can execute shell commands. We'll set it to an allowlist-only mode so it can only run commands you've explicitly approved.
{
// Only allow explicitly approved commands
"tools": {
"exec": {
"security": "allowlist",
"allow": [
"git pull",
"npm install",
"npm test",
"python3",
"pip install"
],
"deny": [
"rm -rf /",
"git push --force",
"chmod 777",
"curl | sh",
"wget | sh"
],
"ask": "on-miss"
}
}
}
Create this config file:
# Create the config directory mkdir -p ~/.config/openclaw # Create the config file with exec restrictions cat > ~/.config/openclaw/config.json5 << 'CONF' { "tools": { "exec": { "security": "allowlist", "allow": [ "git pull", "npm install", "npm test", "python3", "pip install" ], "deny": [ "rm -rf /", "git push --force", "chmod 777", "curl | sh", "wget | sh" ], "ask": "on-miss" } } } CONF
Prevent runaway costs by setting spending caps in your LLM provider dashboards.
Harden the Docker container itself β drop all capabilities except what's needed and enforce read-only filesystems where possible.
# Check that OpenClaw binds to localhost only (not 0.0.0.0) # This ensures the web UI isn't exposed to the entire internet without auth sudo ss -tlnp | grep 18789
127.0.0.1:18789 means only accessible locally. If it shows 0.0.0.0:18789, the port is open to the world β that's fine for the workshop, but in production you'd put it behind a reverse proxy with HTTPS.
OpenClaw has a built-in security audit command that checks your configuration for common issues.
# Run OpenClaw's security audit β reports misconfigurations openclaw security audit # Run the doctor command β checks for risky DM policies and other issues openclaw doctor
Connect everything and build your first automation
Skills extend what OpenClaw can do. We'll install the ones needed for our morning briefing: email, web search, and weather.
# Install the himalaya skill β gives OpenClaw email access via IMAP openclaw skills install himalaya # Install web search skill β for fetching news headlines openclaw skills install web-search # Install weather skill β for forecast data openclaw skills install weather # List all installed skills to verify openclaw skills list
If you want the morning brief to include email summaries, configure himalaya with your email account:
# Interactive wizard β walks you through IMAP/SMTP setup
himalaya account configure
Or configure manually:
[accounts.myemail] email = "you@example.com" [accounts.myemail.imap] host = "imap.gmail.com" port = 993 login = "you@example.com" passwd.cmd = "echo 'your-app-password'" [accounts.myemail.smtp] host = "smtp.gmail.com" port = 587 login = "you@example.com" passwd.cmd = "echo 'your-app-password'"
Verify it works:
# List your latest emails β should show subjects and senders
himalaya list
On your phone or Telegram desktop:
/newbotbot, e.g. my_openclaw_bot)cd ~/openclaw # Register the Telegram channel with your bot token docker compose run --rm openclaw-cli channels add \ --channel telegram \ --token "YOUR_BOT_TOKEN_FROM_BOTFATHER"
By default, anyone who finds your bot can talk to it (and use your API credits). Let's restrict it to your Telegram account only.
First, send any message to your bot in Telegram, then find your user ID:
# Watch logs β look for "from":{"id":123456789} in the output
openclaw logs --follow
Now restrict the bot to only accept messages from your Telegram ID:
# Set DM policy to allowlist β only your user ID can talk to the bot # Replace the number with YOUR Telegram user ID from the logs cat >> ~/.config/openclaw/config.json5 << 'CONF' // Telegram DM restrictions β only allow specific user IDs // "channels": { // "telegram": { // "dmPolicy": "allowlist", // "allowFrom": [YOUR_TELEGRAM_USER_ID] // } // } CONF
Send a message to your bot in Telegram β try "Hello, are you working?"
The grand finale β a scheduled automation that sends you a daily briefing at 9:00 AM via Telegram with news, weather, and email highlights.
openclaw cron add \ --name "Morning Brief" \ --cron "0 9 * * *" \ --tz "Europe/Berlin" \ --session isolated \ --announce \ --channel telegram \ --to "YOUR_TELEGRAM_CHAT_ID" \ --message "Good morning! Please prepare my daily briefing: 1. **Top News**: Summarize the 3-5 most important world and tech news headlines from today. 2. **Weather**: What is the weather forecast for today and tomorrow in your city? 3. **Email Summary** (if himalaya is available): Check my recent emails from the last 12 hours. Summarize any important or action-required messages. Skip newsletters and spam. Keep it concise and actionable. Under 500 words."
Europe/Berlin, Europe/London, Europe/Kyiv, America/New_York, Asia/Tokyo. Use yours!
# List your cron jobs β note the JOB_ID openclaw cron list # Trigger it immediately to test (don't wait until 9 AM!) openclaw cron run JOB_ID
Check your Telegram β within a minute or two, your bot should send you a full morning briefing!
# Manage cron jobs openclaw cron list # List all jobs openclaw cron runs --id ID --limit 10 # See run history openclaw cron edit ID # Edit a job openclaw cron remove ID # Delete a job # Common schedules # "0 9 * * *" β 9:00 AM every day # "0 9 * * 1-5" β 9:00 AM weekdays only # "30 8,18 * * *" β 8:30 AM and 6:30 PM daily # "0 */3 * * *" β every 3 hours
SKILL.md files for domain-specific tasks