Skip to content

Instantly share code, notes, and snippets.

@nabilfreeman
Last active January 4, 2026 18:16
Show Gist options
  • Select an option

  • Save nabilfreeman/6f5d7367411fdf428be27f5126ce1ff1 to your computer and use it in GitHub Desktop.

Select an option

Save nabilfreeman/6f5d7367411fdf428be27f5126ce1ff1 to your computer and use it in GitHub Desktop.
Bypass Internet censorship in authoritarian countries like the United Kingdom. Recipe to deploy a V2Ray on AWS fully autonomously. All you need is AWS CLI authenticated with an admin role. Designed to be used with an agent like Claude Code, Codex etc. Paste the entire file to them, and they will deploy a server for you!

V2Ray Server Deployment Playbook

Complete guide for deploying a V2Ray VLESS-H2-TLS server on AWS EC2 using Systems Manager (SSM)


Table of Contents

  1. Prerequisites
  2. Variables to Customize
  3. Step-by-Step Deployment
  4. DNS Configuration
  5. SSM Communication Guide
  6. Clash Client Configuration
  7. Management Commands
  8. Troubleshooting
  9. Cost Information

Prerequisites

Before starting the deployment, ensure you have:

Required Software & Tools

  • AWS CLI installed and configured with appropriate credentials
  • Domain name with ability to create DNS A records
  • jq (JSON processor) for parsing AWS CLI outputs (optional but recommended)
  • SSH client (optional, we use SSM instead)

AWS Account Requirements

  • Active AWS account with billing enabled
  • IAM permissions to:
    • Create IAM roles and policies
    • Launch EC2 instances
    • Create security groups
    • Use Systems Manager (SSM)

Knowledge Requirements

  • Basic understanding of:
    • AWS EC2 and IAM
    • DNS configuration
    • Command-line operations
    • VPN/proxy concepts

Variables to Customize

IMPORTANT: Replace these variables with your own values before running commands.

Variable Example Value Description
DOMAIN_NAME my.server.com Your fully qualified domain name
INSTANCE_NAME V2Ray-Server Name tag for your EC2 instance
REGION us-east-1 AWS region for deployment
KEY_PAIR your-key-name EC2 key pair name (optional with SSM)
AMI_ID ami-0e2c8caa4b6378d8c Ubuntu 22.04 LTS AMI for your region
ROLE_NAME V2Ray-SSM-Role IAM role name for SSM access
SECURITY_GROUP_NAME V2Ray-Security-Group Security group name

Step-by-Step Deployment

Step 1: Create IAM Role for SSM Access

This role allows the EC2 instance to communicate with AWS Systems Manager without requiring SSH access.

1.1 Create Trust Policy File

Create ec2-trust-policy.json:

cat > ec2-trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "ec2.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}
EOF

1.2 Create IAM Role

aws iam create-role \
  --role-name V2Ray-SSM-Role \
  --assume-role-policy-document file://ec2-trust-policy.json \
  --description "Role for V2Ray EC2 instance to use SSM"

1.3 Attach SSM Managed Policy

aws iam attach-role-policy \
  --role-name V2Ray-SSM-Role \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

1.4 Create Instance Profile

aws iam create-instance-profile \
  --instance-profile-name V2Ray-SSM-InstanceProfile

1.5 Add Role to Instance Profile

aws iam add-role-to-instance-profile \
  --instance-profile-name V2Ray-SSM-InstanceProfile \
  --role-name V2Ray-SSM-Role

⏱️ Wait time: 10-15 seconds for IAM propagation

sleep 15

Step 2: Create Security Group

Configure firewall rules to allow HTTP (80), HTTPS (443), and optionally SSH (22).

2.1 Get Your VPC ID

VPC_ID=$(aws ec2 describe-vpcs \
  --filters "Name=isDefault,Values=true" \
  --query "Vpcs[0].VpcId" \
  --output text)

echo "VPC ID: $VPC_ID"

2.2 Create Security Group

SG_ID=$(aws ec2 create-security-group \
  --group-name V2Ray-Security-Group \
  --description "Security group for V2Ray server - HTTPS and HTTP" \
  --vpc-id $VPC_ID \
  --query 'GroupId' \
  --output text)

echo "Security Group ID: $SG_ID"

2.3 Add Firewall Rules

# Allow HTTPS (443) - Required for V2Ray
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0

# Allow HTTP (80) - Required for Let's Encrypt certificate validation
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol tcp \
  --port 80 \
  --cidr 0.0.0.0/0

# Optional: Allow SSH (22) - Only if you need direct SSH access
# aws ec2 authorize-security-group-ingress \
#   --group-id $SG_ID \
#   --protocol tcp \
#   --port 22 \
#   --cidr 0.0.0.0/0

Step 3: Create User Data Script

This script runs automatically when the instance boots, installing necessary packages and configuring the SSM agent.

Create user-data.sh:

