diff --git a/README.md b/README.md
index 96e8267a0..0847ace33 100644
--- a/README.md
+++ b/README.md
@@ -264,6 +264,7 @@ No modules.
| [aws_default_route_table.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_route_table) | resource |
| [aws_default_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_security_group) | resource |
| [aws_default_vpc.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_vpc) | resource |
+| [aws_ec2_instance_connect_endpoint.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_instance_connect_endpoint) | resource |
| [aws_egress_only_internet_gateway.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/egress_only_internet_gateway) | resource |
| [aws_eip.nat](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource |
| [aws_elasticache_subnet_group.elasticache](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_subnet_group) | resource |
@@ -359,6 +360,7 @@ No modules.
| [create\_flow\_log\_cloudwatch\_iam\_role](#input\_create\_flow\_log\_cloudwatch\_iam\_role) | Whether to create IAM role for VPC Flow Logs | `bool` | `false` | no |
| [create\_flow\_log\_cloudwatch\_log\_group](#input\_create\_flow\_log\_cloudwatch\_log\_group) | Whether to create CloudWatch log group for VPC Flow Logs | `bool` | `false` | no |
| [create\_igw](#input\_create\_igw) | Controls if an Internet Gateway is created for public subnets and the related routes that connect them | `bool` | `true` | no |
+| [create\_instance\_connect\_endpoint](#input\_create\_instance\_connect\_endpoint) | Whether to create EC2 Instance Connect Endpoint(s) | `bool` | `false` | no |
| [create\_multiple\_intra\_route\_tables](#input\_create\_multiple\_intra\_route\_tables) | Indicates whether to create a separate route table for each intra subnet. Default: `false` | `bool` | `false` | no |
| [create\_multiple\_public\_route\_tables](#input\_create\_multiple\_public\_route\_tables) | Indicates whether to create a separate route table for each public subnet. Default: `false` | `bool` | `false` | no |
| [create\_private\_nat\_gateway\_route](#input\_create\_private\_nat\_gateway\_route) | Controls if a nat gateway route should be created to give internet access to the private subnets | `bool` | `true` | no |
@@ -456,6 +458,11 @@ No modules.
| [flow\_log\_per\_hour\_partition](#input\_flow\_log\_per\_hour\_partition) | (Optional) Indicates whether to partition the flow log per hour. This reduces the cost and response time for queries | `bool` | `false` | no |
| [flow\_log\_traffic\_type](#input\_flow\_log\_traffic\_type) | The type of traffic to capture. Valid values: ACCEPT, REJECT, ALL | `string` | `"ALL"` | no |
| [igw\_tags](#input\_igw\_tags) | Additional tags for the internet gateway | `map(string)` | `{}` | no |
+| [instance\_connect\_endpoint\_create\_in\_private\_subnets](#input\_instance\_connect\_endpoint\_create\_in\_private\_subnets) | Create EC2 Instance Connect Endpoint(s) in all private subnets if no subnet IDs are provided | `bool` | `true` | no |
+| [instance\_connect\_endpoint\_subnets](#input\_instance\_connect\_endpoint\_subnets) | List of subnet IDs where EC2 Instance Connect Endpoint(s) should be created. If null and create\_in\_private\_subnets is true, defaults to private subnets | `list(string)` | `null` | no |
+| [instance\_connect\_preserve\_client\_ip](#input\_instance\_connect\_preserve\_client\_ip) | Whether to preserve the client IP address when connecting via EC2 Instance Connect Endpoint | `bool` | `false` | no |
+| [instance\_connect\_security\_group\_ids](#input\_instance\_connect\_security\_group\_ids) | List of security group IDs to associate with EC2 Instance Connect Endpoint(s). If null, defaults to no security groups | `list(string)` | `null` | no |
+| [instance\_connect\_tags](#input\_instance\_connect\_tags) | Additional tags for EC2 Instance Connect Endpoint resources | `map(string)` | `{}` | no |
| [instance\_tenancy](#input\_instance\_tenancy) | A tenancy option for instances launched into the VPC | `string` | `"default"` | no |
| [intra\_acl\_tags](#input\_intra\_acl\_tags) | Additional tags for the intra subnets network ACL | `map(string)` | `{}` | no |
| [intra\_dedicated\_network\_acl](#input\_intra\_dedicated\_network\_acl) | Whether to use dedicated network ACL (not default) and custom rules for intra subnets | `bool` | `false` | no |
@@ -632,6 +639,7 @@ No modules.
| [elasticache\_subnets\_ipv6\_cidr\_blocks](#output\_elasticache\_subnets\_ipv6\_cidr\_blocks) | List of IPv6 cidr\_blocks of elasticache subnets in an IPv6 enabled VPC |
| [igw\_arn](#output\_igw\_arn) | The ARN of the Internet Gateway |
| [igw\_id](#output\_igw\_id) | The ID of the Internet Gateway |
+| [instance\_connect\_endpoints](#output\_instance\_connect\_endpoints) | Map of EC2 Instance Connect Endpoints created, keyed by subnet index |
| [intra\_network\_acl\_arn](#output\_intra\_network\_acl\_arn) | ARN of the intra network ACL |
| [intra\_network\_acl\_id](#output\_intra\_network\_acl\_id) | ID of the intra network ACL |
| [intra\_route\_table\_association\_ids](#output\_intra\_route\_table\_association\_ids) | List of IDs of the intra route table association |
diff --git a/examples/ec2-instance-connect-endpoint/README.md b/examples/ec2-instance-connect-endpoint/README.md
new file mode 100644
index 000000000..bfb0ed47b
--- /dev/null
+++ b/examples/ec2-instance-connect-endpoint/README.md
@@ -0,0 +1,84 @@
+# EC2 Instance Connect Endpoint
+
+Configuration in this directory creates a **VPC** with **EC2 Instance Connect Endpoints (EICE)** deployed in private subnets.
+This allows secure SSH or RDP access to instances **without using a bastion host** or assigning public IPs.
+
+## Features
+
+This example demonstrates:
+
+- Deployment of a VPC with public and private subnets across multiple Availability Zones
+- Creation of a **NAT Gateway** for outbound internet access
+- Deployment of **EC2 Instance Connect Endpoints** in private subnets
+- Creation of a security group allowing SSH access
+- Demonstration of the `preserve_client_ip` and tagging features for EICE resources
+
+---
+
+## Usage
+
+To run this example, execute:
+
+```bash
+terraform init
+terraform plan
+terraform apply
+```
+
+⚠️ Note:
+This example may create resources that incur costs (such as VPCs, NAT Gateways, and EC2 endpoints).
+Run terraform destroy when you no longer need these resources.
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 1.5.7 |
+| [aws](#requirement\_aws) | >= 6.5 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | >= 6.5 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [vpc](#module\_vpc) | ../.. | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_security_group.allow_ssh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |
+
+## Inputs
+
+No inputs.
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [instance\_connect\_endpoints](#output\_instance\_connect\_endpoints) | Map of EC2 Instance Connect Endpoints created |
+| [vpc\_id](#output\_vpc\_id) | The ID of the VPC |
+
+
+##Example Output
+
+Example output after deployment:
+
+```bash
+instance_connect_endpoints = {
+ "0" = {
+ "id" = "eice-0c123456789abcd"
+ "arn" = "arn:aws:ec2:us-east-1:123456789012:ec2-instance-connect-endpoint/eice-0c123456789abcd"
+ "subnet_id" = "subnet-0123456789abcdef"
+ "sg_ids" = ["sg-0123456789abcdef"]
+ }
+}
+```
diff --git a/examples/ec2-instance-connect-endpoint/main.tf b/examples/ec2-instance-connect-endpoint/main.tf
new file mode 100644
index 000000000..54c98e063
--- /dev/null
+++ b/examples/ec2-instance-connect-endpoint/main.tf
@@ -0,0 +1,92 @@
+################################################################################
+# Provider Configuration
+################################################################################
+
+provider "aws" {
+ region = local.region
+}
+
+data "aws_availability_zones" "available" {}
+
+################################################################################
+# Locals
+################################################################################
+
+locals {
+ name = "ex-${basename(path.cwd)}"
+ region = "us-east-1"
+
+ # Get the first 2 availability zones for simplicity
+ azs = slice(data.aws_availability_zones.available.names, 0, 2)
+
+ # Base VPC CIDR
+ vpc_cidr = "10.0.0.0/16"
+
+ # Calculate subnet CIDRs automatically based on AZ count
+ private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
+ public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k + 10)]
+
+ tags = {
+ Example = local.name
+ GithubRepo = "terraform-aws-vpc"
+ GithubOrg = "terraform-aws-modules"
+ }
+}
+
+################################################################################
+# VPC Module
+################################################################################
+
+module "vpc" {
+ source = "../.."
+
+ name = local.name
+ cidr = local.vpc_cidr
+
+ azs = local.azs
+ private_subnets = local.private_subnets
+ public_subnets = local.public_subnets
+
+ enable_nat_gateway = true
+ single_nat_gateway = true
+
+ # EC2 Instance Connect Endpoint Configuration
+ create_instance_connect_endpoint = true
+ instance_connect_preserve_client_ip = true
+ instance_connect_security_group_ids = [aws_security_group.allow_ssh.id]
+ instance_connect_tags = {
+ Environment = "example"
+ }
+
+ tags = merge(local.tags, {
+ Name = local.name
+ })
+}
+
+################################################################################
+# Security Group for EC2 Instance Connect
+################################################################################
+
+resource "aws_security_group" "allow_ssh" {
+ name = "${local.name}-allow-ssh"
+ description = "Allow SSH for EC2 Instance Connect"
+ vpc_id = module.vpc.vpc_id
+
+ ingress {
+ from_port = 22
+ to_port = 22
+ protocol = "tcp"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+ egress {
+ from_port = 0
+ to_port = 0
+ protocol = "-1"
+ cidr_blocks = ["0.0.0.0/0"]
+ }
+
+ tags = merge(local.tags, {
+ Name = "${local.name}-allow-ssh"
+ })
+}
diff --git a/examples/ec2-instance-connect-endpoint/outputs.tf b/examples/ec2-instance-connect-endpoint/outputs.tf
new file mode 100644
index 000000000..41d241a76
--- /dev/null
+++ b/examples/ec2-instance-connect-endpoint/outputs.tf
@@ -0,0 +1,9 @@
+output "vpc_id" {
+ description = "The ID of the VPC"
+ value = module.vpc.vpc_id
+}
+
+output "instance_connect_endpoints" {
+ description = "Map of EC2 Instance Connect Endpoints created"
+ value = module.vpc.instance_connect_endpoints
+}
diff --git a/examples/ec2-instance-connect-endpoint/variables.tf b/examples/ec2-instance-connect-endpoint/variables.tf
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/ec2-instance-connect-endpoint/versions.tf b/examples/ec2-instance-connect-endpoint/versions.tf
new file mode 100644
index 000000000..1548bda1a
--- /dev/null
+++ b/examples/ec2-instance-connect-endpoint/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.5.7"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 6.5"
+ }
+ }
+}
diff --git a/main.tf b/main.tf
index 31deb5988..4b759d979 100644
--- a/main.tf
+++ b/main.tf
@@ -19,8 +19,20 @@ locals {
vpc_id = try(aws_vpc_ipv4_cidr_block_association.this[0].vpc_id, aws_vpc.this[0].id, "")
create_vpc = var.create_vpc && var.putin_khuylo
+
+ # EC2 Instance Connect Endpoint target subnets
+ instance_connect_target_subnets = (
+ var.instance_connect_endpoint_subnets != null && length(var.instance_connect_endpoint_subnets) > 0
+ ? var.instance_connect_endpoint_subnets
+ : (
+ var.instance_connect_endpoint_create_in_private_subnets && local.len_private_subnets > 0
+ ? aws_subnet.private[*].id
+ : []
+ )
+ )
}
+
################################################################################
# VPC
################################################################################
@@ -1541,3 +1553,31 @@ resource "aws_default_route_table" "default" {
var.default_route_table_tags,
)
}
+
+################################################################################
+# EC2 Instance Connect Endpoint
+################################################################################
+
+resource "aws_ec2_instance_connect_endpoint" "this" {
+ for_each = var.create_instance_connect_endpoint ? {
+ for idx, subnet_id in local.instance_connect_target_subnets : idx => subnet_id
+ } : {}
+
+ subnet_id = each.value
+
+ security_group_ids = (
+ var.instance_connect_security_group_ids != null
+ ? var.instance_connect_security_group_ids
+ : []
+ )
+
+ preserve_client_ip = var.instance_connect_preserve_client_ip
+
+ tags = merge(
+ var.tags,
+ var.instance_connect_tags,
+ {
+ Name = "${var.name}-ec2-instance-connect-${each.key}"
+ }
+ )
+}
diff --git a/outputs.tf b/outputs.tf
index 1d1d2783a..063bf4505 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -667,3 +667,19 @@ output "name" {
description = "The name of the VPC specified as argument to this module"
value = var.name
}
+
+################################################################################
+# EC2 Instance Connect Endpoint
+################################################################################
+
+output "instance_connect_endpoints" {
+ description = "Map of EC2 Instance Connect Endpoints created, keyed by subnet index"
+ value = try({
+ for k, v in aws_ec2_instance_connect_endpoint.this : k => {
+ id = v.id
+ arn = v.arn
+ subnet_id = v.subnet_id
+ sg_ids = v.security_group_ids
+ }
+ }, {})
+}
diff --git a/variables.tf b/variables.tf
index ea23a3e52..c48747ab6 100644
--- a/variables.tf
+++ b/variables.tf
@@ -1678,3 +1678,43 @@ variable "putin_khuylo" {
type = bool
default = true
}
+
+################################################################################
+# EC2 Instance Connect Endpoint
+################################################################################
+
+variable "create_instance_connect_endpoint" {
+ type = bool
+ default = false
+ description = "Whether to create EC2 Instance Connect Endpoint(s)"
+}
+
+variable "instance_connect_endpoint_create_in_private_subnets" {
+ type = bool
+ default = true
+ description = "Create EC2 Instance Connect Endpoint(s) in all private subnets if no subnet IDs are provided"
+}
+
+variable "instance_connect_endpoint_subnets" {
+ type = list(string)
+ default = null
+ description = "List of subnet IDs where EC2 Instance Connect Endpoint(s) should be created. If null and create_in_private_subnets is true, defaults to private subnets"
+}
+
+variable "instance_connect_security_group_ids" {
+ type = list(string)
+ default = null
+ description = "List of security group IDs to associate with EC2 Instance Connect Endpoint(s). If null, defaults to no security groups"
+}
+
+variable "instance_connect_tags" {
+ type = map(string)
+ default = {}
+ description = "Additional tags for EC2 Instance Connect Endpoint resources"
+}
+
+variable "instance_connect_preserve_client_ip" {
+ type = bool
+ default = false
+ description = "Whether to preserve the client IP address when connecting via EC2 Instance Connect Endpoint"
+}
diff --git a/wrappers/main.tf b/wrappers/main.tf
index bef0c73fc..65c0f4a0a 100644
--- a/wrappers/main.tf
+++ b/wrappers/main.tf
@@ -16,6 +16,7 @@ module "wrapper" {
create_flow_log_cloudwatch_iam_role = try(each.value.create_flow_log_cloudwatch_iam_role, var.defaults.create_flow_log_cloudwatch_iam_role, false)
create_flow_log_cloudwatch_log_group = try(each.value.create_flow_log_cloudwatch_log_group, var.defaults.create_flow_log_cloudwatch_log_group, false)
create_igw = try(each.value.create_igw, var.defaults.create_igw, true)
+ create_instance_connect_endpoint = try(each.value.create_instance_connect_endpoint, var.defaults.create_instance_connect_endpoint, false)
create_multiple_intra_route_tables = try(each.value.create_multiple_intra_route_tables, var.defaults.create_multiple_intra_route_tables, false)
create_multiple_public_route_tables = try(each.value.create_multiple_public_route_tables, var.defaults.create_multiple_public_route_tables, false)
create_private_nat_gateway_route = try(each.value.create_private_nat_gateway_route, var.defaults.create_private_nat_gateway_route, true)
@@ -183,6 +184,11 @@ module "wrapper" {
flow_log_per_hour_partition = try(each.value.flow_log_per_hour_partition, var.defaults.flow_log_per_hour_partition, false)
flow_log_traffic_type = try(each.value.flow_log_traffic_type, var.defaults.flow_log_traffic_type, "ALL")
igw_tags = try(each.value.igw_tags, var.defaults.igw_tags, {})
+ instance_connect_endpoint_create_in_private_subnets = try(each.value.instance_connect_endpoint_create_in_private_subnets, var.defaults.instance_connect_endpoint_create_in_private_subnets, true)
+ instance_connect_endpoint_subnets = try(each.value.instance_connect_endpoint_subnets, var.defaults.instance_connect_endpoint_subnets, null)
+ instance_connect_preserve_client_ip = try(each.value.instance_connect_preserve_client_ip, var.defaults.instance_connect_preserve_client_ip, false)
+ instance_connect_security_group_ids = try(each.value.instance_connect_security_group_ids, var.defaults.instance_connect_security_group_ids, null)
+ instance_connect_tags = try(each.value.instance_connect_tags, var.defaults.instance_connect_tags, {})
instance_tenancy = try(each.value.instance_tenancy, var.defaults.instance_tenancy, "default")
intra_acl_tags = try(each.value.intra_acl_tags, var.defaults.intra_acl_tags, {})
intra_dedicated_network_acl = try(each.value.intra_dedicated_network_acl, var.defaults.intra_dedicated_network_acl, false)