Deploy OpenClaw on AWS EC2
A complete, step-by-step guide to deploying the OpenClaw AI agent framework on an Amazon EC2 instance. Covers instance provisioning, security hardening, service management, monitoring, and optional auto-scaling for production workloads.
Estimated time: 45--60 minutes Difficulty: Advanced Cost: $15--50/month depending on instance size and usage
Prerequisites
Before you begin, make sure you have the following:
- AWS account with billing enabled. A free-tier account works for initial testing with
t3.micro, but production workloads requiret3.smallor larger. - AWS CLI v2 installed and configured on your local machine. Verify with:
aws --version # Expected: aws-cli/2.x.x ... aws sts get-caller-identity # Should return your account ID without errors - An SSH client (built into macOS/Linux; use PuTTY or Windows Terminal on Windows).
- Basic familiarity with EC2, VPCs, and security groups. You do not need to be an AWS expert, but you should understand what an instance is and how key pairs work.
Step 1: Launch an EC2 Instance
1.1 Choose an AMI
Use the Ubuntu Server 22.04 LTS (Jammy Jellyfish) AMI. It provides long-term support through April 2027, broad community tooling, and is well-tested for Node.js workloads.
Find the latest AMI ID for your region with the AWS CLI:
aws ec2 describe-images \
--owners 099720109477 \
--filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" \
"Name=state,Values=available" \
--query "Images | sort_by(@, &CreationDate) | [-1].ImageId" \
--output text \
--region us-east-1
This returns the most recent Ubuntu 22.04 AMI ID (e.g., ami-0abcdef1234567890). Save this value for the launch command.
1.2 Select an Instance Type
| Instance | vCPUs | Memory | Use Case |
|---|---|---|---|
t3.small | 2 | 2 GiB | Development, light agent usage |
t3.medium | 2 | 4 GiB | Production, moderate throughput |
t3.large | 2 | 8 GiB | High concurrency, multiple agents |
Recommendation: Start with t3.small for testing. Move to t3.medium for production workloads where OpenClaw handles concurrent agent sessions or runs memory-intensive tools.
1.3 Create a Key Pair
aws ec2 create-key-pair \
--key-name openclaw-key \
--query "KeyMaterial" \
--output text > ~/.ssh/openclaw-key.pem
chmod 400 ~/.ssh/openclaw-key.pem
1.4 Create a Security Group
# Create the security group
aws ec2 create-security-group \
--group-name openclaw-sg \
--description "Security group for OpenClaw EC2 instance"
# Allow SSH from your IP only
MY_IP=$(curl -s https://checkip.amazonaws.com)
aws ec2 authorize-security-group-ingress \
--group-name openclaw-sg \
--protocol tcp \
--port 22 \
--cidr "${MY_IP}/32"
OpenClaw runs a web dashboard on port 18789 for chat, configuration, and execution approvals. Open this port restricted to your IP so you can access it remotely:
aws ec2 authorize-security-group-ingress \
--group-name openclaw-sg \
--protocol tcp \
--port 18789 \
--cidr "${MY_IP}/32"
Security note: The dashboard is an admin surface — do not open it to
0.0.0.0/0. Restrict it to your IP as shown above, or use SSH tunneling (ssh -L 18789:localhost:18789 ubuntu@YOUR_EC2_IP) to avoid exposing the port at all. Authentication is enforced via a gateway token (seeOPENCLAW_GATEWAY_TOKENin the environment setup below).
1.5 Launch the Instance
aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t3.small \
--key-name openclaw-key \
--security-groups openclaw-sg \
--block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":30,"VolumeType":"gp3"}}]' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=openclaw-server}]' \
--count 1
Replace the --image-id value with the AMI ID you retrieved in step 1.1. The gp3 EBS volume provides 3,000 baseline IOPS at no extra cost over gp2.
Wait for the instance to reach the running state:
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=openclaw-server" \
--query "Reservations[0].Instances[0].[InstanceId, State.Name, PublicIpAddress]" \
--output table
Step 2: Connect and Initial Setup
2.1 SSH into the Instance
ssh -i ~/.ssh/openclaw-key.pem ubuntu@<PUBLIC_IP>
Replace <PUBLIC_IP> with the IP address from the previous command.
2.2 Update System Packages
sudo apt update && sudo apt upgrade -y
2.3 Create a Non-Root User
Running services as root is a security risk. Create a dedicated user:
sudo adduser --disabled-password --gecos "" openclaw
sudo usermod -aG sudo openclaw
# Copy SSH authorized keys so you can log in as this user
sudo mkdir -p /home/openclaw/.ssh
sudo cp /home/ubuntu/.ssh/authorized_keys /home/openclaw/.ssh/
sudo chown -R openclaw:openclaw /home/openclaw/.ssh
sudo chmod 700 /home/openclaw/.ssh
sudo chmod 600 /home/openclaw/.ssh/authorized_keys
From now on, connect as the openclaw user:
ssh -i ~/.ssh/openclaw-key.pem openclaw@<PUBLIC_IP>
Step 3: Install Dependencies
3.1 Install Node.js 20 via nvm
# Install nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
# Install and use Node.js 20 LTS
nvm install 20
nvm alias default 20
node --version # Expected: v20.x.x
npm --version # Expected: 10.x.x
3.2 Install Git
sudo apt install -y git
git --version
3.3 Install Docker (Optional)
Docker is useful if you want to run OpenClaw in a container or use Docker-based tools within your agents.
# Install Docker using the official convenience script
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Allow the openclaw user to run Docker without sudo
sudo usermod -aG docker openclaw
newgrp docker
# Verify
docker --version
docker run hello-world
Step 4: Install and Configure OpenClaw
4.1 Install OpenClaw
npm install -g openclaw
openclaw --version
4.2 Configure Environment Variables
OpenClaw needs API keys for the language models it orchestrates. You have two options for managing secrets.
Option A: AWS Systems Manager Parameter Store (recommended for production)
Store secrets in Parameter Store and retrieve them at runtime. This avoids placing plaintext keys on disk.
# From your local machine (with AWS CLI configured):
aws ssm put-parameter \
--name "/openclaw/ANTHROPIC_API_KEY" \
--value "sk-ant-..." \
--type SecureString
aws ssm put-parameter \
--name "/openclaw/OPENAI_API_KEY" \
--value "sk-..." \
--type SecureString
On the EC2 instance, retrieve them into environment variables (requires an IAM role with ssm:GetParameter permission -- see Step 6):
export ANTHROPIC_API_KEY=$(aws ssm get-parameter \
--name "/openclaw/ANTHROPIC_API_KEY" \
--with-decryption \
--query "Parameter.Value" \
--output text)
export OPENAI_API_KEY=$(aws ssm get-parameter \
--name "/openclaw/OPENAI_API_KEY" \
--with-decryption \
--query "Parameter.Value" \
--output text)
Option B: .env file (simpler, suitable for development)
mkdir -p ~/openclaw-config
cat > ~/openclaw-config/.env << 'EOF'
ANTHROPIC_API_KEY=sk-ant-your-key-here
OPENAI_API_KEY=sk-your-key-here
OPENCLAW_LOG_LEVEL=info
OPENCLAW_PORT=3000
EOF
chmod 600 ~/openclaw-config/.env
4.3 Initialize and Verify
# If using .env:
cd ~/openclaw-config
openclaw init
# Quick verification
openclaw doctor
The openclaw doctor command checks that all required dependencies and API keys are properly configured.
Step 5: Run as a Service
A systemd service ensures OpenClaw starts on boot and restarts automatically if it crashes.
5.1 Create the Service File
sudo tee /etc/systemd/system/openclaw.service > /dev/null << 'EOF'
[Unit]
Description=OpenClaw AI Agent Framework
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=openclaw
Group=openclaw
WorkingDirectory=/home/openclaw/openclaw-config
EnvironmentFile=/home/openclaw/openclaw-config/.env
ExecStart=/home/openclaw/.nvm/versions/node/v20/bin/openclaw start
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=openclaw
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/openclaw/openclaw-config
[Install]
WantedBy=multi-user.target
EOF
If you are using Parameter Store (Option A), replace the EnvironmentFile line with an ExecStartPre script that fetches secrets and writes them to a temporary env file, or use a wrapper shell script as the ExecStart command.
5.2 Enable and Start
sudo systemctl daemon-reload
sudo systemctl enable openclaw
sudo systemctl start openclaw
# Verify status
sudo systemctl status openclaw
5.3 View Logs
# Follow live logs
journalctl -u openclaw -f
# View last 100 lines
journalctl -u openclaw -n 100 --no-pager
Step 6: Security Hardening
6.1 Security Group Best Practices
- Restrict SSH access to your IP or a bastion host CIDR. Never use
0.0.0.0/0for port 22. - Open only the ports you need. If OpenClaw does not serve HTTP traffic, do not open port 80 or 443.
- Use separate security groups for different roles (e.g., one for SSH, one for application traffic) and compose them on the instance.
- Review security group rules periodically with:
aws ec2 describe-security-groups \ --group-names openclaw-sg \ --query "SecurityGroups[0].IpPermissions"
6.2 Attach an IAM Role
An IAM instance profile lets the EC2 instance access AWS services (like Parameter Store or CloudWatch) without embedding long-lived credentials.
# Create the IAM policy
aws iam create-policy \
--policy-name OpenClawSSMReadOnly \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters"
],
"Resource": "arn:aws:ssm:*:*:parameter/openclaw/*"
}]
}'
# Create role and instance profile
aws iam create-role \
--role-name OpenClawEC2Role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
aws iam attach-role-policy \
--role-name OpenClawEC2Role \
--policy-arn arn:aws:iam::<ACCOUNT_ID>:policy/OpenClawSSMReadOnly
aws iam create-instance-profile \
--instance-profile-name OpenClawProfile
aws iam add-role-to-instance-profile \
--instance-profile-name OpenClawProfile \
--role-name OpenClawEC2Role
# Attach to the running instance
aws ec2 associate-iam-instance-profile \
--instance-id <INSTANCE_ID> \
--iam-instance-profile Name=OpenClawProfile
Replace <ACCOUNT_ID> and <INSTANCE_ID> with your actual values.
6.3 Disable Password Authentication
On the EC2 instance, ensure only key-based SSH is allowed:
sudo sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
6.4 Set Up CloudWatch Monitoring
Install the CloudWatch agent to stream logs and instance metrics:
# Download and install the agent
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
sudo dpkg -i amazon-cloudwatch-agent.deb
# Create a basic config
sudo tee /opt/aws/amazon-cloudwatch-agent/etc/config.json > /dev/null << 'CWEOF'
{
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/var/log/syslog",
"log_group_name": "/openclaw/syslog",
"log_stream_name": "{instance_id}",
"retention_in_days": 14
}
]
}
}
},
"metrics": {
"namespace": "OpenClaw",
"metrics_collected": {
"mem": { "measurement": ["mem_used_percent"] },
"disk": { "measurement": ["disk_used_percent"], "resources": ["*"] }
}
}
}
CWEOF
# Start the agent
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
-a fetch-config \
-m ec2 \
-c file:/opt/aws/amazon-cloudwatch-agent/etc/config.json \
-s
The IAM role from Step 6.2 must also include cloudwatch:PutMetricData and logs:CreateLogGroup, logs:CreateLogStream, logs:PutLogEvents permissions. Add those to the policy as needed.
Step 7: Elastic IP and Domain Setup
7.1 Assign an Elastic IP
By default, a public IP changes every time you stop and start the instance. An Elastic IP gives you a fixed address.
# Allocate an Elastic IP
ALLOC_ID=$(aws ec2 allocate-address \
--domain vpc \
--query "AllocationId" \
--output text)
# Associate it with your instance
aws ec2 associate-address \
--instance-id <INSTANCE_ID> \
--allocation-id $ALLOC_ID
# Confirm the new public IP
aws ec2 describe-addresses --allocation-ids $ALLOC_ID \
--query "Addresses[0].PublicIp" --output text
Elastic IPs are free while associated with a running instance. AWS charges approximately $0.005/hour for unattached Elastic IPs, so release them if you terminate the instance.
7.2 Optional: Route 53 Domain Setup
If you want a friendly DNS name (e.g., agent.yourdomain.com):
# Create an A record pointing to your Elastic IP
aws route53 change-resource-record-sets \
--hosted-zone-id <HOSTED_ZONE_ID> \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "agent.yourdomain.com",
"Type": "A",
"TTL": 300,
"ResourceRecords": [{"Value": "<ELASTIC_IP>"}]
}
}]
}'
Replace <HOSTED_ZONE_ID> and <ELASTIC_IP> with your actual values. A hosted zone in Route 53 costs $0.50/month plus $0.40 per million queries.
Step 8: Auto-Scaling (Optional)
For high availability or bursty workloads, wrap your instance in an Auto Scaling Group (ASG).
8.1 Create a Launch Template
aws ec2 create-launch-template \
--launch-template-name openclaw-template \
--launch-template-data '{
"ImageId": "ami-0abcdef1234567890",
"InstanceType": "t3.medium",
"KeyName": "openclaw-key",
"SecurityGroupIds": ["sg-xxxxxxxxx"],
"IamInstanceProfile": {"Name": "OpenClawProfile"},
"UserData": "'$(base64 -w 0 <<'USERDATA'
#!/bin/bash
apt update && apt upgrade -y
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm install 20
npm install -g openclaw
# Add your startup configuration here
USERDATA
)'"
}'
8.2 Create the Auto Scaling Group
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name openclaw-asg \
--launch-template LaunchTemplateName=openclaw-template,Version='$Latest' \
--min-size 1 \
--max-size 3 \
--desired-capacity 1 \
--vpc-zone-identifier "subnet-aaaa,subnet-bbbb" \
--health-check-type EC2 \
--health-check-grace-period 300 \
--tags Key=Name,Value=openclaw-asg-instance,PropagateAtLaunch=true
8.3 Add a Scaling Policy
aws autoscaling put-scaling-policy \
--auto-scaling-group-name openclaw-asg \
--policy-name openclaw-cpu-scale-out \
--policy-type TargetTrackingScaling \
--target-tracking-configuration '{
"TargetValue": 60.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ASGAverageCPUUtilization"
}
}'
This scales out when average CPU exceeds 60% and scales in when it drops below that threshold.
Cost Breakdown
All prices below are for the us-east-1 region running Linux instances. Prices are approximate and should be verified against the AWS EC2 Pricing page.
| Instance | vCPUs | RAM | On-Demand ($/hr) | On-Demand ($/mo) | 1-Year Reserved ($/mo) | Savings |
|---|---|---|---|---|---|---|
t3.small | 2 | 2 GiB | $0.0208 | ~$15.18 | ~$9.64 | ~36% |
t3.medium | 2 | 4 GiB | $0.0416 | ~$30.37 | ~$19.27 | ~37% |
t3.large | 2 | 8 GiB | $0.0832 | ~$60.74 | ~$38.54 | ~37% |
Additional costs to consider:
- EBS storage (gp3): $0.08/GB/month. A 30 GB volume costs ~$2.40/month.
- Data transfer out: First 100 GB/month are free (as of the AWS Free Tier for data transfer). Beyond that, ~$0.09/GB.
- Elastic IP: Free while attached to a running instance. ~$3.60/month if unattached.
- Route 53: $0.50/month per hosted zone.
- CloudWatch: Basic monitoring is free. Detailed metrics and custom metrics start at $0.30/metric/month.
Estimated monthly total for a typical production setup (t3.medium + 30 GB gp3 + Elastic IP): approximately $33/month on-demand, or $22/month with a 1-year reserved instance.
Troubleshooting
Connection Refused When SSHing
Symptom: ssh: connect to host <IP> port 22: Connection refused
Possible causes and fixes:
- Security group does not allow your IP: Verify your current IP matches the ingress rule.
curl -s https://checkip.amazonaws.com aws ec2 describe-security-groups --group-names openclaw-sg \ --query "SecurityGroups[0].IpPermissions[?ToPort==\`22\`].IpRanges" - Instance is still booting: Wait 1--2 minutes after launch. Check the instance state:
aws ec2 describe-instance-status --instance-ids <INSTANCE_ID> - Wrong IP address: If you stopped and started (not rebooted) the instance without an Elastic IP, the public IP changed. Re-check with
describe-instances.
Instance Will Not Start or Gets Stuck
Symptom: Instance stays in pending state or moves to stopped immediately.
Possible causes and fixes:
- Insufficient capacity: The availability zone may be out of capacity for that instance type. Try launching in a different AZ:
aws ec2 run-instances \ --placement AvailabilityZone=us-east-1b \ ... # same parameters as before - EBS volume limit reached: Check your account limits:
aws service-quotas get-service-quota \ --service-code ec2 \ --quota-code L-D18FCD1D - Invalid AMI: Ensure the AMI ID exists in your region. AMI IDs are region-specific.
Permission Denied (Public Key)
Symptom: Permission denied (publickey) when SSHing.
Possible causes and fixes:
- Wrong username: Ubuntu AMIs use
ubuntu, notec2-userorroot.ssh -i ~/.ssh/openclaw-key.pem ubuntu@<PUBLIC_IP> - Wrong key file: Ensure you are using the key pair specified at launch.
- Key file permissions too open: The private key must have restrictive permissions.
chmod 400 ~/.ssh/openclaw-key.pem
OpenClaw Service Fails to Start
Symptom: systemctl status openclaw shows failed.
Steps to diagnose:
- Check the journal for error output:
journalctl -u openclaw -n 50 --no-pager - Verify the
ExecStartpath is correct. Since nvm installs Node.js per-user, the full path matters:
Update thewhich openclaw # Should output something like /home/openclaw/.nvm/versions/node/v20.x.x/bin/openclawExecStartline in the service file to match. - Verify the
.envfile exists, has correct permissions (600), and contains valid API keys. - Test running OpenClaw manually as the
openclawuser before relying on systemd:sudo -u openclaw bash -c 'source /home/openclaw/.nvm/nvm.sh && openclaw start'
High CPU or Memory Usage
If CloudWatch or htop shows resource exhaustion:
- Upgrade the instance type without losing data:
aws ec2 stop-instances --instance-ids <INSTANCE_ID> aws ec2 modify-instance-attribute \ --instance-id <INSTANCE_ID> \ --instance-type '{"Value":"t3.medium"}' aws ec2 start-instances --instance-ids <INSTANCE_ID> - Check for runaway agent loops in the OpenClaw logs.
- Add swap space as a temporary buffer:
sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Next Steps
Once your OpenClaw instance is running:
- Set up HTTPS with a reverse proxy (nginx + Let's Encrypt) to secure access to the OpenClaw dashboard.
- Configure backups using EBS snapshots scheduled via AWS Data Lifecycle Manager.
- Enable AWS Systems Manager Session Manager for browser-based shell access without opening port 22.
- Join the OpenClaw community at openclaw.directory to discover skills, templates, and workflow recipes.