cat > user-data.sh << 'EOF'
#!/bin/bash
set -x
exec > >(tee /var/log/user-data.log) 2>&1
apt-get update
apt-get upgrade -y
apt-get install -y curl wget unzip expect
snap start amazon-ssm-agent || systemctl start snap.amazon-ssm-agent.amazon-ssm-agent.service
systemctl enable snap.amazon-ssm-agent.amazon-ssm-agent.service
timedatectl set-timezone UTC
apt-get install -y chrony
systemctl enable chrony
systemctl start chrony
echo "User data completed at $(date)" >> /var/log/user-data.log
EOF

What this script does:

  • Updates system packages
  • Installs curl, wget, unzip, and expect (needed for V2Ray automation)
  • Starts and enables SSM agent
  • Configures UTC timezone
  • Installs and enables chrony for time synchronization (critical for TLS)

Step 4: Launch EC2 Instance

4.1 Find Ubuntu 22.04 AMI (Optional)

aws ec2 describe-images \
  --owners 099720109477 \
  --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" \
  --query "Images | sort_by(@, &CreationDate) | [-1].[ImageId,Name,CreationDate]" \
  --output table

4.2 Launch Instance

INSTANCE_ID=$(aws ec2 run-instances \
  --image-id ami-0e2c8caa4b6378d8c \
  --instance-type t3.micro \
  --security-group-ids $SG_ID \
  --iam-instance-profile Name=V2Ray-SSM-InstanceProfile \
  --user-data file://user-data.sh \
  --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=V2Ray-Server}]" \
  --block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":8,"VolumeType":"gp3","DeleteOnTermination":true}}]' \
  --query 'Instances[0].InstanceId' \
  --output text)

echo "Instance ID: $INSTANCE_ID"

Instance specifications:

  • Type: t3.micro (2 vCPUs, 1 GB RAM) - recommended for reliable VPN operation
  • Storage: 8 GB GP3 (cost-effective)
  • OS: Ubuntu 22.04 LTS

⚠️ CRITICAL WARNING: While t3.nano (0.5 GB RAM) can technically run V2Ray, it may experience memory pressure and potential OOM (Out Of Memory) issues under moderate load. The t3.micro with 1 GB RAM is the recommended minimum for stable operation and only costs an additional ~$3.80/month.

4.3 Wait for Instance to Run

aws ec2 wait instance-running --instance-ids $INSTANCE_ID
echo "Instance is running!"

4.4 Get Instance Public IP

PUBLIC_IP=$(aws ec2 describe-instances \
  --instance-ids $INSTANCE_ID \
  --query 'Reservations[0].Instances[0].PublicIpAddress' \
  --output text)

echo "Public IP: $PUBLIC_IP"

⚠️ Important: This auto-assigned IP will change if you stop/start the instance. You'll allocate a permanent Elastic IP in the next step.


Step 5: Allocate and Associate Elastic IP (Static IP)

An Elastic IP provides a permanent, static public IP address that won't change when you stop/start your instance. This is critical for maintaining consistent DNS configuration.

5.1 Why Elastic IP is Required

Benefits:

  • Persistent IP: IP address remains constant even after instance stop/start
  • No DNS updates needed: Your DNS configuration never needs to change
  • Cost-effective: FREE while associated with a running instance
  • Professional setup: Standard practice for production deployments

Without Elastic IP:

  • IP changes every time you stop/start the instance
  • Must update DNS records after each IP change
  • DNS propagation delays (5-60 minutes)
  • Service interruptions during DNS updates

5.2 Attach Elastic IP Policy to IAM Role (Optional but Recommended)

If you want to manage Elastic IPs programmatically, attach the Elastic IP policy to your IAM role.

Create elastic-ip-policy.json:

cat > elastic-ip-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:AllocateAddress",
        "ec2:AssociateAddress",
        "ec2:DescribeAddresses",
        "ec2:DisassociateAddress",
        "ec2:ReleaseAddress"
      ],
      "Resource": "*"
    }
  ]
}
EOF

Attach to your IAM role:

# Create the policy
POLICY_ARN=$(aws iam create-policy \
  --policy-name V2Ray-ElasticIP-Policy \
  --policy-document file://elastic-ip-policy.json \
  --query 'Policy.Arn' \
  --output text)

# Attach to role
aws iam attach-role-policy \
  --role-name V2Ray-SSM-Role \
  --policy-arn $POLICY_ARN

5.3 Allocate Elastic IP

# Allocate a new Elastic IP
ELASTIC_IP_ALLOCATION=$(aws ec2 allocate-address \
  --domain vpc \
  --query 'AllocationId' \
  --output text)

echo "Elastic IP Allocation ID: $ELASTIC_IP_ALLOCATION"

# Get the actual IP address
ELASTIC_IP=$(aws ec2 describe-addresses \
  --allocation-ids $ELASTIC_IP_ALLOCATION \
  --query 'Addresses[0].PublicIp' \
  --output text)

echo "Elastic IP Address: $ELASTIC_IP"

5.4 Associate Elastic IP with Instance

# Associate the Elastic IP with your instance
ASSOCIATION_ID=$(aws ec2 associate-address \
  --instance-id $INSTANCE_ID \
  --allocation-id $ELASTIC_IP_ALLOCATION \
  --query 'AssociationId' \
  --output text)

echo "Association ID: $ASSOCIATION_ID"
echo "✅ Elastic IP $ELASTIC_IP is now associated with instance $INSTANCE_ID"

