DEV Community

Cover image for Cloud workstation on AWS for $36/month: Windows EC2, static IP and Denver egress explained
lbc
lbc

Posted on

Cloud workstation on AWS for $36/month: Windows EC2, static IP and Denver egress explained

TL;DR

I built this for a client based in Denver. The project fell through (as they do). But the setup was too pretty to waste, so here it is: a Windows cloud workstation with a static Denver IP, built from Argentina, for a use case that no longer exists. Turns out it's useful anyway.

  • Instance: t3.large — 2 vCPU, 8 GB RAM, Windows Server 2022
  • Static inbound IP: Elastic IP (free while the instance is running)
  • Denver egress: a $3/month static residential proxy — not a VPN, not a second EC2
  • Total monthly cost: ~$36–39 running 8h/day on weekdays, or ~$118 if you leave it on 24/7

No overengineering. No enterprise fluff. Just a clean, hardened Windows box that does what it needs to do.


This is what we're building

Three independent flows:

  1. You → EC2 via RDP (encrypted, locked to your IP only)
  2. Chrome → Proxy → Web (all browser traffic exits from a real Denver residential IP)
  3. EC2 → EBS (persistent disk that survives stops and restarts)

The key insight the diagram makes obvious: AWS region and Denver geolocation are completely separate concerns. Your instance lives in us-east-1 (Virginia) for billing reasons. Denver happens at the proxy layer, outside AWS entirely. These two decisions don't interfere with each other.


Who is this actually for?

Before going further, this setup makes sense if you need any of these

  • A persistent Windows environment with a stable identity, especially if you travel frequently and your IP changes constantly. Your cloud machine stays in "Denver" while you're physically in Buenos Aires, Bangkok, or a airport lounge somewhere in between
  • Browser-based workflows that benefit from a consistent IP identity. Think platforms that tie account trust to IP history, or any service that behaves differently depending on your apparent location
  • A static outbound IP so you can whitelist yourself on external services
  • A geo-specific IP (Denver or otherwise) without paying for a full dedicated server
  • A starting point you can scale. This setup is deliberately one node. The same pattern extends to a fleet: one workstation per city, or multiple instances for a distributed remote team. Today it's one VM, later, it's infrastructure.

It's not the right call if you need GPU compute, if you're running heavy desktop software, or if multiple people need to access simultaneously (look at WorkSpaces for that).


Architecture decisions (and what I ruled out)

This is the section most tutorials skip. They tell you what to do but not why this and not that. Here's every real decision I made.

Instance type: why t3.large and not smaller or bigger

The t3 family is burstable — you get a baseline of CPU performance with the ability to spike when needed. For browser-based work, that's ideal: mostly idle, occasionally intense when loading heavy pages or running multiple tabs.

Instance vCPU RAM Windows On-Demand (us-east-1) Verdict
t3.medium 2 4 GB ~$55/mo Too little RAM for Chrome + RDP overhead
t3.large 2 8 GB ~$110/mo Sweet spot
t3.xlarge 4 16 GB ~$220/mo Overkill — 2x cost, same use case

Windows Server itself consumes ~2 GB at idle. RDP session adds ~500 MB. Chrome with a few tabs adds another 1–2 GB. On a t3.medium you're already at the ceiling before doing any work.

Region: us-east-1, not whatever's closest to Denver

Counter-intuitive but important: don't choose your AWS region based on geographic proximity to your target city. Region determines compute pricing. Denver egress is handled at the proxy layer. These are independent.

us-east-1 (N. Virginia) is the cheapest AWS region for Windows On-Demand. There's no AWS region in Denver, and even if there were, AWS IP geolocation at the city level is unreliable — you cannot guarantee a city-level geo from a raw AWS IP regardless of region.

Denver egress: proxy, not VPN, not a second EC2

Denver because that's where the client was. The client is gone. The architecture isn't. Swap the city for wherever you need. The setup is identical.

This was the most important decision. Three options I evaluated:

Option A: VPN client on EC2 (Mullvad, ProtonVPN)
Installs a VPN client on the Windows instance, routes all traffic through a Denver server. Works, but: adds latency overhead, costs $5–10/mo, routes all traffic including RDP (which you don't need geo'd), and VPN IPs are well-known to commercial services.

