Skip to content
by skunxicat

Disposable Cloud Proxies in One Command

Disposable HTTP proxy architecture

The Problem

You need to make HTTP requests from a different IP address. Maybe you’re scraping a site that rate-limits per IP. Maybe you’re testing how your firewall rules behave against traffic from cloud ranges. Maybe you’re validating that a geo-restriction actually works. Or maybe you just need a quick proxy to avoid leaking your real IP during research.

The solutions on the table are all annoying:

  • Commercial proxy services often involve monthly subscriptions, shared IP pools, and trusting a third party with your traffic.
  • VPN providers solve a different problem (privacy for browsing), and their IPs are widely flagged.
  • Manual EC2 setup works, but takes 15 minutes of clicking through the console, configuring security groups, installing software, and — worst of all — remembering to terminate the instance when you’re done.

What you actually want is: run one command, get a proxy URL with a fresh public IP, use it, destroy it, pay fractions of a cent.

The Solution

terraform-aws-ec2-proxy is a Terraform module that codifies the “disposable cloud proxy” pattern into a single operation. It provisions a Squid forward proxy on an EC2 spot instance, outputs a ready-to-use HTTP_PROXY URL, and costs roughly $0.0016/hour to run.

It’s available on the Terraform Registry:

module "proxy" {
  source  = "ql4b/ec2-proxy/aws"
  version = "~> 2.5"

  namespace = "myorg"
  name      = "proxy"
}
terraform init && terraform apply

export HTTP_PROXY=$(terraform output -raw proxy_url)
curl http://httpbin.org/ip   # shows the proxy's IP, not yours

terraform destroy            # done, no residual cost

Every apply cycle gives you a new public IP. No monthly bills, no forgotten instances, no infrastructure to maintain.

How It Works

The module deploys a minimal stack into your AWS account’s default VPC (or a VPC you specify):

  1. A t4g.nano ARM64 spot instance running Amazon Linux 2023. Spot pricing means ~70% savings over on-demand. ARM64 (Graviton) gives the best price-performance at this tier. If you need guaranteed availability (spot instances can be interrupted), set spot = false and the module launches a standard on-demand instance instead.

  2. Squid installed via user_data at boot. No custom AMIs, no Docker, no external dependencies — just dnf install squid from the AL2023 repos. The proxy is ready within 30-60 seconds of launch.

  3. A security group that allows inbound traffic on the proxy port (default: 8888) only from your IP. When you don’t specify CIDRs, the module calls checkip.amazonaws.com at plan time and locks ingress to your current public IP.

The proxy speaks plain HTTP (not HTTPS) between your client and the proxy itself. This is standard for forward proxies — HTTPS CONNECT tunneling still works for reaching HTTPS destinations, but the proxy listener is unencrypted. For local or trusted-network use this is fine. If you’re connecting over the public internet and want an encrypted tunnel to the proxy, use SSM port forwarding instead.

  1. An IAM role with SSM access so you can debug via aws ssm start-session if needed. No SSH key pairs, no port 22.

That’s the entire footprint. No load balancers, no NAT gateways, no persistent storage, no VPN tunnels.

Security by Default

A proxy is sensitive infrastructure — it forwards traffic on your behalf. The module takes a secure-by-default stance:

  • Auto-detected ingress restriction. If you don’t explicitly specify allowed CIDRs, nobody but you can reach the proxy. The security group is locked to your IP at plan time.
  • IMDSv2 enforced. The instance metadata service requires session tokens, blocking the most common SSRF attack vector for credential theft.
  • No SSH. There are no key pairs and no port 22. Access is through AWS Systems Manager only, which provides audit logging and doesn’t require inbound ports.
  • Encrypted root volume. EBS encryption is enabled by default.
  • Optional basic authentication. Set proxy_username and proxy_password to add an HTTP basic auth layer.
  • Privacy headers. Squid is configured with via off and forwarded_for delete, so it doesn’t leak your identity to upstream servers.

Use Cases

Web scraping with IP rotation. Run the module, scrape your target, destroy. Next time you need a different IP, just apply again. For parallel scraping, deploy multiple instances or re-apply in sequence between batches.

Testing rate limits. If you’re building a rate-limiting system, you need to verify it works against real distributed traffic. Spin up proxies in different regions and hit your endpoint through each one.

Firewall rule validation. You’ve added a CIDR block to your WAF or security group — does it actually block traffic from those ranges? Deploy a proxy in the cloud and test from outside your network.

Avoiding IP reputation issues. Your office IP got flagged by an overzealous abuse detection system. Rather than fighting with support, route specific requests through a disposable proxy while you sort it out.

Quick privacy for one-off requests. Sometimes you just want to make a few requests without exposing your real IP. This is faster and more trustworthy than a random free proxy list.

SERP and AI search testing. Search results and AI-generated answers can vary by geography and network origin. Launch the proxy in another AWS region, route the request through it, compare the result, then let the instance disappear when the TTL expires. This is not a residential proxy and should not be treated as one; it is a cheap cloud vantage point for controlled testing.

                        ┌───────────────┐
              ┌────────▶│ us-east-1     │────▶ SERP (US)
              │         │ 3.92.x.x:8888 │
┌──────────┐  │         └───────────────┘
│          │──┤
│ Your     │  │         ┌───────────────┐
│ machine  │  ├────────▶│ eu-west-1     │────▶ SERP (EU)
│          │  │         │ 52.18.x.x:8888│
└──────────┘  │         └───────────────┘

              │         ┌────────────────┐
              └────────▶│ ap-southeast-1 │────▶ SERP (Asia)
                        │ 13.212.x.x:8888│
                        └────────────────┘

         terraform apply     compare results     terraform destroy
         (per region)        (different IPs)     (no residual cost)