5.5 Verify Elastic IP Assignment

# Verify the Elastic IP is associated
aws ec2 describe-addresses \
  --allocation-ids $ELASTIC_IP_ALLOCATION \
  --query 'Addresses[0].[PublicIp,InstanceId,AssociationId]' \
  --output table

Expected output: Should show your Elastic IP, instance ID, and association ID.

5.6 Save Elastic IP for DNS Configuration

echo "🎉 Your permanent static IP is: $ELASTIC_IP"
echo "Use this IP address for your DNS A record in the next step."

💡 Important Notes:

  • Cost: $0.00/month while associated with a running instance
  • Cost if unassociated: ~$3.60/month if you stop the instance but keep the IP
  • Best practice: Always associate Elastic IPs with running instances or release them when not needed
  • This IP is permanent: Use this IP (not the auto-assigned one) for all DNS configuration

Step 6: Configure DNS

Configure your domain to point to the EC2 instance using the Elastic IP.

6.1 Create A Record

You must manually configure an A record in your DNS provider to point your domain to the Elastic IP address.

Create the following DNS record:

Type: A
Name: my.server.com (or your subdomain)
Value: [ELASTIC_IP from Step 5.6]
TTL: 300 (5 minutes) or Auto

⚠️ CRITICAL:

  • Use the Elastic IP (not the auto-assigned IP from Step 4.4)
  • The Elastic IP will NEVER change, even if you stop/start the instance
  • If your DNS provider offers proxy/CDN features (like Cloudflare), ensure they are DISABLED
  • V2Ray uses its own TLS encryption and will not work through a proxy
  • Set to "DNS only" mode (gray cloud in Cloudflare, not orange)

6.2 Verify DNS Propagation

# Check DNS resolution
dig +short my.server.com

# Or use nslookup
nslookup my.server.com

# Should return your PUBLIC_IP

⏱️ Wait time: 1-5 minutes for DNS propagation (depends on TTL)


Step 7: Wait for SSM Agent Registration

The instance needs time to boot, run user-data script, and register with Systems Manager.

7.1 Check SSM Agent Status

aws ssm describe-instance-information \
  --filters "Key=InstanceIds,Values=$INSTANCE_ID" \
  --query "InstanceInformationList[0].PingStatus" \
  --output text

Expected output: Online

7.2 Wait Loop (if needed)

echo "Waiting for SSM agent to come online..."
while true; do
  STATUS=$(aws ssm describe-instance-information \
    --filters "Key=InstanceIds,Values=$INSTANCE_ID" \
    --query "InstanceInformationList[0].PingStatus" \
    --output text 2>/dev/null)

  if [ "$STATUS" = "Online" ]; then
    echo "✅ SSM agent is online!"
    break
  fi

  echo "Status: $STATUS - waiting 10 seconds..."
  sleep 10
done

⏱️ Wait time: 2-5 minutes typically


Step 8: Install V2Ray Using 233boy Script

Install V2Ray using the popular one-click installation script.

8.1 Send Installation Command