Option B: Second EC2 instance in a hypothetical Denver region
Doesn't exist. AWS has no Denver region. Even the closest region (us-west-2, Oregon) wouldn't give you a Denver IP.

Option C: Static residential ISP proxy (chosen)
A single HTTP/SOCKS5 endpoint from a provider like IPRoyal or Webshare. Configured once in Chrome. Costs $2–5/mo for a single static Denver IP with unlimited bandwidth. The exit IP is a real residential ISP address — not a datacenter IP — which matters for services like LinkedIn that flag datacenter ranges.

Why residential matters: Many platforms cross-reference your IP against known datacenter CIDR blocks. A residential proxy from an actual Denver ISP looks like a person in Denver, not a server in Denver.

Storage: 50 GB gp3, not the default

AWS will suggest gp2 by default. Use gp3 — same performance baseline, 20% cheaper, and you can provision throughput independently if needed later. 50 GB gives Windows room to breathe (baseline install + updates + Chrome profile + downloads).

Elastic IP: attach it, don't skip it

Without an Elastic IP, your instance gets a new public IP every time it starts. That means updating your RDP bookmark, your Security Group rules, and any external whitelists every single time. One EIP solves all of that permanently. It's free while attached to a running instance.


Execution: step by step, both CLI and console

Prerequisites: AWS account, AWS CLI configured (aws configure), an RDP client (built into Windows; Microsoft Remote Desktop on Mac).


Step 1: Create a Security Group

Console: EC2 → Security Groups → Create Security Group

CLI:

# Create the security group
aws ec2 create-security-group \
  --group-name windows-workstation-sg \
  --description "Windows cloud workstation" \
  --vpc-id vpc-xxxxxxxxx
Enter fullscreen mode Exit fullscreen mode
# Add RDP inbound rule — YOUR IP ONLY
MY_IP=$(curl -s https://checkip.amazonaws.com)
aws ec2 authorize-security-group-ingress \
  --group-name windows-workstation-sg \
  --protocol tcp \
  --port 3389 \
  --cidr "${MY_IP}/32"
Enter fullscreen mode Exit fullscreen mode

Critical: Never open port 3389 to 0.0.0.0/0. Bots will attempt brute-force login within minutes. Your IP only, always.


Step 2: Launch the EC2 instance!

Console: EC2 → Launch Instance → Windows Server 2022 Base → t3.large → 50 GB gp3 → select your security group → launch

CLI:

# Get the latest Windows Server 2022 AMI ID for us-east-1
aws ec2 describe-images \
  --owners amazon \
  --filters "Name=name,Values=Windows_Server-2022-English-Full-Base-*" \
  --query "sort_by(Images, &CreationDate)[-1].ImageId" \
  --output text
Enter fullscreen mode Exit fullscreen mode
# Launch instance (replace ami-xxxxxxxxx with the ID from above)
aws ec2 run-instances \
  --image-id ami-xxxxxxxxx \
  --instance-type t3.large \
  --key-name your-key-pair-name \
  --security-group-ids sg-xxxxxxxxx \
  --block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":50,"VolumeType":"gp3","DeleteOnTermination":true}}]' \
  --no-associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=windows-workstation}]'
Enter fullscreen mode Exit fullscreen mode

Step 3: Allocate and attach an Elastic IP

Console: EC2 → Elastic IPs → Allocate → Associate → select your instance

CLI:

# Allocate a new Elastic IP
ALLOC_ID=$(aws ec2 allocate-address --domain vpc --query AllocationId --output text)
echo "Allocation ID: $ALLOC_ID"

# Get your instance ID
INSTANCE_ID=$(aws ec2 describe-instances \
  --filters "Name=tag:Name,Values=windows-workstation" \
  --query "Reservations[0].Instances[0].InstanceId" \
  --output text)

# Associate the EIP
aws ec2 associate-address \
  --instance-id $INSTANCE_ID \
  --allocation-id $ALLOC_ID

# Get your permanent IP
aws ec2 describe-addresses \
  --allocation-ids $ALLOC_ID \
  --query "Addresses[0].PublicIp" \
  --output text
Enter fullscreen mode Exit fullscreen mode

Save that IP. It's yours permanently until you explicitly release it.


Step 4: Get the Windows password

Console: EC2 → Instances → select instance → Actions → Security → Get Windows password → upload .pem → decrypt

CLI:

aws ec2 get-password-data \
  --instance-id $INSTANCE_ID \
  --priv-launch-key /path/to/your-key.pem \
  --query PasswordData \
  --output text
Enter fullscreen mode Exit fullscreen mode

Wait 4–5 minutes after launch before this works — the instance needs to finish initializing and encrypt the password.


Step 5: Connect via RDP and baseline setup

# Windows
mstsc /v:<your-elastic-ip>

# Mac — open Microsoft Remote Desktop, add PC with your Elastic IP
Enter fullscreen mode Exit fullscreen mode

Once connected:

# Open PowerShell as Administrator, then:

# 1. Disable IE Enhanced Security (lets you download Chrome)
$AdminKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}"
Set-ItemProperty -Path $AdminKey -Name "IsInstalled" -Value 0
Stop-Process -Name Explorer

