Posted · 1 min read
I've been using OpenClaw as my personal AI gateway — it connects LLMs to Slack, WhatsApp, Telegram, and a bunch of other messaging platforms. It's self-hosted by design, so you need to figure out where to run it.
I started with a quick setup on a bare VM, but that meant managing backups manually, worrying about open ports, and having no easy way to reproduce the setup if something breaks. That's why I put together a small CDK project that deploys OpenClaw on a single EC2 instance with zero public-facing ports. All access goes through Tailscale's mesh VPN.
The whole thing is about 200 lines of TypeScript across four CDK constructs. Here's how it works.
You could SSH into an EC2 instance, install OpenClaw, and call it a day. But then you're left with:
For something I want to run long-term without babysitting, I wanted a setup that's reproducible, locked down, and backs itself up.
The stack creates a VPC with public subnets only — no NAT gateway, which saves about $30/month. The security group has zero inbound rules. Outbound traffic is restricted to HTTPS, HTTP, and NTP. Tailscale handles all inbound connectivity through its encrypted mesh, so the instance never listens on a public port.
this.securityGroup = new ec2.SecurityGroup(this, "SecurityGroup", { vpc: this.vpc, description: "OpenClaw EC2 - zero inbound, restricted outbound", allowAllOutbound: false, });
A single SSM parameter (/openclaw/tailscale/auth-key) with a CHANGE_ME placeholder. CDK can't create SecureString parameters directly, so you replace it after the first deploy with aws ssm put-parameter --type SecureString. This is the only secret the stack manages — OpenClaw's own config (Slack tokens, LLM API keys) lives on the instance itself.
A t3.medium running Ubuntu 24.04 with a persistent EBS volume (30 GB, gp3, encrypted) mounted at /data. The volume has RemovalPolicy.RETAIN, so it survives instance replacements and even stack deletion.
The bootstrap script (bootstrap.sh) runs on first boot: it mounts the data volume (only formats if there's no existing filesystem), installs Docker, Node 22, OpenClaw, AWS CLI v2, and Tailscale. If the SSM parameter has a real auth key, Tailscale joins the tailnet automatically.
I used AWS Backup for this — a daily snapshot at 2:00 AM UTC with 7-day retention. It picks up the data volume automatically by looking for a backup=openclaw tag. No Lambda functions, no cron jobs to maintain.
https://openclaw-ec2.<tailnet>.ts.net/). Only devices on your tailnet can reach it.Everything is outbound-initiated. The security group has zero inbound rules.
You'll need:
git clone <repo-url> cd infra pnpm install pnpm exec cdk deploy
1. Replace the Tailscale auth key placeholder:
aws ssm delete-parameter --name "/openclaw/tailscale/auth-key" --region eu-central-1 aws ssm put-parameter \ --name "/openclaw/tailscale/auth-key" \ --value "tskey-auth-YOUR_KEY" \ --type SecureString --region eu-central-1
2. SSM into the instance and join Tailscale:
aws ssm start-session --target <INSTANCE_ID> --region eu-central-1 sudo -iu ubuntu sudo tailscale up --authkey="tskey-auth-YOUR_KEY" --hostname=openclaw-ec2
3. Run OpenClaw onboarding:
openclaw onboard --install-daemon
Pick loopback for the gateway bind and Serve for Tailscale exposure. Note that the --install-daemon flag will fail to create a systemd user service because SSM sessions don't have a D-Bus session — this is expected. Create a system-level service instead:
sudo tee /etc/systemd/system/openclaw-gateway.service > /dev/null <<'EOF' [Unit] Description=OpenClaw Gateway After=network-online.target tailscaled.service Wants=network-online.target [Service] Type=simple User=ubuntu Environment=PATH=/usr/bin:/bin ExecStart=/usr/bin/openclaw gateway --tailscale serve Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable --now openclaw-gateway
4. Open the dashboard at https://openclaw-ec2.<tailnet>.ts.net/, approve the browser pairing, and you're live.
There are a few things to keep in mind:
onboard command is interactive and can't be fully automated in cloud-init. The bootstrap script gets you 90% there. The rest is a one-time SSM session.The full CDK project is on GitHub: openclaw-self-hosted-aws

Helping teams build reliable cloud infrastructure — without the bloated bill.
My Other Blogs
© 2026 Dzhuneyt Ahmed. All rights reserved.