AWS EC2 Deployment

Scalable cloud deployment on Amazon EC2 with load balancing and auto-scaling

Platform: AWS
Cost: $10-50/month
Time: 1 hour
Difficulty: Advanced

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 require t3.small or 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

InstancevCPUsMemoryUse Case
t3.small22 GiBDevelopment, light agent usage
t3.medium24 GiBProduction, moderate throughput
t3.large28 GiBHigh 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 (see OPENCLAW_GATEWAY_TOKEN in 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/0 for 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.

InstancevCPUsRAMOn-Demand ($/hr)On-Demand ($/mo)1-Year Reserved ($/mo)Savings
t3.small22 GiB$0.0208~$15.18~$9.64~36%
t3.medium24 GiB$0.0416~$30.37~$19.27~37%
t3.large28 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:

  1. 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"
    
  2. Instance is still booting: Wait 1--2 minutes after launch. Check the instance state:
    aws ec2 describe-instance-status --instance-ids <INSTANCE_ID>
    
  3. 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:

  1. 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
    
  2. EBS volume limit reached: Check your account limits:
    aws service-quotas get-service-quota \
      --service-code ec2 \
      --quota-code L-D18FCD1D
    
  3. 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:

  1. Wrong username: Ubuntu AMIs use ubuntu, not ec2-user or root.
    ssh -i ~/.ssh/openclaw-key.pem ubuntu@<PUBLIC_IP>
    
  2. Wrong key file: Ensure you are using the key pair specified at launch.
  3. 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:

  1. Check the journal for error output:
    journalctl -u openclaw -n 50 --no-pager
    
  2. Verify the ExecStart path is correct. Since nvm installs Node.js per-user, the full path matters:
    which openclaw
    # Should output something like /home/openclaw/.nvm/versions/node/v20.x.x/bin/openclaw
    
    Update the ExecStart line in the service file to match.
  3. Verify the .env file exists, has correct permissions (600), and contains valid API keys.
  4. Test running OpenClaw manually as the openclaw user 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:

  1. 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>
    
  2. Check for runaway agent loops in the OpenClaw logs.
  3. 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.