# 2. Install Chrome silently
$installer = "$env:TEMP\ChromeSetup.exe"
Invoke-WebRequest "https://dl.google.com/chrome/install/ChromeStandaloneSetup64.exe" -OutFile $installer
Start-Process $installer -ArgumentList "/silent /install" -Wait

# 3. Disable unnecessary services
Stop-Service -Name "Spooler" -Force
Set-Service -Name "Spooler" -StartupType Disabled
Stop-Service -Name "Fax" -Force  
Set-Service -Name "Fax" -StartupType Disabled

# 4. Set account lockout policy (5 attempts, 30 min lockout)
net accounts /lockoutthreshold:5 /lockoutduration:30 /lockoutwindow:30
Enter fullscreen mode Exit fullscreen mode

Step 6: Configure the Denver proxy

Purchase a static residential proxy with Denver, CO targeting from IPRoyal (~$2/mo) or Webshare (~$3/mo). You'll get: a host, a port, a username, and a password.

Option A: Chrome only (recommended, most surgical)

Create a desktop shortcut pointing to:

"C:\Program Files\Google\Chrome\Application\chrome.exe" --proxy-server="socks5://USERNAME:PASSWORD@HOST:PORT"
Enter fullscreen mode Exit fullscreen mode

Use this shortcut for all geo-sensitive work. Regular Chrome remains proxy-free for anything else.

Option B: System-wide proxy (all traffic)

# Set system proxy via PowerShell
$proxyAddress = "HOST:PORT"
Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" `
  -Name ProxyServer -Value $proxyAddress
Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" `
  -Name ProxyEnable -Value 1
Enter fullscreen mode Exit fullscreen mode

Verify Denver egress:

Open Chrome (via proxy shortcut) → navigate to https://ipinfo.io
Expected output: city: Denver, region: Colorado
Enter fullscreen mode Exit fullscreen mode

Step 7: OS hardening

# Disable Remote Assistance (separate from RDP — you don't need it)
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Remote Assistance" `
  -Name "fAllowToGetHelp" -Value 0

# Enable Windows Defender real-time protection
Set-MpPreference -DisableRealtimeMonitoring $false

# Disable Xbox services (not needed on a server)
$xboxServices = @("XblAuthManager","XblGameSave","XboxGipSvc","XboxNetApiSvc")
foreach ($svc in $xboxServices) {
    Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue
    Set-Service -Name $svc -StartupType Disabled -ErrorAction SilentlyContinue
}

# Rename Administrator account (minor but effective hardening)
Rename-LocalUser -Name "Administrator" -NewName "cloudadmin"
Enter fullscreen mode Exit fullscreen mode

Monthly cost scenarios

Scenario EC2 Compute EBS Elastic IP Proxy Total
Always on (730 hrs) $109.79 $4.00 $0.00 $3.00 ~$117/mo
Business hours (175 hrs) $26.32 $4.00 $3.60* $3.00 ~$37/mo
Minimal use (80 hrs) $12.03 $4.00 $3.60* $3.00 ~$23/mo
Instance stopped entirely $0.00 $4.00 $3.60 $3.00 ~$11/mo

EIP charges $0.005/hr while the instance is stopped (~$3.60/mo if stopped all non-working hours)

The single most effective cost optimization: stop the instance when you're done working. Not terminate — stop. The EBS volume persists. Your Chrome profile, your files, your proxy config — all intact. You pick up exactly where you left off.

Want to automate stop/start? A Lambda function + EventBridge rule can auto-stop at 8 PM and auto-start at 8 AM on weekdays. Adds maybe 30 minutes of setup. Saves ~$80/month if you'd otherwise leave it running.


Troubleshooting and maintenance

"I can't RDP in"

Most common cause: your local IP changed (happens with residential ISPs).

# Get your current IP
curl https://checkip.amazonaws.com

# Update the Security Group rule
aws ec2 revoke-security-group-ingress \
  --group-name windows-workstation-sg \
  --protocol tcp --port 3389 \
  --cidr OLD_IP/32

aws ec2 authorize-security-group-ingress \
  --group-name windows-workstation-sg \
  --protocol tcp --port 3389 \
  --cidr NEW_IP/32
Enter fullscreen mode Exit fullscreen mode

"The proxy isn't working / ipinfo.io shows wrong city"

  1. Confirm the Chrome shortcut includes the full --proxy-server flag
  2. Check proxy credentials haven't expired (some providers rotate them)
  3. Try curl --proxy socks5://USER:PASS@HOST:PORT https://ipinfo.io from PowerShell to isolate whether it's a Chrome config issue or a proxy issue

"Instance is slow / Chrome is laggy"

Check CPU credit balance — t3 instances use CPU credits and can throttle if you've sustained high load:

aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUCreditBalance \
  --dimensions Name=InstanceId,Value=$INSTANCE_ID \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 300 \
  --statistics Average
Enter fullscreen mode Exit fullscreen mode

If credits are near zero, either wait for them to replenish (they refill at ~24 credits/hr on t3.large) or temporarily upgrade to a t3.large with unlimited burst mode enabled.

Routine maintenance (monthly, 10 minutes)

# Windows Update — run in PowerShell
Install-Module PSWindowsUpdate -Force
Get-WUInstall -AcceptAll -AutoReboot

# Check disk space
Get-PSDrive C | Select-Object Used,Free

# Rotate your Administrator password if shared
net user cloudadmin NewStrongPassword123!
Enter fullscreen mode Exit fullscreen mode

"I accidentally clicked Terminate instead of Stop"

I'm sorry. The instance is gone. The EBS volume may still exist if you unchecked "Delete on termination" during setup — check EC2 → Volumes for an available volume and attach it to a new instance. This is why snapshots exist:

# Take a snapshot before anything risky
aws ec2 create-snapshot \
  --volume-id vol-xxxxxxxxx \
  --description "workstation-backup-$(date +%Y%m%d)"
Enter fullscreen mode Exit fullscreen mode

Run this once a week. It costs ~$0.05/GB/month and has saved me more than once.


The project that started this is gone. The setup isn't. If it saves you three hours of clicking around the AWS console and a surprise bill, it did its job.


All pricing based on AWS us-east-1 On-Demand rates as of early 2026. Proxy pricing based on IPRoyal single static residential IP. Your numbers may vary slightly — always verify at aws.amazon.com/ec2/pricing.

Top comments (4)

Collapse
 
alexeybaltacov profile image
Alexey Baltacov

if you purchase proxy services with Denver egress residential IP - what is the reason for EC2 at all? you can use that from any browser at any place. what am I missing?

Collapse
 
lbcristaldo profile image
lbc

good question! :) the proxy gives you Denver IP, but the EC2 gives you a persistent Windows environment (same desktop, Chrome profile, files, configs) accessible from anywhere. If you're traveling, your local machine changes but your cloud workstation doesn't.
proxy = geo. EC2 = persistence + portability.

Collapse
 
alexeybaltacov profile image
Alexey Baltacov

that makes sense when travel without any computer at all

Thread Thread
 
lbcristaldo profile image
lbc

yeah, the idea is to have a cloud workstation accessible from any computer, not only your personal one