Skip to content

Security Group Rules for Outbound HTTPS to a Model Provider

9 min read · updated August 11, 2026

Every security group you create starts by allowing all outbound traffic. Replacing that with a single HTTPS rule is five minutes of work; deciding what to put in the destination field is the part that takes a week and then gets reverted.

What you are replacing

Amazon documents that a new security group carries one outbound rule permitting all traffic, and that you can remove it and add rules allowing specific outbound traffic only (AWS, security group rules). Two properties of security groups shape everything below.

They are stateful: a permitted outbound connection implies permission for its return traffic, so you never write an inbound rule for the response. This is the difference from a network ACL, and it is why a correct egress rule needs no counterpart. They are also allow-only: there is no deny rule in a security group, so the effective policy is the union of every rule attached, and adding a group to an instance can only ever widen what it can reach. Removing the allow-all rule is therefore not optional if you want the narrow rule to mean anything — leaving both attached leaves you with allow-all.

What tightening egress buys you is worth stating plainly, because it is narrower than the compliance framing suggests. It does not stop a compromised process from using your provider key: that call goes to port 443 and your rule permits port 443. What it stops is the second stage — a process reaching a host on some other port to fetch a payload or open a reverse shell — and it stops a misconfigured service quietly reaching a dependency nobody documented.

Writing the rule

  1. Create the group with no egress rule you did not write. The API adds the allow-all rule on creation regardless, so the sequence is create, revoke, authorize — in that order, and from a session that will not lose connectivity to the instance mid-way.
  2. Revoke the default. The rule to remove is protocol -1 to 0.0.0.0/0, which is what “all traffic” is represented as.
  3. Authorize HTTPS only, with a description, because six months from now the description is the only record of why the rule exists.
SG=$(aws ec2 create-security-group \
  --group-name inference-egress \
  --description "Inference workers: HTTPS egress to model providers" \
  --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)

aws ec2 revoke-security-group-egress \
  --group-id "$SG" \
  --ip-permissions 'IpProtocol=-1,IpRanges=[{CidrIp=0.0.0.0/0}]'

aws ec2 authorize-security-group-egress \
  --group-id "$SG" \
  --ip-permissions 'IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=0.0.0.0/0,Description="HTTPS to model provider APIs"}]'

One rule is usually not enough, and the missing ones are predictable. If the workload resolves names through a resolver outside the VPC it needs UDP and TCP 53 outbound. If it fetches an OS package or a model weight over plain HTTP at start-up it needs 80, and if it does, that is worth fixing rather than allowing. If it reaches AWS services — Secrets Manager, ECR, CloudWatch Logs — it needs 443 to those too, and the better answer there is an interface VPC endpoint, whose destination genuinely can be narrowed: an endpoint has a prefix list you can name in the rule instead of a CIDR.

The destination is the hard part

The rule above allows 443 to anywhere, and the instinct is to replace 0.0.0.0/0 with the provider’s addresses. Before spending a week on that, understand what you are pointing at. Major model providers serve their APIs from behind a CDN, which means the address a resolver returns depends on where you are, changes without notice, and is shared with unrelated tenants of the same CDN. Pinning today’s answers produces a rule that is simultaneously too narrow — it breaks the first time the CDN shifts — and too wide, because the ranges it does cover belong to a very large number of other services.

AWS-managed prefix lists do not help here either. They exist for AWS services, so com.amazonaws.eu-west-1.s3 can be a destination and a third-party model API cannot. There is no vendor-published prefix list you can reference from an egress rule for a provider that does not run inside your account.

The two approaches that hold up are these.

  • Filter on name, not address, one layer up. An egress proxy that allows a list of hostnames does what you actually wanted, because the policy is written in the same terms as the thing it controls. The security group then permits 443 to the proxy only, which is a genuinely narrow rule and a stable one.
  • Use a private endpoint where the provider offers one. AWS PrivateLink to Bedrock, or Private Service Connect to Vertex AI, turns the destination into an address in your own VPC — and then the egress rule can name that and nothing else. See PrivateLink to a model endpoint and Private Service Connect to Vertex AI.

If a control framework requires a named destination and neither approach is available, the honest position is that the security group allows 443 to the internet and the real control lives in the proxy or the endpoint policy. Writing a CIDR list that will be stale next month satisfies an auditor and nobody else.

The same rule in Terraform

Do not use the inline egress block on aws_security_group for this. Inline rules are authoritative for the whole group, so a rule added by anything else is silently deleted on the next apply, and the block does not let you express “this group has exactly one rule and it is not the default” without fighting it. The current single-rule resources are clearer and each rule gets its own address in state:

resource "aws_security_group" "inference_egress" {
  name        = "inference-egress"
  description = "Inference workers: HTTPS egress to model providers"
  vpc_id      = var.vpc_id
}

resource "aws_vpc_security_group_egress_rule" "https" {
  security_group_id = aws_security_group.inference_egress.id
  description       = "HTTPS to model provider APIs"
  ip_protocol       = "tcp"
  from_port         = 443
  to_port           = 443
  cidr_ipv4         = "0.0.0.0/0"
}

Terraform creates the group without the allow-all rule when you declare no inline egress block, so there is nothing to revoke. Note that ip_protocol = "-1" is the one case where from_port and to_port must be omitted rather than set to zero; supplying them with -1 is a common apply error.

Verifying the model call still works

Verify from inside the workload, not from the console, and verify the negative case as well as the positive one. A test that only proves 443 works does not prove the rule is narrow.

# Positive: the model call succeeds.
curl -sS --max-time 15 https://api.openai.com/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY" -o /dev/null -w '%{http_code}\n'

# Negative: a non-443 destination now hangs and times out.
curl -sS --max-time 5 http://example.com/ -o /dev/null; echo "exit=$?"

The negative test should time out rather than refuse the connection, on the same reasoning as debugging a timeout from a locked-down VPC: a dropped packet is silent, and silence is what a security group does. If the second command returns quickly with a connection-refused error, something else is answering and your rule is not the thing being tested. Add both to whatever runs after a deploy — an egress rule that quietly reverted to allow-all looks exactly like a working system.