COMMAND_ID=$(aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["bash <(wget -qO- -o- https://github.com/233boy/v2ray/raw/master/install.sh)"]' \
  --query "Command.CommandId" \
  --output text)

echo "Command ID: $COMMAND_ID"

8.2 Wait for Installation to Complete

aws ssm wait command-executed \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID

echo "✅ Installation complete!"

8.3 Check Installation Output

aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID \
  --query "StandardOutputContent" \
  --output text

⏱️ Wait time: 1-2 minutes


Step 9: Configure V2Ray with VLESS-H2-TLS

Use the expect script to automate the interactive V2Ray configuration.

9.1 Create Expect Script

Create v2ray-ws-tls-v2.exp:

cat > v2ray-ws-tls-v2.exp << 'EOF'
#!/usr/bin/expect -f
set timeout 300
spawn v2ray change
expect "请选择" {
    send "1\r"
}
expect "请选择协议" {
    send "7\r"
}
expect "请输入域名" {
    send "my.server.com\r"
}
expect eof
EOF

What this does:

  • Option 1: Modify transport/protocol
  • Option 7: Select VLESS-H2-TLS protocol
  • Input domain name for TLS certificate

⚠️ Important: Replace my.server.com with your actual domain name!

9.2 Base64 Encode the Script

cat v2ray-ws-tls-v2.exp | base64 > v2ray-ws-tls-v2.exp.b64

9.3 Send Configuration Command

COMMAND_ID=$(aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters commands=["cat > /tmp/v2ray-config.exp << 'EOFEXP'
$(cat v2ray-ws-tls-v2.exp)
EOFEXP
","chmod +x /tmp/v2ray-config.exp","expect /tmp/v2ray-config.exp"] \
  --query "Command.CommandId" \
  --output text)

echo "Configuration Command ID: $COMMAND_ID"

9.4 Wait for Configuration

aws ssm wait command-executed \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID

echo "✅ Configuration complete!"

9.5 Check Configuration Output

aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID \
  --query "StandardOutputContent" \
  --output text

⏱️ Wait time: 2-3 minutes (includes Let's Encrypt certificate generation)


Step 10: Extract Configuration Details

Get the connection details needed for your Clash client.

10.1 Get V2Ray URL

COMMAND_ID=$(aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray url"]' \
  --query "Command.CommandId" \
  --output text)

aws ssm wait command-executed \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID

aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID \
  --query "StandardOutputContent" \
  --output text

10.2 Get Full Configuration Info

COMMAND_ID=$(aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray info"]' \
  --query "Command.CommandId" \
  --output text)

aws ssm wait command-executed \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID

aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID \
  --query "StandardOutputContent" \
  --output text

Save the output - you'll need these details for Clash configuration.


Step 11: Verify Service Status

Ensure V2Ray is running correctly.

COMMAND_ID=$(aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray status"]' \
  --query "Command.CommandId" \
  --output text)

aws ssm wait command-executed \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID

aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID \
  --query "StandardOutputContent" \
  --output text

Expected output: V2Ray should be running (active)


DNS Configuration

DNS Setup Instructions

You must manually configure an A record in your DNS provider to point your domain to the EC2 public IP address.

Basic requirements:

  • Create an A record
  • Point it to your EC2 instance's public IP
  • Disable any proxy/CDN features (if offered by your provider)
  • Use a low TTL (300 seconds) for easier updates

⚠️ Critical: If your DNS provider offers proxy/CDN features (like Cloudflare's orange cloud), ensure they are DISABLED. V2Ray uses its own TLS encryption and will not work through a proxy.


SSM Communication Guide

AWS Systems Manager (SSM) allows you to execute commands on EC2 instances without SSH access.

How to Send Commands

Method 1: Using AWS CLI (Recommended)

# Send a command
COMMAND_ID=$(aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["YOUR_COMMAND_HERE"]' \
  --query "Command.CommandId" \
  --output text)

# Wait for completion
aws ssm wait command-executed \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID

# Get output
aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID \
  --query "StandardOutputContent" \
  --output text

Method 2: Using AWS Console

  1. Go to AWS Systems Manager Console
  2. Click Session Manager or Run Command
  3. Select AWS-RunShellScript
  4. Select your instance
  5. Enter commands in the text box
  6. Click Run
  7. View output in the command history

How to Check Command Status

# Check if command finished
aws ssm list-commands \
  --command-id $COMMAND_ID \
  --query "Commands[0].Status" \
  --output text

Possible statuses:

  • Pending - Command is queued
  • InProgress - Command is executing
  • Success - Command completed successfully
  • Failed - Command failed
  • TimedOut - Command exceeded timeout
  • Cancelled - Command was cancelled

How to Retrieve Command Output

# Get standard output
aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID \
  --query "StandardOutputContent" \
  --output text

# Get error output
aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID \
  --query "StandardErrorContent" \
  --output text

# Get full details (JSON)
aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID

Interactive SSM Session

For interactive shell access (like SSH but through SSM):

aws ssm start-session --target $INSTANCE_ID

Requirements:


Clash Client Configuration

VLESS-H2-TLS Format for Clash

Add this proxy configuration to your clash.yaml file:

proxies:
  # V2Ray Server with VLESS-H2-TLS
  - name: My V2Ray Server (us-east-1)
    server: my.server.com
    port: 443
    type: vless
    uuid: YOUR-UUID-HERE
    udp: true
    tls: true
    servername: my.server.com
    alpn:
      - h2
    network: h2
    h2-opts:
      host:
        - my.server.com
      path: /YOUR-UUID-HERE

Complete Clash Configuration Example

mixed-port: 7890
allow-lan: true
mode: Rule
log-level: info
external-controller: :9090

proxies:
  # us-east-1 V2Ray Server
  - {
      name: My V2Ray Server (us-east-1),
      server: my.server.com,
      port: 443,
      type: vless,
      uuid: YOUR-UUID-HERE,
      udp: true,
      tls: true,
      servername: my.server.com,
      alpn: [h2],
      network: h2,
      h2-opts: { host: [my.server.com], path: /YOUR-UUID-HERE },
    }

proxy-groups:
  # Master proxy selector
  - name: 🌏 Select Servers...
    type: select
    proxies:
      - My V2Ray Server (us-east-1)

rules:
  - MATCH,🌏 Select Servers...

How to Get Your UUID and Path

Run this command to extract your configuration:

COMMAND_ID=$(aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray info"]' \
  --query "Command.CommandId" \
  --output text)

aws ssm wait command-executed \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID

aws ssm get-command-invocation \
  --command-id $COMMAND_ID \
  --instance-id $INSTANCE_ID \
  --query "StandardOutputContent" \
  --output text

Look for:

  • UUID: Usually in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  • Path: Usually /YOUR-UUID

Testing Clash Connection

  1. Start Clash with your config:

    clash -f clash.yaml
  2. Test the proxy:

    curl -x http://127.0.0.1:7890 https://api.ipify.org?format=json
  3. Should return your server's IP, not your local IP


Management Commands

V2Ray Service Commands

All commands are executed via SSM:

Check V2Ray Status

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray status"]'

Restart V2Ray Service

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray restart"]'

Stop V2Ray Service

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray stop"]'

Start V2Ray Service

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray start"]'

Get Configuration Info

Display Connection URL

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray url"]'

Display Full Configuration

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray info"]'

Show QR Code

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray qr"]'

View Logs

V2Ray Access Logs

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["tail -n 100 /var/log/v2ray/access.log"]'

V2Ray Error Logs

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["tail -n 100 /var/log/v2ray/error.log"]'

User Data Script Logs

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["cat /var/log/user-data.log"]'

System Logs (systemd)

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["journalctl -u v2ray -n 100"]'

Update/Modify Configuration

Change Protocol/Transport

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray change"]'

Modify Port

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["v2ray port 8443"]'

System Maintenance

Update V2Ray

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["bash <(wget -qO- -o- https://github.com/233boy/v2ray/raw/master/install.sh)"]'

Check System Resources

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["free -h && df -h && uptime"]'

Check Network Connectivity

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["netstat -tlnp | grep v2ray"]'

Troubleshooting

Common Issues and Solutions

Issue 1: SSM Agent Not Online

Symptoms:

  • describe-instance-information returns empty or shows ConnectionLost
  • Cannot send commands via SSM

Solutions:

  1. Check IAM Role: Ensure instance has the correct IAM role attached

    aws ec2 describe-instances \
      --instance-ids $INSTANCE_ID \
      --query 'Reservations[0].Instances[0].IamInstanceProfile'
  2. Verify SSM Agent Status via SSH (if you have SSH access):

    systemctl status snap.amazon-ssm-agent.amazon-ssm-agent.service
  3. Restart SSM Agent:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["systemctl restart snap.amazon-ssm-agent.amazon-ssm-agent.service"]'
  4. Check instance system log:

    aws ec2 get-console-output --instance-id $INSTANCE_ID

Issue 2: V2Ray Not Starting

Symptoms:

  • v2ray status shows inactive/failed
  • Cannot connect to server

Solutions:

  1. Check service status:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["systemctl status v2ray"]'
  2. Check error logs:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["journalctl -u v2ray -n 50"]'
  3. Verify configuration file:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["cat /etc/v2ray/config.json"]'
  4. Restart V2Ray:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["v2ray restart"]'

Issue 3: TLS Certificate Issues

Symptoms:

  • Connection errors related to TLS/SSL
  • Certificate verification failures

Solutions:

  1. Verify DNS is resolving correctly:

    dig +short my.server.com

    Should return your EC2 instance's public IP

  2. Check certificate status:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["ls -la /root/.acme.sh/my.server.com/"]'
  3. Renew certificate manually:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["/root/.acme.sh/acme.sh --renew -d my.server.com --force"]'
  4. Check time synchronization (critical for TLS):

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["timedatectl status"]'

Issue 4: Cannot Connect from Client

Symptoms:

  • Clash shows connection timeout or failure
  • curl tests fail

Solutions:

  1. Verify security group rules:

    aws ec2 describe-security-groups \
      --group-ids $SG_ID \
      --query 'SecurityGroups[0].IpPermissions'

    Ensure ports 80 and 443 are open

  2. Test network connectivity:

    # Test HTTP
    curl -I http://my.server.com
    
    # Test HTTPS
    curl -I https://my.server.com
  3. Verify V2Ray is listening:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["netstat -tlnp | grep 443"]'
  4. Check V2Ray access logs:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["tail -f /var/log/v2ray/access.log"]'

Issue 5: Clash Configuration Not Working

Symptoms:

  • Clash accepts config but connections fail
  • Wrong IP reported by curl -x

Solutions:

  1. Verify UUID and path match server:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["v2ray info"]'

    Compare UUID and path with your Clash config

  2. Ensure correct protocol type:

    • Must be vless, not vmess
    • Network must be h2 (HTTP/2)
    • TLS must be enabled
  3. Check Clash logs:

    clash -f clash.yaml -d .

    Look for connection errors or TLS issues

  4. Test with minimal config:

    proxies:
      - name: Test
        server: my.server.com
        port: 443
        type: vless
        uuid: YOUR-UUID
        udp: true
        tls: true
        network: h2

Issue 6: Instance Public IP Changed

Symptoms:

  • DNS points to old IP
  • Connection was working, now fails

Note: This issue should NOT occur if you followed the deployment guide and allocated an Elastic IP in Step 5. Elastic IPs never change.

Solutions:

  1. Check if you have an Elastic IP:

    aws ec2 describe-addresses \
      --filters "Name=instance-id,Values=$INSTANCE_ID" \
      --query 'Addresses[0].[PublicIp,AllocationId]' \
      --output table
  2. If no Elastic IP, allocate one now (recommended):

    # Allocate Elastic IP
    ALLOCATION_ID=$(aws ec2 allocate-address \
      --domain vpc \
      --query 'AllocationId' \
      --output text)
    
    # Associate with instance
    aws ec2 associate-address \
      --instance-id $INSTANCE_ID \
      --allocation-id $ALLOCATION_ID
    
    # Get the new Elastic IP
    ELASTIC_IP=$(aws ec2 describe-addresses \
      --allocation-ids $ALLOCATION_ID \
      --query 'Addresses[0].PublicIp' \
      --output text)
    echo "Your permanent Elastic IP: $ELASTIC_IP"
  3. Update DNS A record to point to the Elastic IP (see Step 6: Configure DNS)

Issue 7: Out Of Memory (OOM) Errors

Symptoms:

  • V2Ray service crashes unexpectedly
  • Instance becomes unresponsive
  • System logs show OOM killer messages

Note: This issue is significantly reduced with t3.micro (1 GB RAM) compared to t3.nano (0.5 GB RAM). If you're using t3.nano, upgrade to t3.micro.

Solutions:

  1. Check for OOM events:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["dmesg | grep -i \"out of memory\"","journalctl --since \"1 hour ago\" | grep -i oom"]'
  2. Upgrade to t3.micro (if using t3.nano):

    # Stop instance
    aws ec2 stop-instances --instance-ids $INSTANCE_ID
    aws ec2 wait instance-stopped --instance-ids $INSTANCE_ID
    
    # Upgrade instance type
    aws ec2 modify-instance-attribute \
      --instance-id $INSTANCE_ID \
      --instance-type "{\"Value\": \"t3.micro\"}"
    
    # Start instance
    aws ec2 start-instances --instance-ids $INSTANCE_ID
  3. Monitor memory usage:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["free -h","ps aux --sort=-%mem | head -10"]'

Performance Issues

Slow Connection Speed

  1. Check instance CPU/memory:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["top -bn1 | head -20"]'
  2. Upgrade instance type if needed:

    # Stop instance
    aws ec2 stop-instances --instance-ids $INSTANCE_ID
    aws ec2 wait instance-stopped --instance-ids $INSTANCE_ID
    
    # Change instance type
    aws ec2 modify-instance-attribute \
      --instance-id $INSTANCE_ID \
      --instance-type "{\"Value\": \"t3.small\"}"
    
    # Start instance
    aws ec2 start-instances --instance-ids $INSTANCE_ID
  3. Consider different AWS region:

    • Choose region closer to your physical location
    • Test with ping to compare latency

Debug Mode

Enable V2Ray debug logging:

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["sed -i \"s/\\\"loglevel\\\": \\\"warning\\\"/\\\"loglevel\\\": \\\"debug\\\"/\" /etc/v2ray/config.json && v2ray restart"]'

View debug logs:

aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["tail -f /var/log/v2ray/error.log"]'

Cost Information

AWS EC2 Pricing Breakdown

Instance Costs (t3.micro)

On-Demand Pricing (us-east-1):

  • Hourly: $0.0104
  • Daily: $0.2496 (24 hours)
  • Monthly: ~$7.60 (730 hours)

Other regions may vary:

  • eu-west-1 (Ireland): $0.0116/hour ($8.47/month)
  • ap-southeast-1 (Singapore): $0.0128/hour ($9.34/month)

Comparison with t3.nano:

Instance Type RAM Monthly Cost Recommended Use
t3.nano 0.5 GB ~$3.80 Not recommended - may experience OOM
t3.micro 1 GB ~$7.60 Recommended - stable VPN operation
t3.small 2 GB ~$15.20 Overkill for personal VPN use

Why t3.micro? The additional ~$3.80/month provides double the RAM (1 GB vs 0.5 GB), significantly reducing the risk of Out Of Memory (OOM) errors that can crash your VPN server unexpectedly.

Storage Costs (GP3)

  • 8 GB GP3 volume: ~$0.64/month
  • IOPS: 3,000 baseline (included, sufficient for VPN)
  • Throughput: 125 MB/s (included)

Data Transfer Costs

Outbound data transfer:

  • First 100 GB/month: $0.09/GB ($9.00 max)
  • Next 10 TB/month: $0.085/GB
  • Over 10 TB/month: Lower rates apply

Inbound data transfer:

  • FREE (all traffic to AWS)

Example usage scenarios:

Usage Level Monthly Transfer Transfer Cost Total Cost
Light 20 GB $1.80 ~$6.24
Moderate 50 GB $4.50 ~$8.94
Heavy 100 GB $9.00 ~$13.44

Additional Costs

  • Elastic IP (recommended): $0.00/month while associated with running instance, ~$3.60/month if instance stopped but IP reserved
  • SSM usage: FREE (no additional cost for Systems Manager)
  • CloudWatch logs: First 5 GB ingestion FREE, then $0.50/GB

Note: With the new t3.micro standard and recommended Elastic IP setup, there are no additional monthly costs beyond the instance and data transfer when your instance is running.

Total Monthly Cost Estimate

Recommended setup (with Elastic IP, light usage):

EC2 t3.micro:       $7.60
GP3 Storage:        $0.64
Elastic IP:         $0.00 (associated with running instance)
Data Transfer:      $1.80 (20 GB)
─────────────────────────
TOTAL:              ~$10.04/month

Moderate usage (recommended):

EC2 t3.micro:       $7.60
GP3 Storage:        $0.64
Elastic IP:         $0.00 (associated with running instance)
Data Transfer:      $4.50 (50 GB)
─────────────────────────
TOTAL:              ~$12.74/month

Heavy usage:

EC2 t3.micro:       $7.60
GP3 Storage:        $0.64
Elastic IP:         $0.00 (associated with running instance)
Data Transfer:      $9.00 (100 GB)
─────────────────────────
TOTAL:              ~$17.24/month

Budget option (t3.nano without Elastic IP - NOT recommended):

EC2 t3.nano:        $3.80
GP3 Storage:        $0.64
Data Transfer:      $1.80 (20 GB)
─────────────────────────
TOTAL:              ~$6.24/month
Note: Risk of OOM errors and IP changes on restart

Cost Optimization Tips

  1. Use Elastic IP strategically: FREE while instance is running, but costs $3.60/month if instance is stopped. Always release Elastic IPs if you plan to stop the instance for extended periods.
  2. Stop instance when not in use: Only pay for stopped EBS storage (~$0.64/month). Remember to release or reassociate Elastic IP.
  3. Monitor data transfer: Use CloudWatch to track bandwidth usage - this is typically your largest variable cost
  4. Right-size instance: t3.micro provides the right balance of cost and stability; t3.small is overkill for personal VPN use
  5. Use AWS Free Tier: New accounts get 750 hours/month free for 12 months (t2.micro or t3.micro qualify)
  6. Avoid unnecessary upgrades: The t3.micro is sufficient for personal use with multiple concurrent connections

Billing Alerts

Set up billing alarm to avoid surprises:

# Create SNS topic for billing alerts
TOPIC_ARN=$(aws sns create-topic \
  --name billing-alerts \
  --query 'TopicArn' \
  --output text)

# Subscribe your email
aws sns subscribe \
  --topic-arn $TOPIC_ARN \
  --protocol email \
  --notification-endpoint [email protected]

# Create CloudWatch alarm (requires us-east-1 region for billing)
aws cloudwatch put-metric-alarm \
  --alarm-name "MonthlyBillingAlarm" \
  --alarm-description "Alert when monthly charges exceed $15" \
  --namespace "AWS/Billing" \
  --metric-name "EstimatedCharges" \
  --dimensions Name=Currency,Value=USD \
  --statistic Maximum \
  --period 21600 \
  --evaluation-periods 1 \
  --threshold 15 \
  --comparison-operator GreaterThanThreshold \
  --alarm-actions $TOPIC_ARN \
  --region us-east-1

Cost Tracking

View current month charges:

# Get current month charges (requires AWS Cost Explorer enabled)
aws ce get-cost-and-usage \
  --time-period Start=$(date -u +%Y-%m-01),End=$(date -u +%Y-%m-%d) \
  --granularity MONTHLY \
  --metrics "UnblendedCost" \
  --group-by Type=SERVICE

Quick Reference

Essential Commands Cheat Sheet

# Get instance ID
INSTANCE_ID=i-xxxxxxxxxxxxx

# Check SSM status
aws ssm describe-instance-information \
  --filters "Key=InstanceIds,Values=$INSTANCE_ID"

# Quick send command
aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["COMMAND"]'

# Check V2Ray status
# ... send-command with: v2ray status

# Get V2Ray info
# ... send-command with: v2ray info

# Restart V2Ray
# ... send-command with: v2ray restart

# View logs
# ... send-command with: tail -n 50 /var/log/v2ray/error.log

Important File Locations

Purpose Path
V2Ray config /etc/v2ray/config.json
V2Ray binary /usr/local/bin/v2ray
TLS certificates /root/.acme.sh/your-domain.com/
Access logs /var/log/v2ray/access.log
Error logs /var/log/v2ray/error.log
User-data logs /var/log/user-data.log
V2Ray management script /usr/local/bin/v2ray (wrapper script)

Security Considerations

Best Practices

  1. Use strong domain name: Don't use obvious VPN-related names

  2. Keep system updated: Regularly update packages

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["apt-get update && apt-get upgrade -y"]'
  3. Monitor access logs: Check for suspicious activity

  4. Rotate UUID periodically: Change UUID every few months for better security

  5. Use firewall rules: Only open necessary ports (80, 443)

  6. Enable AWS CloudTrail: Track API calls for audit purposes

  7. Don't share configuration: Each user should have their own UUID

Recommended Security Enhancements

  1. Enable CloudWatch alarms for unusual traffic:

    aws cloudwatch put-metric-alarm \
      --alarm-name "HighNetworkOut" \
      --alarm-description "Alert on high network out" \
      --namespace "AWS/EC2" \
      --metric-name "NetworkOut" \
      --dimensions Name=InstanceId,Value=$INSTANCE_ID \
      --statistic Average \
      --period 300 \
      --evaluation-periods 2 \
      --threshold 1000000000 \
      --comparison-operator GreaterThanThreshold
  2. Use AWS WAF (optional, for advanced protection):

    • Protect against DDoS
    • Filter malicious traffic
    • Additional cost applies
  3. Implement fail2ban (optional):

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["apt-get install -y fail2ban"]'

Backup and Disaster Recovery

Configuration Backup

Save your configuration locally:

# Get V2Ray config
aws ssm send-command \
  --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["cat /etc/v2ray/config.json"]' \
  --query "Command.CommandId" \
  --output text | \
  xargs -I {} aws ssm get-command-invocation \
    --command-id {} \
    --instance-id $INSTANCE_ID \
    --query "StandardOutputContent" \
    --output text > v2ray-config-backup.json

Instance Snapshot

Create AMI for quick recovery:

# Create AMI
AMI_ID=$(aws ec2 create-image \
  --instance-id $INSTANCE_ID \
  --name "V2Ray-Backup-$(date +%Y%m%d)" \
  --description "V2Ray server backup" \
  --query 'ImageId' \
  --output text)

echo "AMI ID: $AMI_ID"

Restore from Snapshot

# Launch new instance from AMI
NEW_INSTANCE_ID=$(aws ec2 run-instances \
  --image-id $AMI_ID \
  --instance-type t3.micro \
  --security-group-ids $SG_ID \
  --iam-instance-profile Name=V2Ray-SSM-InstanceProfile \
  --query 'Instances[0].InstanceId' \
  --output text)

# Associate your existing Elastic IP (recommended)
aws ec2 associate-address \
  --instance-id $NEW_INSTANCE_ID \
  --allocation-id $ELASTIC_IP_ALLOCATION

# If no Elastic IP, update DNS to point to new instance
# ... (follow DNS configuration steps from Step 6)

Cleanup and Termination

Graceful Shutdown

When you want to terminate the server:

  1. Stop V2Ray service:

    aws ssm send-command \
      --instance-ids $INSTANCE_ID \
      --document-name "AWS-RunShellScript" \
      --parameters 'commands=["v2ray stop"]'
  2. Terminate instance:

    aws ec2 terminate-instances --instance-ids $INSTANCE_ID
  3. Delete security group:

    aws ec2 delete-security-group --group-id $SG_ID
  4. Delete IAM resources:

    # Remove role from instance profile
    aws iam remove-role-from-instance-profile \
      --instance-profile-name V2Ray-SSM-InstanceProfile \
      --role-name V2Ray-SSM-Role
    
    # Delete instance profile
    aws iam delete-instance-profile \
      --instance-profile-name V2Ray-SSM-InstanceProfile
    
    # Detach policies
    aws iam detach-role-policy \
      --role-name V2Ray-SSM-Role \
      --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
    
    # Delete role
    aws iam delete-role --role-name V2Ray-SSM-Role
  5. Release Elastic IP (if used):

    # Get allocation ID
    ALLOCATION_ID=$(aws ec2 describe-addresses \
      --filters "Name=instance-id,Values=$INSTANCE_ID" \
      --query 'Addresses[0].AllocationId' \
      --output text)
    
    # Release
    aws ec2 release-address --allocation-id $ALLOCATION_ID
  6. Delete DNS record: Remove A record from your DNS provider


Additional Resources

Official Documentation

Clash Clients

Community Resources

Useful Tools


Changelog

Version 1.2.0 (2026-01-04)

Major Updates:

  • Changed standard instance type from t3.nano to t3.micro

    • Doubled RAM from 0.5 GB to 1 GB for improved stability
    • Significantly reduces Out Of Memory (OOM) errors
    • Additional cost: ~$3.80/month for better reliability
  • Added required Elastic IP allocation step (new Step 5)

    • Prevents IP address changes on instance stop/start
    • Eliminates need for DNS updates after maintenance
    • FREE while instance is running
    • Includes complete setup instructions and IAM policy
  • Renumbered all deployment steps

    • Original Step 5 (DNS) → Step 6
    • Original Step 6 (SSM) → Step 7
    • Original Steps 7-10 → Steps 8-11
  • Updated DNS configuration guidance

    • Emphasizes using Elastic IP instead of auto-assigned IP
    • Added warnings about IP permanence
    • Clarified Cloudflare proxy settings
  • Updated cost calculations throughout

    • t3.micro pricing: ~$7.60/month (vs t3.nano $3.80/month)
    • Updated total cost estimates for all usage tiers
    • Added Elastic IP cost breakdown
    • New recommended setup: ~$10-17/month depending on data transfer
  • Enhanced troubleshooting section

    • Added Issue 7: Out Of Memory (OOM) Errors
    • Updated Issue 6 with Elastic IP guidance
    • Added upgrade instructions from t3.nano to t3.micro
  • Updated examples and references

    • Backup/restore commands now use t3.micro
    • Cost optimization tips reflect new recommendations

Version 1.0.0 (2026-01-03)

  • Initial release
  • Complete deployment playbook for V2Ray VLESS-H2-TLS
  • AWS SSM-based management without SSH
  • Comprehensive troubleshooting guide
  • Cost analysis and optimization tips

Support and Contributions

This playbook is based on the V2Ray deployment process completed on 2026-01-03, updated on 2026-01-04.

Tested with:

  • AWS Region: us-east-1
  • Instance Type: t3.micro (recommended), t3.nano (deprecated)
  • OS: Ubuntu 22.04 LTS
  • V2Ray: 233boy script (latest)
  • Protocol: VLESS-H2-TLS
  • Client: Clash

For issues or improvements, refer to the official V2Ray and AWS documentation.


End of Playbook

Last updated: 2026-01-04 Deployment time: ~20 minutes (including Elastic IP allocation and DNS propagation)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment