Elastic Network Interfaces: ENI Limits, awsvpc Mode, and Trunk ENIs
An ENI is a virtual NIC attached to an EC2 instance. In awsvpc mode, each ECS task and EKS pod gets its own ENI — but per-instance ENI limits constrain how many tasks can run per host. Trunk ENIs (branch ENIs) work around this limit for ECS and EKS at scale. Secondary ENIs enable multi-homed instances and appliance patterns.
What an ENI is
An Elastic Network Interface (ENI) is a virtual network card attached to an EC2 instance. Every instance has a primary ENI (eth0) created at launch. Additional ENIs can be attached to the same instance, each with:
- A private IPv4 address from the subnet
- Optional Elastic IP (public IPv4)
- Security group assignments (per-ENI, not per-instance)
- A MAC address
ENIs are independent resources — you can detach a secondary ENI from one instance and attach it to another. The private IP and security groups move with it.
Per-instance ENI limits constrain task density in awsvpc mode
GotchaAWS NetworkingIn awsvpc mode, each ECS task and EKS pod gets its own ENI. This provides network isolation — each task has its own IP address and security group. But EC2 instances have a hard limit on how many ENIs they can have attached, which caps how many tasks can run on a single host.
Prerequisites
- EC2 instance types
- ECS task networking
- VPC subnets
Key Points
- ENI limit varies by instance type: t3.micro supports 3 ENIs, m5.xlarge supports 8, c5.18xlarge supports 15.
- One ENI is consumed by the primary network interface — available task ENIs = instance ENI limit - 1.
- Larger instance types have higher ENI limits, but the limit is not proportional to vCPU count.
- Subnet IP exhaustion: each ENI consumes a subnet IP. A /28 subnet (11 usable IPs) supports 11 tasks maximum regardless of ENI limits.
ENI limits by instance type
# Check ENI limits for a specific instance type
aws ec2 describe-instance-types \
--instance-types m5.xlarge \
--query "InstanceTypes[].NetworkInfo.{MaxENIs:MaximumNetworkInterfaces,MaxIPs:Ipv4AddressesPerInterface}"
Common limits:
| Instance type | Max ENIs | IPs per ENI | Max private IPs | |---|---|---|---| | t3.micro | 3 | 2 | 6 | | t3.large | 3 | 12 | 36 | | m5.xlarge | 4 | 15 | 60 | | m5.4xlarge | 8 | 30 | 240 | | c5.18xlarge | 15 | 50 | 750 |
For ECS in awsvpc mode: available task slots = Max ENIs - 1 (primary ENI). An m5.xlarge supports 3 simultaneous ECS tasks with awsvpc networking.
awsvpc mode: task-level network isolation
awsvpc mode gives each ECS task its own ENI. Tasks are VPC-first-class — they have their own IP addresses, security groups, and appear directly in VPC flow logs.
Without awsvpc (bridge mode): tasks share the host's ENI, and security groups are applied at the host level. All tasks on the same host share the same security group rules.
With awsvpc:
resource "aws_ecs_task_definition" "app" {
family = "app"
network_mode = "awsvpc" # each task gets its own ENI
requires_compatibilities = ["FARGATE"]
cpu = 256
memory = 512
container_definitions = jsonencode([{
name = "app"
image = "nginx:latest"
portMappings = [{
containerPort = 80
protocol = "tcp"
}]
}])
}
resource "aws_ecs_service" "app" {
name = "app"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = 3
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.app_task.id]
assign_public_ip = false
}
}
Each task gets a distinct security group (aws_security_group.app_task). This enables security group rules that target specific tasks rather than all tasks on a host.
📝Trunk ENIs (awsvpcTrunking): scaling past the per-instance ENI limit
AWS introduced trunk ENIs to solve the ENI density problem. A trunk ENI is a special ENI that multiplexes traffic for many branch ENIs. With awsvpcTrunking enabled, an instance can host many more tasks than its normal ENI limit would allow.
How it works:
- A trunk ENI is attached to the instance
- Branch ENIs are logically attached to the trunk — they don't count against the instance's regular ENI limit
- Traffic for each branch ENI is tagged with a VLAN ID and routed through the trunk
# Enable awsvpcTrunking on an account (opt-in, per-region)
aws ec2 modify-account-attribute \
--attribute awsvpcTrunking \
--attribute-value true
After enabling, new instances of supported types automatically get a trunk ENI attached. The number of branch ENIs (and therefore ECS tasks) supported increases significantly:
| Instance type | Normal awsvpc tasks | With trunk ENI | |---|---|---| | m5.xlarge | 3 | 58 | | m5.4xlarge | 7 | 234 | | c5.18xlarge | 14 | 750 |
Instances launched before awsvpcTrunking was enabled don't benefit — only new instances get the trunk ENI. For EKS, the VPC CNI plugin supports a similar mechanism with prefix delegation, assigning a /28 prefix per ENI instead of individual IPs.
Secondary ENIs: multi-homed instances and appliance patterns
Secondary ENIs enable a single instance to have network interfaces in different subnets. Common patterns:
Network appliances: a firewall or NAT instance with one ENI facing the public subnet and one ENI facing the private subnet. Traffic flows through the instance between subnets.
Management networks: one ENI for application traffic, a second ENI on a restricted management subnet for monitoring and SSH. Security groups differ per ENI.
# Attach a secondary ENI to an existing instance
resource "aws_network_interface" "secondary" {
subnet_id = var.management_subnet_id
security_groups = [aws_security_group.management.id]
attachment {
instance = aws_instance.app.id
device_index = 1 # eth1, primary is device_index 0
}
}
ENI failover: detach a secondary ENI (carrying an Elastic IP) from a failed instance and attach it to a standby instance. The IP moves within seconds — faster than DNS-based failover.
# Detach ENI from failed instance
aws ec2 detach-network-interface \
--attachment-id eni-attach-abc123 \
--force
# Attach to standby instance
aws ec2 attach-network-interface \
--network-interface-id eni-0abc123 \
--instance-id i-0xyz456 \
--device-index 1
You're running ECS tasks in awsvpc mode on m5.xlarge instances. Each instance supports 8 ENIs. You expect to run 10 tasks per instance but observe that only 7 tasks start per instance before ECS places additional tasks on new instances. Why?
mediumThe m5.xlarge supports 8 ENIs. Subnet IP space is not exhausted. Tasks are using awsvpc network mode. awsvpcTrunking is not enabled.
AECS reserves one ENI per instance for its own agent communication
Incorrect.The ECS agent uses the primary ENI (eth0) which is already counted in the instance's ENI limit. ECS doesn't reserve an additional ENI for agent traffic.BThe primary network interface (eth0) counts against the ENI limit — available task ENIs = 8 - 1 = 7
Correct!Every EC2 instance has eth0 as its primary ENI, which is included in the instance's total ENI limit. An m5.xlarge supports 8 total ENIs. With eth0 consuming one slot, 7 ENIs remain for awsvpc tasks. This is working as expected — 7 tasks per m5.xlarge in awsvpc mode without trunk ENIs. To increase density, enable awsvpcTrunking (which allows branch ENIs beyond the normal limit) or use larger instance types with higher ENI limits.CECS leaves one ENI slot empty as headroom for rolling deployments
Incorrect.ECS doesn't reserve an ENI slot for deployment headroom. The constraint is purely the EC2 ENI limit minus the primary interface.Dawsvpc mode requires two ENIs per task — one for inbound and one for outbound traffic
Incorrect.awsvpc mode uses one ENI per task. The ENI handles both inbound and outbound traffic.
Hint:Count the ENIs: 8 total - how many are already in use before any tasks start?