Auto-Termination: No Forgotten Instances

The most common failure mode with cloud proxies is forgetting to shut them down. The module solves this:

module "proxy" {
  source  = "ql4b/ec2-proxy/aws"
  version = "~> 2.5"

  namespace = "myorg"
  name      = "proxy"
  ttl_hours = 2
}

Set ttl_hours and the instance self-terminates after that duration. It uses shutdown -h combined with instance_initiated_shutdown_behavior = "terminate" — no Lambda functions, no CloudWatch rules, no external schedulers. The instance simply doesn’t exist after your TTL expires.

Cost

A t4g.nano spot instance in us-east-1 costs approximately $0.0016/hour. That’s:

  • Less than $0.01 for a typical scraping session
  • About $1.15/month if somehow left running 24/7 (which the TTL feature prevents)
  • Effectively free for the “deploy, use, destroy” workflow

There’s no minimum commitment, no idle cost when destroyed, and no data transfer charges for the proxy itself (only standard AWS egress applies to your actual traffic).

Getting Started

Prerequisites:

  • Terraform >= 1.0
  • An AWS account with a default VPC, or an existing VPC and public subnet
  • AWS credentials configured

Simplest deployment:

module "proxy" {
  source  = "ql4b/ec2-proxy/aws"
  version = "~> 2.5"

  namespace = "myorg"
  name      = "proxy"
}

output "proxy_url" {
  value     = module.proxy.proxy_url
  sensitive = true
}

With authentication and TTL:

module "proxy" {
  source  = "ql4b/ec2-proxy/aws"
  version = "~> 2.5"

  namespace      = "myorg"
  name           = "proxy"
  proxy_username = var.proxy_username
  proxy_password = var.proxy_password
  ttl_hours      = 4
}

With explicit CIDRs (for shared use):

module "proxy" {
  source  = "ql4b/ec2-proxy/aws"
  version = "~> 2.5"

  namespace     = "myorg"
  name          = "proxy"
  spot          = false
  allowed_cidrs = ["203.0.113.0/24", "198.51.100.10/32"]
}

With a custom VPC (when the default VPC is missing):

module "proxy" {
  source  = "ql4b/ec2-proxy/aws"
  version = "~> 2.5"

  namespace = "myorg"
  name      = "proxy"

  vpc_id    = "vpc-abc123"
  subnet_id = "subnet-def456"   # must be a public subnet
}

Some regions (or locked-down accounts) don’t have a default VPC. Pass vpc_id and subnet_id to deploy into any existing public subnet. The subnet needs a route to an internet gateway — the module sets associate_public_ip_address = true so the instance always gets a public IP.

Design Philosophy

The module optimizes for disposability over durability:

  • Stateless. The proxy caches nothing meaningful. Reprovisioning is the upgrade path.
  • Zero pre-existing infra. Works in any AWS account with a default VPC — or bring your own VPC and public subnet. No Terraform state backends to configure, no network setup.
  • Single-purpose. It’s a forward proxy, not a VPN, not a bastion, not a NAT gateway. One job, done well.
  • Minimal blast radius. A t4g.nano spot instance with a TTL and a locked security group is about as low-risk as cloud infrastructure gets.

If you need high availability, load balancing, or a persistent proxy fleet, this isn’t the right tool. It’s for the cases where you need a fresh IP for a few hours, want it in one command, and don’t want to think about it again.

Just Want a Proxy? Clone and Go

The Terraform module is designed for composition — embedding in larger infrastructure stacks. But if you just want a working proxy without writing any Terraform yourself, there’s a companion repo: cloudless-proxy.

It wraps the module with a .env config file and a proxy CLI that handles the entire lifecycle:

git clone https://github.com/ql4b/cloudless-proxy.git
cd cloudless-proxy
cp .env.example .env   # set your AWS profile and region
source activate
proxy up               # deploy
eval $(proxy env)      # export HTTP_PROXY into your shell

From there:

CommandWhat it does
proxy upDeploy the proxy
proxy downDestroy it
proxy recreateTerminate + redeploy for a fresh IP
proxy testVerify it’s working
proxy envPrint export statements for your shell

No Terraform knowledge required beyond cp .env.example .env and editing two values. The wrapper handles init, apply, destroy, and IP discovery.

Power user: MITM inspection

Chain the cloud proxy with mitmproxy to inspect HTTPS traffic flowing through it:

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ Browser/curl │────▶│  mitmproxy   │────▶│ Squid (EC2)  │────▶ Target
│              │     │ 127.0.0.1    │     │ 3.92.x.x     │
│              │◀────│  :8080       │◀────│  :8888       │◀────
└──────────────┘     │ inspect      │     │ cloud IP     │
                     └──────────────┘     └──────────────┘
                      your machine         AWS region
proxy up
eval $(proxy env)

# mitmproxy listens locally, forwards upstream through the cloud proxy
mitmproxy \
    --mode upstream:$HTTP_PROXY \
    --listen-host 127.0.0.1 \
    --listen-port 8080 \
    --ssl-insecure

# In another terminal, point your browser or curl at localhost:8080
curl --proxy http://127.0.0.1:8080 https://example.com

You get full request/response inspection with the traffic appearing to originate from the proxy’s cloud IP. Useful for debugging API behaviour that varies by source IP, or verifying that the proxy strips the headers it claims to.