Configure AWS Security Group Rules

The aws:ec2/securityGroupRule:SecurityGroupRule resource, part of the Pulumi AWS provider, defines individual ingress or egress rules that control traffic to and from security groups. This resource is maintained for backwards compatibility; for new projects, use aws.vpc.SecurityGroupIngressRule and aws.vpc.SecurityGroupEgressRule, which provide better support for multiple CIDR blocks and include tags and descriptions. This guide focuses on three capabilities: CIDR-based traffic rules, VPC endpoint integration, and AWS-managed prefix lists.

Security group rules reference existing security groups and specify traffic sources using CIDR blocks, prefix lists, or other security groups. The examples are intentionally small. Combine them with your own security groups and avoid mixing this resource with inline rules on aws.ec2.SecurityGroup resources.

Allow traffic from CIDR blocks on TCP ports

Most rules start by allowing inbound traffic from specific IP ranges, establishing baseline network access for applications.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.ec2.SecurityGroupRule("example", {
    type: "ingress",
    fromPort: 0,
    toPort: 65535,
    protocol: aws.ec2.ProtocolType.TCP,
    cidrBlocks: [exampleAwsVpc.cidrBlock],
    ipv6CidrBlocks: [exampleAwsVpc.ipv6CidrBlock],
    securityGroupId: "sg-123456",
});
import pulumi
import pulumi_aws as aws

