<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adnan Sattar</title>
    <description>The latest articles on DEV Community by Adnan Sattar (@adnansattar).</description>
    <link>https://dev.to/adnansattar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F267539%2Ffc42602f-6389-4896-b76f-72f6b9bf3a89.jpeg</url>
      <title>DEV Community: Adnan Sattar</title>
      <link>https://dev.to/adnansattar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adnansattar"/>
    <language>en</language>
    <item>
      <title>NemoClaw for the Enterprise: Installing NemoClaw and Bootstrapping the Sandbox (Part 2)</title>
      <dc:creator>Adnan Sattar</dc:creator>
      <pubDate>Fri, 08 May 2026 10:21:02 +0000</pubDate>
      <link>https://dev.to/adnansattar/nemoclaw-for-the-enterprise-installing-nemoclaw-and-bootstrapping-the-sandbox-part-2-335f</link>
      <guid>https://dev.to/adnansattar/nemoclaw-for-the-enterprise-installing-nemoclaw-and-bootstrapping-the-sandbox-part-2-335f</guid>
      <description>&lt;p&gt;&lt;em&gt;The substrate is ready. Now we move the agent into its cell and try not to bulldoze it on the way in.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/adnansattar/nemoclaw-for-the-enterprise-a-zero-trust-setup-for-openclaw-part-1-3hf9"&gt;Part 1&lt;/a&gt; we turned a fresh VPS into something an AI agent can safely live on; rootless user, Tailscale mesh, UFW, no public attack surface. That was the safe house. Empty.&lt;/p&gt;

&lt;p&gt;This article puts the tenant inside.&lt;/p&gt;

&lt;p&gt;The stack you're about to install is layered in a way that confuses people the first time they meet it, and the most expensive failure mode, running one perfectly innocent-looking command on the wrong day, quietly nukes everything you set up. So we're going to slow down on the mental model, install carefully, and treat the bootstrap as a one-shot operation. Because that's exactly what it is.&lt;/p&gt;

&lt;p&gt;By the end of this guide you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The NemoClaw CLI installed and authenticated against an inference provider&lt;/li&gt;
&lt;li&gt;A running NemoClaw sandbox (k3s + OpenShell + OpenClaw, all the way down)&lt;/li&gt;
&lt;li&gt;A working &lt;code&gt;nemoclaw connect&lt;/code&gt; shell into the sandboxed agent&lt;/li&gt;
&lt;li&gt;The OpenClaw dashboard reachable from your laptop over Tailscale&lt;/li&gt;
&lt;li&gt;A clear mental model of which command lives at which layer and which command will silently destroy your state if you run it twice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No Matrix yet. No skills, no policies.&lt;/p&gt;

&lt;p&gt;Just a clean install with the failure modes labelled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AIJ50aLTJXk10PjTqs5cJSw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AIJ50aLTJXk10PjTqs5cJSw.jpeg" alt="Stylised illustration of an AI agent confined inside a hardened sandbox cell, representing the rootless containerised execution environment" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Agent Cell&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What You're Building
&lt;/h3&gt;

&lt;p&gt;Here's the updated architecture, picking up where Part 1 left off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────────────┐
│ Your Tailnet (Private)                                             │
│                                                                    │
│  [Your Laptop] ───SSH/HTTPS───▶ VPS (openclaw user)                │
│                                  │                                 │
│                                  │ Docker engine                   │
│                                  ▼                                 │
│                  ┌────────────────────────┐                        │
│                  │ openshell-cluster-     │                        │
│                  │ nemoclaw (Docker ctr)  │                        │
│                  │                        │                        │
│                  │ ┌────────────────────┐ │                        │
│                  │ │ k3s (single-node)  │ │                        │
│                  │ │                    │ │                        │
│                  │ │ ┌────────────────┐ │ │                        │
│                  │ │ │ NemoClaw       │ │ │                        │
│                  │ │ │ sandbox pod    │ │ │                        │
│                  │ │ │                │ │ │                        │
│                  │ │ │ OpenClaw ──────┼─┼─┼──▶ inference API       │
│                  │ │ │ agent          │ │ │                        │
│                  │ │ └────────────────┘ │ │                        │
│                  │ └────────────────────┘ │                        │
│                  └────────────────────────┘                        │
└────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F609%2F1%2AtOTBLlwT1cpOwPV19w7qhA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F609%2F1%2AtOTBLlwT1cpOwPV19w7qhA.png" alt="Compact architecture diagram of the four-layer stack: Docker engine, openshell-cluster-nemoclaw container, k3s single-node cluster, and OpenClaw agent inside the sandbox pod" width="609" height="410"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Architecture&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Four layers, top to bottom: the Docker engine on your VPS, a single Docker container running a self-contained k3s cluster (&lt;code&gt;openshell-cluster-nemoclaw&lt;/code&gt;), a Kubernetes pod inside that cluster running the OpenShell sandbox, and inside that pod the OpenClaw agent itself.&lt;/p&gt;

&lt;p&gt;This sounds like overkill for a single-VPS deployment. It isn't.&lt;/p&gt;

&lt;p&gt;The k3s layer is what gives you cheap, repeatable sandbox lifecycle create, destroy, reset, snapshot without dragging Docker plumbing into agent execution. The OpenShell sandbox is what enforces the network and filesystem policies we'll write in Part 4. And the OpenClaw agent on top is just the workload.&lt;/p&gt;
&lt;h3&gt;
  
  
  The 60-Second Mental Model
&lt;/h3&gt;

&lt;p&gt;Three commands, three layers. Internalise this before you type anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;nemoclaw …&lt;/code&gt;&lt;/strong&gt;  — runs on the host. The orchestrator. Knows about Docker, k3s, and the sandbox lifecycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;openshell …&lt;/code&gt;&lt;/strong&gt;  — runs on the host. Talks directly to the gateway and lets you manage policies, providers, port forwards. Lower-level than &lt;code&gt;nemoclaw&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;openclaw …&lt;/code&gt;&lt;/strong&gt;  — runs &lt;em&gt;inside&lt;/em&gt; the sandbox. Manages plugins, skills, sessions. The agent's own CLI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you ever find yourself typing &lt;code&gt;openclaw plugins install&lt;/code&gt; on the host, you're at the wrong layer. If you find yourself typing &lt;code&gt;nemoclaw onboard&lt;/code&gt; &lt;em&gt;twice&lt;/em&gt;, stop reading and go make coffee, we'll get to that one in a minute.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AsdXy5xFu6vTy-NrKX1eY7A.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AsdXy5xFu6vTy-NrKX1eY7A.jpeg" alt="Three-layer command model showing nemoclaw and openshell on the host VPS, and openclaw running inside the sandboxed agent environment" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;60-Second Mental Model&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Install the NemoClaw CLI
&lt;/h3&gt;

&lt;p&gt;NemoClaw ships as an npm package. From your &lt;code&gt;openclaw&lt;/code&gt; user on the VPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; nemoclaw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If npm complains about permissions, you skipped a step in Part 1. &lt;code&gt;npm install -g&lt;/code&gt; should not need sudo if your user owns its npm prefix. Fix that before continuing rather than papering over it with sudo, which will create root-owned files in places you'll regret later.&lt;/p&gt;

&lt;p&gt;Verify the install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nemoclaw &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the help banner showing version 0.1.x or newer, with sections for &lt;em&gt;Sandbox Management&lt;/em&gt;, &lt;em&gt;Policy Presets&lt;/em&gt;, &lt;em&gt;Services&lt;/em&gt;, and &lt;em&gt;Troubleshooting&lt;/em&gt;. If the version reads v0.0.x, upgrade. There are real lifecycle bugs in the early-zero releases that bit several of us during early-2026 deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Pick an Inference Provider
&lt;/h3&gt;

&lt;p&gt;NemoClaw is provider-agnostic but nudges you toward NVIDIA's Nemotron family. In practice, the cleanest path for a fresh deployment is OpenRouter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single API key, dozens of models behind it&lt;/li&gt;
&lt;li&gt;Per-token billing, no commitments&lt;/li&gt;
&lt;li&gt;Direct access to &lt;code&gt;nvidia/nemotron-3-super-120b-a12b&lt;/code&gt;, which is what NemoClaw is tuned around&lt;/li&gt;
&lt;li&gt;If you later want to swap to Anthropic or OpenAI, it's a one-line change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sign up at &lt;a href="https://openrouter.ai/" rel="noopener noreferrer"&gt;openrouter.ai&lt;/a&gt;, generate an API key, and keep it on your clipboard. We'll feed it to &lt;code&gt;onboard&lt;/code&gt; in a moment.&lt;/p&gt;

&lt;p&gt;If you're an enterprise with a direct NVIDIA NIM contract, point at your NIM endpoint instead. The flow is identical. &lt;code&gt;onboard&lt;/code&gt; will ask which provider you want and what credentials to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Onboard (and Why You'll Only Do This Once)
&lt;/h3&gt;

&lt;p&gt;This is the section where I get to be slightly insufferable about a warning, because it has cost more than one of my evenings.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nemoclaw onboard&lt;/code&gt; is the bootstrap command. It does five things in one shot: configures your inference provider, generates the gateway's mTLS certificates, pulls the OpenShell container images, brings up the k3s cluster inside Docker, and creates the sandbox pod with default policies attached.&lt;/p&gt;

&lt;p&gt;It is a &lt;em&gt;create-from-scratch&lt;/em&gt; command. Not idempotent. Not a "rerun to update" command.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Hard rule.&lt;/strong&gt; Never run &lt;code&gt;nemoclaw onboard&lt;/code&gt; against an existing sandbox. It will recreate everything from zero: your provider config, your policies, your sessions, your installed skills, your Matrix tokens (Part 3), all of it. There is no confirmation prompt that adequately captures how destructive this is.&lt;/p&gt;

&lt;p&gt;If you need to change a policy, use &lt;code&gt;nemoclaw &amp;lt;name&amp;gt; policy-add&lt;/code&gt; or &lt;code&gt;openshell policy set&lt;/code&gt;. If you need to change a provider, use &lt;code&gt;openshell provider create&lt;/code&gt; and &lt;code&gt;openshell inference set&lt;/code&gt;. Treat &lt;code&gt;onboard&lt;/code&gt; like &lt;code&gt;mkfs&lt;/code&gt;: useful exactly once.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OK. With that out of the way, run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nemoclaw onboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI walks you through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sandbox name&lt;/strong&gt; — accept the default (&lt;code&gt;nemoclaw-sandbox&lt;/code&gt;) unless you have a reason. The companion commands all default to it, and overriding the name buys you nothing but typing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inference provider&lt;/strong&gt; — pick &lt;code&gt;openrouter&lt;/code&gt; (or &lt;code&gt;nvidia&lt;/code&gt;, &lt;code&gt;anthropic&lt;/code&gt;, &lt;code&gt;openai&lt;/code&gt; per your choice in Step 2).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model&lt;/strong&gt; — for OpenRouter + Nemotron: &lt;code&gt;nvidia/nemotron-3-super-120b-a12b&lt;/code&gt;. NemoClaw will validate it exists by issuing a tiny test completion before continuing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API key&lt;/strong&gt; — paste it. It gets written to &lt;code&gt;~/.nemoclaw/credentials.json&lt;/code&gt; with mode 600.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The CLI then hands off to OpenShell to bootstrap the gateway and sandbox. Don't kill the terminal. This takes anywhere from two to seven minutes depending on your VPS network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A1J5_j23mJ-6njtJOg5fyDw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A1J5_j23mJ-6njtJOg5fyDw.jpeg" alt="Warning illustration of a kill switch labelled nemoclaw onboard, signalling that the command is destructive if run twice" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Kill Switch&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Watch the Bootstrap
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;onboard&lt;/code&gt; runs, what's actually happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Docker pulls the OpenShell gateway image and starts the &lt;code&gt;openshell-cluster-nemoclaw&lt;/code&gt; container.&lt;/li&gt;
&lt;li&gt;Inside that container, k3s comes up as a single-node cluster.&lt;/li&gt;
&lt;li&gt;OpenShell deploys its gateway pod into the cluster and waits for it to report healthy.&lt;/li&gt;
&lt;li&gt;NemoClaw applies the default network policy preset and creates the sandbox pod (&lt;code&gt;nemoclaw-sandbox&lt;/code&gt;) in the openshell namespace.&lt;/li&gt;
&lt;li&gt;The sandbox pod pulls its OpenClaw image and starts the agent.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to follow along live, open a second SSH session to the VPS and tail the relevant logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Container-level: is k3s healthy?&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; openshell-cluster-nemoclaw

&lt;span class="c"&gt;# Sandbox-level: is the agent coming up?&lt;/span&gt;
nemoclaw nemoclaw-sandbox logs &lt;span class="nt"&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second command will fail until the sandbox pod exists, which is fine. Retry it once &lt;code&gt;onboard&lt;/code&gt; reports the sandbox is created.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;onboard&lt;/code&gt; finishes, you'll see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Gateway running at https://127.0.0.1:8080
✓ Sandbox 'nemoclaw-sandbox' is healthy
✓ Default policies applied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AhWBqECG5LAG6w3MpNb8Krw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AhWBqECG5LAG6w3MpNb8Krw.jpeg" alt="Bootstrap sequence diagram showing Docker pulling images, k3s starting, gateway pod becoming healthy, and the sandbox pod coming online" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Bootstrap&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: Verify the Gateway and Recognise the False Alarm
&lt;/h3&gt;

&lt;p&gt;Sanity-check the gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nemoclaw status
openshell status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both should show the gateway as running and the sandbox as healthy.&lt;/p&gt;

&lt;p&gt;A wart worth knowing: on a slow VPS, &lt;code&gt;nemoclaw connect&lt;/code&gt; immediately after &lt;code&gt;onboard&lt;/code&gt; will sometimes greet you with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gateway process started but is not responding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;almost always a timing race condition, not a real failure.&lt;/strong&gt; OpenShell's health check fires before the gateway has finished initialising mTLS. Wait thirty seconds, retry, and it's there. If it's still failing after a minute, &lt;em&gt;then&lt;/em&gt; break out the diagnostics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openshell doctor check
openshell doctor logs &lt;span class="nt"&gt;--lines&lt;/span&gt; 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;doctor check&lt;/code&gt; validates that Docker, k3s, and the gateway are all in expected states. &lt;code&gt;doctor logs&lt;/code&gt; pulls the gateway container's stdout. Between them, you'll see the actual cause of any genuine failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Connect to the Sandbox
&lt;/h3&gt;

&lt;p&gt;The moment of truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nemoclaw nemoclaw-sandbox connect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first connection negotiates an SSH session over the gateway's mTLS tunnel and drops you into a shell inside the sandbox pod. The prompt changes; the hostname changes; you're now executing commands inside an isolated environment whose filesystem and network access are governed by OpenShell policies.&lt;/p&gt;

&lt;p&gt;Inside the sandbox, run the obvious sanity checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;whoami
pwd&lt;/span&gt; &lt;span class="c"&gt;# /sandbox&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
openclaw &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll find yourself as a non-root user inside &lt;code&gt;/sandbox&lt;/code&gt;, with the OpenClaw binary on your PATH and a bare home directory. This is intentional. The sandbox starts almost empty by design; skills, plugins, and credentials get layered in deliberately rather than inherited from the host.&lt;/p&gt;

&lt;p&gt;Try poking at the network from inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://google.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll likely get a connection-refused or DNS failure, depending on the default policy. &lt;strong&gt;That's the policy engine working as advertised.&lt;/strong&gt; Part 4 covers how to write policies that grant the agent exactly the network access it needs and nothing more.&lt;/p&gt;

&lt;p&gt;Type &lt;code&gt;exit&lt;/code&gt; to drop back to the host. The sandbox keeps running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A4OMBwvthrppt6M8g5fTtcA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2A4OMBwvthrppt6M8g5fTtcA.jpeg" alt="End-to-end workflow diagram showing the operator connecting from a laptop, through the Tailscale mesh, into the gateway, and into the sandboxed agent shell" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;End to End Workflow&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 7: Reach the OpenClaw Dashboard
&lt;/h3&gt;

&lt;p&gt;OpenClaw exposes a web UI for chatting with the agent and managing sessions. NemoClaw maps it to &lt;code&gt;127.0.0.1:18789&lt;/code&gt; on the VPS. From the host it's local-only by design. There's no public listener, and there shouldn't be.&lt;/p&gt;

&lt;p&gt;To reach it from your laptop, use your Tailscale-resolved hostname:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;your-vps-tailscale-hostname&amp;gt;:18789
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you enabled MagicDNS in Part 1, that's something like &lt;code&gt;http://openclaw-staging:18789&lt;/code&gt;. From any device on your tailnet, the dashboard loads. From anywhere else on the internet, the connection won't even establish. The port isn't exposed past localhost, and the firewall would drop it anyway.&lt;/p&gt;

&lt;p&gt;The first time you load the dashboard it'll ask for a gateway auth token. That brings us to the one operational wart you should know about now, before it surprises you in production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AjZ4ixvnUgPGzF-gZhwdKgg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AjZ4ixvnUgPGzF-gZhwdKgg.jpeg" alt="Browser screenshot illustration showing the OpenClaw dashboard loading over a Tailscale hostname with a gateway authentication token prompt" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Reach the OpenClaw Dashboard&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  A Known Wart: The Gateway Doesn't Survive Host Reboots Cleanly
&lt;/h3&gt;

&lt;p&gt;If you reboot the VPS, lose network on the host long enough for the gateway to give up, or restart Docker, the gateway process drops and the dashboard refuses your existing session. The sandbox pod is fine. The agent state is fine. The OpenClaw container in k3s is fine. It's just that the gateway's auth token gets rotated on restart and the dashboard doesn't pick up the new one automatically.&lt;/p&gt;

&lt;p&gt;The recovery is mechanical: pull the new token out of the sandbox config and paste it into the dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;openshell-cluster-nemoclaw &lt;span class="se"&gt;\&lt;/span&gt;
  kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; openshell nemoclaw-sandbox &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import json; print(json.load(open('/sandbox/.openclaw/openclaw.json'))['gateway']['auth']['token'])"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command is a worked example of the four-layer mental model: Docker → k3s → sandbox pod → file inside the pod. Read it left to right and you'll see each &lt;code&gt;exec&lt;/code&gt; peeling off one layer.&lt;/p&gt;

&lt;p&gt;Copy the printed token, paste it into the dashboard's auth prompt, and you're back. Keep this command in a paste-buffer somewhere; you will need it more than once.&lt;/p&gt;

&lt;p&gt;A proper systemd-based autostart that watches for network changes and refreshes the dashboard auth automatically is doable, and it's on the roadmap for a later article in this series. For now, the manual recovery is twenty seconds and worth the explicitness. It forces you to notice when the gateway has restarted, which on a security-sensitive deployment is information you actually want.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification Checklist
&lt;/h3&gt;

&lt;p&gt;Before moving on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nemoclaw status&lt;/code&gt; shows the sandbox healthy and the gateway running&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nemoclaw nemoclaw-sandbox connect&lt;/code&gt; drops you into a &lt;code&gt;/sandbox&lt;/code&gt; shell&lt;/li&gt;
&lt;li&gt;From inside the sandbox, &lt;code&gt;openclaw --version&lt;/code&gt; returns a version string&lt;/li&gt;
&lt;li&gt;From your laptop, &lt;code&gt;http://&amp;lt;vps-tailscale-host&amp;gt;:18789&lt;/code&gt; loads the OpenClaw dashboard&lt;/li&gt;
&lt;li&gt;From anywhere &lt;em&gt;not&lt;/em&gt; on your tailnet, the dashboard is unreachable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;openshell doctor check&lt;/code&gt; reports green across the board&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.nemoclaw/credentials.json&lt;/code&gt; exists with mode 600&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those fail, fix them before Part 3. Matrix layered on top of a flaky bootstrap will produce confusing failures that look like Matrix problems and aren't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where You Are Now
&lt;/h3&gt;

&lt;p&gt;You have a four-layer agent stack running on a hardened VPS that has no public attack surface. The agent is alive, sandboxed, reachable from your laptop over Tailscale, and gated behind mTLS. It can't yet be talked to over a real chat protocol, can't reach external APIs beyond what the default policy allows, and has no skills installed. That's deliberate. We're laying down each capability one article at a time, and verifying it works before piling the next one on top.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AZ6wAOnmBV6vMrhMxdULS9w.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AZ6wAOnmBV6vMrhMxdULS9w.jpeg" alt="Stack diagram showing the private AI agent layered architecture: hardened VPS, rootless user, Docker, k3s, OpenShell sandbox, and OpenClaw agent on top" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Private AI Agent Layer Stack&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The thing worth pausing on: this is already a defensible deployment. Even with no extra hardening, an attacker who somehow compromised your VPS would still have to escape the rootless &lt;code&gt;openclaw&lt;/code&gt; user, then escape the sandbox container, then escape the k3s namespace isolation, before they could touch the host kernel. Each layer is breakable in theory. Stacking them is what makes the practical attack vanishingly expensive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2ARgtfixLkg222n1p0tcTX0Q.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2ARgtfixLkg222n1p0tcTX0Q.jpeg" alt="Defense-in-depth illustration showing concentric security layers an attacker would have to breach: rootless user, sandbox container, k3s namespace, and host kernel" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Defense in Depth&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Part 3. Matrix as the Control Channel.&lt;/strong&gt; The default OpenClaw control surface is the dashboard you just loaded. That's fine for development. For a deployment where the messages flowing into the agent are, by definition, high-privilege instructions, you want the channel itself to be end-to-end encrypted and authenticated. Telegram doesn't cut it. Matrix does, but the install path has a few sharp edges around OIDC migration and device key conflicts that I'm going to walk through in detail, because the docs don't, and the failure modes are genuinely confusing the first time you hit them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 4. Policy Engineering.&lt;/strong&gt; OpenShell's policy engine is the actual reason to run NemoClaw rather than vanilla OpenClaw. We'll write per-domain network policies, set up filesystem allowlists, and walk through the live-update flow with &lt;code&gt;openshell policy set --wait&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 5. Skills, Plugins, and Model Switching.&lt;/strong&gt; ClawHub, the docker-cp-then-kubectl-cp pattern for getting skills into the sandbox safely, and how to flip between Nemotron and Claude Sonnet without a restart.&lt;/p&gt;

&lt;p&gt;I'm collecting deployment war stories for Part 3's appendix. If this broke in a way I didn't cover, wrong CLI version, weird VPS provider, a &lt;code&gt;doctor check&lt;/code&gt; red I haven't seen, drop the error in the comments. The Matrix article gets sharper for every one of these I see.&lt;/p&gt;

</description>
      <category>devsecops</category>
      <category>cybersecurity</category>
      <category>nemoclaw</category>
      <category>openshell</category>
    </item>
    <item>
      <title>NemoClaw for the Enterprise: A Zero-Trust Setup for OpenClaw (Part 1)</title>
      <dc:creator>Adnan Sattar</dc:creator>
      <pubDate>Fri, 17 Apr 2026 12:24:59 +0000</pubDate>
      <link>https://dev.to/adnansattar/nemoclaw-for-the-enterprise-a-zero-trust-setup-for-openclaw-part-1-3hf9</link>
      <guid>https://dev.to/adnansattar/nemoclaw-for-the-enterprise-a-zero-trust-setup-for-openclaw-part-1-3hf9</guid>
      <description>&lt;p&gt;&lt;em&gt;How to give your AI agent a safe house: full shell access, without becoming a liability to your security team.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An AI agent with shell access is one prompt injection away from a very bad day. It can read your files, touch your network, run commands, and if the box it's sitting on is exposed to the public internet invite the rest of the world in with it.&lt;/p&gt;

&lt;p&gt;This is the first article in a series on running OpenClaw via NemoClaw in OpenShell sandbox. Before we touch agents, policies, or skills, we need to build the house they live in. That means a hardened VPS, no public attack surface, and a clear blast radius if something goes sideways.&lt;/p&gt;

&lt;p&gt;By the end of this guide you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fresh VPS running as a non-root user&lt;/li&gt;
&lt;li&gt;Passwordless SSH with password login fully disabled&lt;/li&gt;
&lt;li&gt;A Tailscale mesh that makes the box invisible to the public internet&lt;/li&gt;
&lt;li&gt;A UFW firewall that drops anything not coming over Tailscale&lt;/li&gt;
&lt;li&gt;Docker, Node.js, and uv installed and ready for OpenClaw&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No agent yet. Just a safe house. Parts 2 and 3 will cover the NemoClaw install, Matrix E2EE messaging, and policy engineering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fho2j926hcwjrobj1e12m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fho2j926hcwjrobj1e12m.png" alt="Title illustration for the NemoClaw zero-trust setup guide showing a hardened server with security layers around an AI agent" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;NemoClaw for the Enterprise: A Zero-Trust Setup for OpenClaw&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why This Matters (the 30-second version)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtnl0fiyv2q9kje76eau.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtnl0fiyv2q9kje76eau.png" alt="Diagram explaining why a zero-trust setup matters for AI agents with shell access, showing the threat surface of an unguarded agent" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Why A Zero-Trust Setup Matters&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;OpenClaw is powerful because it has broad access to the system it runs on. Shell, files, network. It can do almost anything a human operator can do. That's the feature. It's also the threat model.&lt;/p&gt;

&lt;p&gt;A single crafted message coming through a public channel (Telegram, Discord, email) can, in the worst case, convince an unguarded agent to run commands on your behalf that you'd never consent to. The defense isn't one thing; it's layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rootless execution&lt;/strong&gt; : if something escapes, it's confined to a restricted user, not root&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-trust networking&lt;/strong&gt; : the machine has no public attack surface at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Passwordless SSH&lt;/strong&gt; : brute-force attacks become mathematically hopeless&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict firewall&lt;/strong&gt; : anything not coming over the Tailscale interface is dropped&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E2EE communication&lt;/strong&gt; (covered in Part 2): the control channel can't be snooped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each layer is cheap to set up. Skipping any one of them punches a hole that the others can't fully cover. Defense-in-depth only works when it's actually, you know, in depth.&lt;/p&gt;
&lt;h3&gt;
  
  
  What You're Building (architecture at a glance)
&lt;/h3&gt;

&lt;p&gt;Here's the shape of what the stack looks like after this guide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────────┐
│ Your Tailnet (Private)                                         │
│                                                                │
│ [Your Laptop] ───SSH───▶ [VPS: openclaw user]                  │
│                          │                                     │
│                          ├─ UFW (deny by default)              │
│                          ├─ tailscale0 (allowed)               │
│                          └─ OpenClaw Running in Sandbox        │
│                                                                │
└────────────────────────────────────────────────────────────────┘
             ▲
             │ (public internet never reaches here)
             ▼
        ╳ Port 22 closed ╳ Port 80/443 closed ╳
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server has no listening ports exposed to the public internet. Your laptop reaches it only through the Tailscale mesh, which is authenticated with your Tailscale identity, not a password. The openclaw user that owns the agent has sudo but not root shell access, so any escape is already one privilege short.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpfv4qkjl6nhf3fl1atc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpfv4qkjl6nhf3fl1atc.png" alt="Table showing risks mitigated by the zero-trust setup, mapping each threat to the corresponding defense layer" width="800" height="361"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Mitigated Risks By Zero Trust Setup&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgn9k6oa3jlvx3w4cw3eh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgn9k6oa3jlvx3w4cw3eh.png" alt="Architecture diagram of the zero-trust setup showing the laptop, Tailscale mesh, hardened VPS, UFW firewall, and OpenClaw sandbox" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Zero Trust Architecture&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before you start, have these lined up:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardware:&lt;/strong&gt; A VPS with at least 4 vCPU, 8 GB RAM, 50 GB SSD. OpenClaw will run on less, but NemoClaw pulls container images and runs a k3s cluster, so undersized boxes will bite you later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OS:&lt;/strong&gt; Ubuntu 24.04 LTS (recommended) or Debian 12. This guide assumes Ubuntu 24.04.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtualization:&lt;/strong&gt; KVM. OpenVZ and similar shared-kernel setups don't play well with Docker. Hostinger, DigitalOcean, Linode, and Vultr all use KVM by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accounts:&lt;/strong&gt; Tailscale (free tier is fine).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local tools:&lt;/strong&gt; An SSH client (macOS/Linux have one built in; Windows users can use OpenSSH via PowerShell or WSL) and a Tailscale client on whatever machine you'll connect from.&lt;/p&gt;

&lt;p&gt;One non-negotiable: &lt;strong&gt;don't host an unhardened OpenClaw instance on your primary workstation or any box with sensitive local data.&lt;/strong&gt; If something goes wrong, you want the blast contained to a cheap VPS you can nuke and rebuild, not your daily driver.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Provision and Update the VPS
&lt;/h3&gt;

&lt;p&gt;Spin up an Ubuntu 24.04 instance with your provider of choice. Pick a region close to you for lower latency. Set a strong initial root password. You'll use it exactly once.&lt;/p&gt;

&lt;p&gt;SSH in as root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@YOUR_VPS_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the system and enable unattended security upgrades:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; unattended-upgrades
dpkg-reconfigure &lt;span class="nt"&gt;-plow&lt;/span&gt; unattended-upgrades
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the packages you'll need over the rest of the guide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ca-certificates &lt;span class="se"&gt;\&lt;/span&gt;
  curl &lt;span class="se"&gt;\&lt;/span&gt;
  gnupg &lt;span class="se"&gt;\&lt;/span&gt;
  lsb-release &lt;span class="se"&gt;\&lt;/span&gt;
  build-essential &lt;span class="se"&gt;\&lt;/span&gt;
  git &lt;span class="se"&gt;\&lt;/span&gt;
  unzip &lt;span class="se"&gt;\&lt;/span&gt;
  jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it for the root session. Everything else runs as an unprivileged user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create a Rootless User
&lt;/h3&gt;

&lt;p&gt;Root is a loaded gun. Running OpenClaw as root means any exploit (a bad skill, a prompt injection that slips past guardrails, a malicious package) gets full control of the box. Instead, we'll create a dedicated user with sudo rights for setup, but no root shell, so the default blast radius is limited.&lt;/p&gt;

&lt;p&gt;Still as root on the VPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adduser &lt;span class="nt"&gt;--gecos&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; openclaw
usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;openclaw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The --gecos "" flag skips the interactive "Full Name / Room Number" prompts. You'll be asked for a password. Set one, but you won't need it often because we're about to switch to SSH keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Set Up Passwordless SSH
&lt;/h3&gt;

&lt;p&gt;Password logins are a liability. Every Ubuntu box on the public internet is getting hammered with login attempts right now; yours is no exception. SSH keys are mathematically impossible to brute-force, and they're faster to use anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On your local machine&lt;/strong&gt; (not the VPS), generate an ED25519 key pair if you don't already have one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"openclaw"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accept the default path (~/.ssh/id_ed25519) or give it a custom name. A passphrase is optional but recommended.&lt;/p&gt;

&lt;p&gt;Copy the public key to the VPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-copy-id &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/id_ed25519.pub openclaw@YOUR_VPS_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be prompted for the openclaw user's password (the one you set in Step 2). This is the last time you'll need it.&lt;/p&gt;

&lt;p&gt;Verify the key works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh openclaw@YOUR_VPS_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should land directly at a shell without being asked for a password. &lt;strong&gt;Don't skip this verification step.&lt;/strong&gt; The next section disables password login entirely, and if the key doesn't work, you'll lock yourself out of the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs8w5x15dcajdk5kii3pf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs8w5x15dcajdk5kii3pf.png" alt="Illustration of SSH key-based authentication showing keys as the only allowed entry method to the VPS" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;SSH Only Access in Zero Trust Setup&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Disable Password Login
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Critical:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;If your SSH key doesn't already work, fix that first. This step makes key-based auth the only way in. Get it wrong and your only recourse is the VPS provider's web console.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you've confirmed keys work, SSH in as the openclaw user and edit the SSH daemon config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find and set the following values (uncomment them if they're prefixed with #):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;PasswordAuthentication&lt;/span&gt; &lt;span class="err"&gt;no&lt;/span&gt;
&lt;span class="err"&gt;PermitRootLogin&lt;/span&gt; &lt;span class="err"&gt;no&lt;/span&gt;
&lt;span class="err"&gt;ChallengeResponseAuthentication&lt;/span&gt; &lt;span class="err"&gt;no&lt;/span&gt;
&lt;span class="err"&gt;UsePAM&lt;/span&gt; &lt;span class="err"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and exit (Ctrl+O, Enter, Ctrl+X in nano).&lt;/p&gt;

&lt;p&gt;Restart SSH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Don't close your current SSH session yet.&lt;/strong&gt; Open a new terminal and try to connect. If it works, you're good. If it doesn't, you still have the original session open to fix sshd_config. Only after you've confirmed a fresh connection works should you close the original.&lt;/p&gt;

&lt;p&gt;Password-based attacks now hit a wall regardless of how weak the user's password is. This single change blocks the vast majority of automated SSH bot traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Install Core Dependencies
&lt;/h3&gt;

&lt;p&gt;OpenClaw, NemoClaw and Openshell need a handful of runtimes:&lt;/p&gt;

&lt;p&gt;Node.js 22+ for the CLI, Docker for the execution substrate, uv for Python package management, and Git because everything needs Git.&lt;/p&gt;

&lt;p&gt;Here's the mental model worth internalizing before you run these commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenShell&lt;/strong&gt; is the control plane. It orchestrates sandboxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; is the execution substrate. Containers are where agents actually run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenClaw&lt;/strong&gt; is the workload, the agent itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're installing substrate and plumbing now. Workload comes in Part 2.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js 22
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deb.nodesource.com/setup_22.x | &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; bash -
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs
node &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="c"&gt;# should print v22.x.x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker
&lt;/h3&gt;

&lt;p&gt;Add Docker's official GPG key and repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/apt/keyrings
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.gpg

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"deb [arch=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/etc/apt/keyrings/docker.gpg] &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the Docker stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  docker-ce &lt;span class="se"&gt;\&lt;/span&gt;
  docker-ce-cli &lt;span class="se"&gt;\&lt;/span&gt;
  containerd.io &lt;span class="se"&gt;\&lt;/span&gt;
  docker-buildx-plugin &lt;span class="se"&gt;\&lt;/span&gt;
  docker-compose-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable and start Docker, then add your user to the docker group so you don't need sudo for every command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
newgrp docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker info
docker run hello-world
docker compose version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If hello-world pulls and prints its banner, Docker is good.&lt;/p&gt;

&lt;h3&gt;
  
  
  uv (Python package manager)
&lt;/h3&gt;

&lt;p&gt;uv is Astral's Rust-based replacement for pip and venv. It's dramatically faster and NemoClaw uses it under the hood:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-Ls&lt;/span&gt; https://astral.sh/uv/install.sh | bash
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
uv &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If uv --version fails because it's not on your PATH, add it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.local/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="$HOME/.local/bin:$PATH"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sanity Check
&lt;/h3&gt;

&lt;p&gt;Before moving on, verify the whole stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="c"&gt;# v22.x.x&lt;/span&gt;
git &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="c"&gt;# any recent version&lt;/span&gt;
docker info &lt;span class="c"&gt;# no errors&lt;/span&gt;
docker compose version
uv &lt;span class="nt"&gt;--version&lt;/span&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://google.com &lt;span class="c"&gt;# outbound network works&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all five commands produce sensible output, your substrate is ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Go Invisible with Tailscale
&lt;/h3&gt;

&lt;p&gt;Here's where the architecture pays off. Right now, your VPS has a public IP with SSH (port 22) open to the entire internet. Even with password auth disabled, that's an attack surface. Zero-days in OpenSSH aren't hypothetical, and port scanners catalog your server within minutes of provisioning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcan4kctl5xtnghjaqbc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcan4kctl5xtnghjaqbc6.png" alt="Diagram showing how Tailscale's WireGuard mesh makes the VPS invisible to the public internet by routing all traffic through a private tailnet" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;WireGuard Invisible Mode&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Tailscale replaces that public exposure with a private WireGuard-based mesh network (a "tailnet"). Your devices (laptop, phone, VPS) get private IPs in the 100.x.x.x range, and they talk to each other directly over encrypted tunnels. The rest of the internet doesn't even know your VPS exists.&lt;/p&gt;

&lt;p&gt;Install Tailscale on the VPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://tailscale.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bring the node online with SSH-over-Tailscale enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;tailscale up &lt;span class="nt"&gt;--ssh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI will print an authentication URL. Open it in your browser, log into Tailscale, and approve the device.&lt;/p&gt;

&lt;p&gt;Install the Tailscale client on your local machine too (via tailscale.com/download) and log into the same account. Your laptop and VPS are now on the same tailnet.&lt;/p&gt;

&lt;p&gt;In the Tailscale admin console, enable &lt;strong&gt;MagicDNS&lt;/strong&gt;. It lets you SSH to your box by hostname (ssh openclaw@your-tailscale-hostname) instead of memorizing a 100.x.x.x IP.&lt;/p&gt;

&lt;p&gt;The --ssh flag is worth understanding. It routes SSH through Tailscale's identity layer, which means even your SSH keys become a belt-and-suspenders backup rather than the primary auth mechanism. Tailscale handles the identity check at the network layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Lock Down the Firewall
&lt;/h3&gt;

&lt;p&gt;Tailscale gets you invisible, but belt-and-suspenders. We still want UFW to refuse anything that didn't come in over the tailscale0 interface. If Tailscale ever hiccups or gets misconfigured, the firewall is the last line of defense.&lt;/p&gt;

&lt;p&gt;Install UFW:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ufw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set deny-by-default for inbound, allow-by-default for outbound:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default deny incoming
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default allow outgoing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allow all traffic on the Tailscale interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on tailscale0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allow SSH on the public interface as a temporary safety net. Once you've confirmed Tailscale SSH works end-to-end, you'll remove this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow OpenSSH
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtykx7f7jphpbacnl75l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtykx7f7jphpbacnl75l.png" alt="UFW firewall configuration diagram showing default deny inbound, allow on tailscale0 interface, and a temporary OpenSSH rule" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Lock Down Firewall&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;UFW will warn that enabling may disrupt existing SSH connections. Since we explicitly allowed OpenSSH, you're fine. Type y.&lt;/p&gt;

&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a short list of rules: Tailscale allowed on its interface, OpenSSH allowed on public, everything else denied.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removing the Public SSH Safety Net
&lt;/h3&gt;

&lt;p&gt;Once you've confirmed you can SSH in over Tailscale (ssh openclaw@your-tailscale-hostname), close the public SSH port entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw delete allow OpenSSH
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, running a port scan against your VPS's public IP from anywhere on the internet returns nothing. The box is effectively invisible. The only way in is through your tailnet, authenticated with your Tailscale identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where You Are Now
&lt;/h3&gt;

&lt;p&gt;If you made it this far, the result is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A VPS running Ubuntu 24.04 with automatic security updates&lt;/li&gt;
&lt;li&gt;A non-root openclaw user with sudo rights&lt;/li&gt;
&lt;li&gt;Passwordless SSH with password auth completely disabled&lt;/li&gt;
&lt;li&gt;Tailscale mesh with MagicDNS, making the server reachable only from your tailnet&lt;/li&gt;
&lt;li&gt;UFW dropping everything not coming over tailscale0&lt;/li&gt;
&lt;li&gt;Docker, Node.js 22, Git, and uv installed and working&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You haven't installed OpenClaw yet. That's deliberate. Everything up to this point is infrastructure that would be worth doing even if you were never going to run an AI agent. It's just good server hygiene. Now it's going to serve as the substrate for something that genuinely needs these protections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Verification Checklist
&lt;/h3&gt;

&lt;p&gt;Before moving on, confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ssh openclaw@your-tailscale-host works from your laptop&lt;/li&gt;
&lt;li&gt;ssh openclaw@PUBLIC_IP from a device &lt;em&gt;not&lt;/em&gt; on your tailnet fails to connect&lt;/li&gt;
&lt;li&gt;sudo ufw status shows deny-by-default with tailscale0 allowed&lt;/li&gt;
&lt;li&gt;grep -E "^(PasswordAuthentication|PermitRootLogin)" /etc/ssh/sshd_config shows both set to no&lt;/li&gt;
&lt;li&gt;docker run hello-world succeeds as the openclaw user (no sudo)&lt;/li&gt;
&lt;li&gt;node -v, uv --version, git --version all return versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these fail, fix them before installing OpenClaw. The security posture only holds if every layer is actually in place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tel972l0qf8dp81zk7l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tel972l0qf8dp81zk7l.png" alt="Roadmap diagram of the NemoClaw series showing Part 1 zero-trust setup, Part 2 NemoClaw install with Matrix E2EE, and Part 4 policy engineering" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The NemoClaw Series Roadmap&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/adnansattar/nemoclaw-for-the-enterprise-installing-nemoclaw-and-bootstrapping-the-sandbox-part-2-335f"&gt;Part 2&lt;/a&gt; &amp;amp; Part 3. Installing NemoClaw and Wiring Up Matrix E2EE:&lt;/strong&gt; We'll install the NemoClaw CLI, bootstrap the sandbox, and replace Telegram with Matrix as the control channel. Matrix gives us end-to-end encryption out of the box, which matters because the messages flowing through it are, by definition, high-privilege instructions to an AI agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 4. Policy Engineering and Semantic Guardrails:&lt;/strong&gt; OpenShell's network policy engine lets you declare exactly which domains the agent can reach. We'll write policies that implement least-privilege at the network layer, and build out an AGENTS.md that acts as the agent's operating manual. The behavioral equivalent of a firewall.&lt;/p&gt;

&lt;p&gt;The short version: security for AI agents isn't a single feature. It's a stack. We've just laid the foundation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you found this useful, clap, follow, and drop a comment with what you'd like covered in the follow-ups. Production deployment horror stories especially welcome. They're the best teachers.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devsecops</category>
      <category>cybersecurity</category>
      <category>nemoclaw</category>
      <category>openclaw</category>
    </item>
  </channel>
</rss>