example = aws.ec2.SecurityGroupRule("example",
    type="ingress",
    from_port=0,
    to_port=65535,
    protocol=aws.ec2.ProtocolType.TCP,
    cidr_blocks=[example_aws_vpc["cidrBlock"]],
    ipv6_cidr_blocks=[example_aws_vpc["ipv6CidrBlock"]],
    security_group_id="sg-123456")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := ec2.NewSecurityGroupRule(ctx, "example", &ec2.SecurityGroupRuleArgs{
			Type:     pulumi.String("ingress"),
			FromPort: pulumi.Int(0),
			ToPort:   pulumi.Int(65535),
			Protocol: pulumi.String(ec2.ProtocolTypeTCP),
			CidrBlocks: pulumi.StringArray{
				exampleAwsVpc.CidrBlock,
			},
			Ipv6CidrBlocks: pulumi.StringArray{
				exampleAwsVpc.Ipv6CidrBlock,
			},
			SecurityGroupId: pulumi.String("sg-123456"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Ec2.SecurityGroupRule("example", new()
    {
        Type = "ingress",
        FromPort = 0,
        ToPort = 65535,
        Protocol = Aws.Ec2.ProtocolType.TCP,
        CidrBlocks = new[]
        {
            exampleAwsVpc.CidrBlock,
        },
        Ipv6CidrBlocks = new[]
        {
            exampleAwsVpc.Ipv6CidrBlock,
        },
        SecurityGroupId = "sg-123456",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.SecurityGroupRule;
import com.pulumi.aws.ec2.SecurityGroupRuleArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new SecurityGroupRule("example", SecurityGroupRuleArgs.builder()
            .type("ingress")
            .fromPort(0)
            .toPort(65535)
            .protocol("tcp")
            .cidrBlocks(exampleAwsVpc.cidrBlock())
            .ipv6CidrBlocks(exampleAwsVpc.ipv6CidrBlock())
            .securityGroupId("sg-123456")
            .build());

    }
}
resources:
  example:
    type: aws:ec2:SecurityGroupRule
    properties:
      type: ingress
      fromPort: 0
      toPort: 65535
      protocol: tcp
      cidrBlocks:
        - ${exampleAwsVpc.cidrBlock}
      ipv6CidrBlocks:
        - ${exampleAwsVpc.ipv6CidrBlock}
      securityGroupId: sg-123456

The type property sets the traffic direction (ingress for inbound, egress for outbound). The fromPort and toPort define the port range, while protocol specifies TCP, UDP, ICMP, or a protocol number. The cidrBlocks and ipv6CidrBlocks properties list the IP ranges that can access this port range.

Route egress traffic to VPC endpoints

Applications accessing AWS services through VPC endpoints need egress rules that reference the endpoint’s prefix list.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// ...
const myEndpoint = new aws.ec2.VpcEndpoint("my_endpoint", {});
const allowAll = new aws.ec2.SecurityGroupRule("allow_all", {
    type: "egress",
    toPort: 0,
    protocol: "-1",
    prefixListIds: [myEndpoint.prefixListId],
    fromPort: 0,
    securityGroupId: "sg-123456",
});
import pulumi
import pulumi_aws as aws

# ...
my_endpoint = aws.ec2.VpcEndpoint("my_endpoint")
allow_all = aws.ec2.SecurityGroupRule("allow_all",
    type="egress",
    to_port=0,
    protocol="-1",
    prefix_list_ids=[my_endpoint.prefix_list_id],
    from_port=0,
    security_group_id="sg-123456")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// ...
		myEndpoint, err := ec2.NewVpcEndpoint(ctx, "my_endpoint", nil)
		if err != nil {
			return err
		}
		_, err = ec2.NewSecurityGroupRule(ctx, "allow_all", &ec2.SecurityGroupRuleArgs{
			Type:     pulumi.String("egress"),
			ToPort:   pulumi.Int(0),
			Protocol: pulumi.String("-1"),
			PrefixListIds: pulumi.StringArray{
				myEndpoint.PrefixListId,
			},
			FromPort:        pulumi.Int(0),
			SecurityGroupId: pulumi.String("sg-123456"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    // ...
    var myEndpoint = new Aws.Ec2.VpcEndpoint("my_endpoint");

    var allowAll = new Aws.Ec2.SecurityGroupRule("allow_all", new()
    {
        Type = "egress",
        ToPort = 0,
        Protocol = "-1",
        PrefixListIds = new[]
        {
            myEndpoint.PrefixListId,
        },
        FromPort = 0,
        SecurityGroupId = "sg-123456",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.VpcEndpoint;
import com.pulumi.aws.ec2.SecurityGroupRule;
import com.pulumi.aws.ec2.SecurityGroupRuleArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        // ...
        var myEndpoint = new VpcEndpoint("myEndpoint");

        var allowAll = new SecurityGroupRule("allowAll", SecurityGroupRuleArgs.builder()
            .type("egress")
            .toPort(0)
            .protocol("-1")
            .prefixListIds(myEndpoint.prefixListId())
            .fromPort(0)
            .securityGroupId("sg-123456")
            .build());

    }
}
resources:
  allowAll:
    type: aws:ec2:SecurityGroupRule
    name: allow_all
    properties:
      type: egress
      toPort: 0
      protocol: '-1'
      prefixListIds:
        - ${myEndpoint.prefixListId}
      fromPort: 0
      securityGroupId: sg-123456
  # ...
  myEndpoint:
    type: aws:ec2:VpcEndpoint
    name: my_endpoint

VPC endpoints expose a prefixListId that represents the service’s IP ranges. The prefixListIds property accepts this ID, allowing outbound traffic to the service without hardcoding IP addresses. Setting protocol to “-1” permits all protocols.

Reference AWS-managed prefix lists for service access

AWS publishes prefix lists for its services, providing stable references that update automatically as IP ranges change.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const current = aws.getRegion({});
const s3 = current.then(current => aws.ec2.getPrefixList({
    name: `com.amazonaws.${current.region}.s3`,
}));
const s3GatewayEgress = new aws.ec2.SecurityGroupRule("s3_gateway_egress", {
    description: "S3 Gateway Egress",
    type: "egress",
    securityGroupId: "sg-123456",
    fromPort: 443,
    toPort: 443,
    protocol: aws.ec2.ProtocolType.TCP,
    prefixListIds: [s3.then(s3 => s3.id)],
});
import pulumi
import pulumi_aws as aws

current = aws.get_region()
s3 = aws.ec2.get_prefix_list(name=f"com.amazonaws.{current.region}.s3")
s3_gateway_egress = aws.ec2.SecurityGroupRule("s3_gateway_egress",
    description="S3 Gateway Egress",
    type="egress",
    security_group_id="sg-123456",
    from_port=443,
    to_port=443,
    protocol=aws.ec2.ProtocolType.TCP,
    prefix_list_ids=[s3.id])
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		current, err := aws.GetRegion(ctx, &aws.GetRegionArgs{}, nil)
		if err != nil {
			return err
		}
		s3, err := ec2.GetPrefixList(ctx, &ec2.GetPrefixListArgs{
			Name: pulumi.StringRef(fmt.Sprintf("com.amazonaws.%v.s3", current.Region)),
		}, nil)
		if err != nil {
			return err
		}
		_, err = ec2.NewSecurityGroupRule(ctx, "s3_gateway_egress", &ec2.SecurityGroupRuleArgs{
			Description:     pulumi.String("S3 Gateway Egress"),
			Type:            pulumi.String("egress"),
			SecurityGroupId: pulumi.String("sg-123456"),
			FromPort:        pulumi.Int(443),
			ToPort:          pulumi.Int(443),
			Protocol:        pulumi.String(ec2.ProtocolTypeTCP),
			PrefixListIds: pulumi.StringArray{
				pulumi.String(s3.Id),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var current = Aws.GetRegion.Invoke();

    var s3 = Aws.Ec2.GetPrefixList.Invoke(new()
    {
        Name = $"com.amazonaws.{current.Apply(getRegionResult => getRegionResult.Region)}.s3",
    });

    var s3GatewayEgress = new Aws.Ec2.SecurityGroupRule("s3_gateway_egress", new()
    {
        Description = "S3 Gateway Egress",
        Type = "egress",
        SecurityGroupId = "sg-123456",
        FromPort = 443,
        ToPort = 443,
        Protocol = Aws.Ec2.ProtocolType.TCP,
        PrefixListIds = new[]
        {
            s3.Apply(getPrefixListResult => getPrefixListResult.Id),
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.AwsFunctions;
import com.pulumi.aws.inputs.GetRegionArgs;
import com.pulumi.aws.ec2.Ec2Functions;
import com.pulumi.aws.ec2.inputs.GetPrefixListArgs;
import com.pulumi.aws.ec2.SecurityGroupRule;
import com.pulumi.aws.ec2.SecurityGroupRuleArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        final var current = AwsFunctions.getRegion(GetRegionArgs.builder()
            .build());

        final var s3 = Ec2Functions.getPrefixList(GetPrefixListArgs.builder()
            .name(String.format("com.amazonaws.%s.s3", current.region()))
            .build());

        var s3GatewayEgress = new SecurityGroupRule("s3GatewayEgress", SecurityGroupRuleArgs.builder()
            .description("S3 Gateway Egress")
            .type("egress")
            .securityGroupId("sg-123456")
            .fromPort(443)
            .toPort(443)
            .protocol("tcp")
            .prefixListIds(s3.id())
            .build());

    }
}
resources:
  s3GatewayEgress:
    type: aws:ec2:SecurityGroupRule
    name: s3_gateway_egress
    properties:
      description: S3 Gateway Egress
      type: egress
      securityGroupId: sg-123456
      fromPort: 443
      toPort: 443
      protocol: tcp
      prefixListIds:
        - ${s3.id}
variables:
  current:
    fn::invoke:
      function: aws:getRegion
      arguments: {}
  s3:
    fn::invoke:
      function: aws:ec2:getPrefixList
      arguments:
        name: com.amazonaws.${current.region}.s3

The getPrefixList data source looks up AWS-managed prefix lists by name (format: “com.amazonaws.{region}.{service}”). The returned ID goes into prefixListIds, allowing HTTPS egress to S3. The description property documents the rule’s purpose.

Beyond these examples

These snippets focus on specific rule-level features: CIDR-based ingress and egress rules, and VPC endpoint and AWS-managed prefix list integration. They’re intentionally minimal rather than complete security configurations.

The examples reference pre-existing infrastructure such as security groups (all examples reference existing group IDs) and VPC endpoints (for prefix list examples). They focus on configuring individual rules rather than provisioning security groups or VPC infrastructure.

To keep things focused, common rule patterns are omitted, including:

  • Security group-to-security group rules (sourceSecurityGroupId)
  • Self-referencing rules (self property)
  • Rule descriptions for documentation
  • Port range variations (ICMP, protocol numbers)

These omissions are intentional: the goal is to illustrate how each rule feature is wired, not provide drop-in security modules. See the Security Group Rule resource reference for all available configuration options.

Let's configure AWS Security Group Rules

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Resource Selection & Compatibility
Should I use SecurityGroupRule or the newer VPC rule resources?
Use aws.vpc.SecurityGroupEgressRule and aws.vpc.SecurityGroupIngressRule instead. The aws.ec2.SecurityGroupRule resource struggles with managing multiple CIDR blocks and lacks support for unique IDs, tags, and descriptions.
Can I mix SecurityGroupRule with other rule resources?
No. Using aws.ec2.SecurityGroupRule with aws.vpc.SecurityGroupEgressRule/IngressRule or with inline rules in aws.ec2.SecurityGroup causes rule conflicts, perpetual differences, and overwritten rules. Choose one approach exclusively.
Source & Destination Configuration
What are the rules for configuring traffic sources?
You must provide at least one source: cidrBlocks, ipv6CidrBlocks, sourceSecurityGroupId, self, or prefixListIds. However, cidrBlocks and ipv6CidrBlocks cannot be used with sourceSecurityGroupId or self. Similarly, sourceSecurityGroupId cannot be used with cidrBlocks, ipv6CidrBlocks, or self.
How do I use prefix lists with security group rules?
Set prefixListIds with IDs from VPC endpoints (e.g., myEndpoint.prefixListId) or from the aws.ec2.getPrefixList data source for AWS-managed prefix lists like S3.
Protocol & Port Configuration
What happens when I set protocol to 'all' or -1?
The EC2 API creates a security group rule with all ports open, regardless of your fromPort and toPort values. This API behavior cannot be controlled by Pulumi and may generate warnings.
How do I specify ICMP type and code?
For ICMP protocols, use fromPort for the ICMP type number and toPort for the ICMP code.
Immutability & Updates
What properties can't I change after creating a rule?
These properties are immutable: fromPort, toPort, protocol, type, securityGroupId, sourceSecurityGroupId, cidrBlocks, ipv6CidrBlocks, prefixListIds, and self. Changing any of these requires replacing the rule.
Cross-VPC & Advanced Scenarios
Can I reference security groups across VPC peering connections?
Yes, but with certain restrictions. Review the VPC Peering User Guide for limitations on cross-VPC security group references.

Using a different cloud?

Explore networking guides for other cloud providers: