Multi-Tier Security with ENI Separation

Isolate traffic planes on a single EC2 instance using ENIs and security groups.

Introduction

30 Second Summary

Think about how a hotel separates its guest Wi-Fi from its staff network and its security cameras. Each network carries different traffic with different access rules, even though they all run through the same building.

In this project, you will build a multi-tier security architecture on AWS where a single EC2 instance participates in three isolated network segments using Elastic Network Interfaces (ENIs). You will separate public web traffic, private SSH management, and backend application connectivity onto dedicated interfaces with independent security rules.

What You'll Build

A dual-homed EC2 instance with separate ENIs for public web traffic, private SSH management, and backend application connectivity, plus multi-site hosting using secondary private IPs with individual Elastic IPs and ENI-level VPC Flow Logs for per-interface monitoring.

By the end of this project, you'll have:

  • A three-subnet VPC with per-ENI security groups enforcing strict traffic isolation across public, management, and backend network segments.
  • A dual-homed instance serving web traffic on one ENI and accepting SSH management on a completely separate ENI with its own security group.
  • A multi-site hosting configuration with secondary private IPs mapped to individual Elastic IPs on a single instance.
  • Secret Mission: Create a CloudWatch metric filter and alarm that detects rejected SSH attempts on the management ENI.

Are there any prerequisites?

You need an AWS account with free tier eligibility and the AWS CLI installed and configured. Basic understanding of VPC, EC2, and ENI concepts is recommended.

Set Up the Multi-Subnet VPC

A multi-tier security architecture needs a solid network foundation with distinct traffic zones. Before you can attach multiple ENIs to an instance, you need separate subnets for each ENI to live in.

In this step, you will create a VPC with three subnets (public, management, and backend), an Internet Gateway for public traffic, and three security groups that enforce strict per-ENI traffic policies.

In this step, get ready to:
  • Create a VPC with three subnets in the same Availability Zone.
  • Set up internet connectivity for the public subnet.
  • Create three security groups for per-ENI traffic isolation.
Create the VPC and three subnets

Each ENI must reside in a different subnet to avoid asymmetric routing issues. All three subnets must be in the same Availability Zone because ENIs cannot attach to instances across AZs.

  • Open the AWS Management Console in your browser.
  • Search for VPC in the top search bar and select VPC.
  • Click Create VPC.
  • Select VPC only.
  • Enter multi-tier-vpc in the Name tag field.
  • Enter 10.0.0.0/16 in the IPv4 CIDR block field.
  • Click Create VPC.

What is a CIDR block?

A CIDR block defines the range of IP addresses available in your VPC. 10.0.0.0/16 gives you 65,536 addresses to carve into smaller subnets.

Now create the three subnets. Note the Availability Zone you select for the first subnet. You must use the same AZ for all three.

  • In the left sidebar, click Subnets.
  • Click Create subnet.
  • Select multi-tier-vpc from the VPC ID dropdown.
  • Enter Public in the Subnet name field.
  • Choose an Availability Zone from the Availability Zone dropdown and note which one you pick (for example, us-east-1a).
  • Enter 10.0.1.0/24 in the IPv4 subnet CIDR block field.
  • Click Create subnet.

Repeat the same process to create the second subnet.

  • Click Create subnet again.
  • Select multi-tier-vpc from the VPC ID dropdown.
  • Enter Management in the Subnet name field.
  • Select the same Availability Zone you used for the Public subnet.
  • Enter 10.0.2.0/24 in the IPv4 subnet CIDR block field.
  • Click Create subnet.

Create the third and final subnet.

  • Click Create subnet again.
  • Select multi-tier-vpc from the VPC ID dropdown.
  • Enter Backend in the Subnet name field.
  • Select the same Availability Zone you used for the Public and Management subnets.
  • Enter 10.0.3.0/24 in the IPv4 subnet CIDR block field.
  • Click Create subnet.

Why must all subnets be in the same Availability Zone?

ENIs cannot attach to an instance in a different Availability Zone. Since your single EC2 instance will have ENIs in all three subnets, those subnets must share the same AZ.

Now create an Internet Gateway so resources in the Public subnet can reach the internet.

  • In the left sidebar, click Internet gateways.
  • Click Create internet gateway.
  • Enter multi-tier-igw in the Name tag field.
  • Click Create internet gateway.
  • On the next page, click Actions, then Attach to VPC.
  • Select multi-tier-vpc from the dropdown.
  • Click Attach internet gateway.

Finally, create a route table that sends internet-bound traffic to the IGW and associate it with the Public subnet.

  • In the left sidebar, click Route tables.
  • Click Create route table.
  • Enter Public-RT in the Name field.
  • Select multi-tier-vpc from the VPC dropdown.
  • Click Create route table.
  • On the route table details page, click the Routes tab.
  • Click Edit routes.
  • Click Add route.
  • Enter 0.0.0.0/0 in the Destination field.
  • Select Internet Gateway in the Target dropdown, then select your multi-tier-igw.
  • Click Save changes.

Now associate this route table with the Public subnet.

  • Click the Subnet associations tab.
  • Click Edit subnet associations.
  • Select the checkbox next to the Public subnet.
  • Click Save associations.

Why only the Public subnet gets a route to the internet?

The Management and Backend subnets are intentionally private. Without a route to the Internet Gateway, traffic from those subnets cannot reach (or be reached from) the public internet.

This is the foundation of the multi-tier pattern: only the front-end subnet has internet access. Administrative and database traffic stays private.

Create three security groups

Each ENI on your instance will have its own security group. This is the mechanism that enforces per-ENI traffic isolation.

Start with the web traffic security group.

  • In the left sidebar, click Security groups.
  • Click Create security group.
  • Enter SG-Web in the Security group name field.
  • Enter Allow HTTP and HTTPS from anywhere in the Description field.
  • Select multi-tier-vpc from the VPC dropdown.
  • Under Inbound rules, click Add rule.
  • Set Type to HTTP and Source to Anywhere-IPv4 (0.0.0.0/0).
  • Click Add rule again.
  • Set Type to HTTPS and Source to Anywhere-IPv4 (0.0.0.0/0).
  • Leave the Outbound rules as the default (all traffic allowed).
  • Click Create security group.

Next, create the management security group that only allows SSH from your IP.

  • Click Create security group.
  • Enter SG-Management in the Security group name field.
  • Enter Allow SSH from my IP only in the Description field.
  • Select multi-tier-vpc from the VPC dropdown.
  • Under Inbound rules, click Add rule.
  • Set Type to SSH.
  • Set Source to My IP (this auto-fills your current public IP address).
  • Leave the Outbound rules as the default (all traffic allowed).
  • Click Create security group.

Find your IP if needed

If My IP does not appear as an option, visit whatismyip.com to find your public IP address. Paste it into the Source field followed by /32 to restrict access to your IP only.

Finally, create the backend security group that simulates database-tier access.

  • Click Create security group.
  • Enter SG-Backend in the Security group name field.
  • Enter Allow MySQL from public subnet only in the Description field.
  • Select multi-tier-vpc from the VPC dropdown.
  • Under Inbound rules, click Add rule.
  • Set Type to MYSQL/Aurora (this sets port 3306).
  • Set Source to Custom and enter 10.0.1.0/24 (the Public subnet CIDR).
  • Leave the Outbound rules as the default (all traffic allowed).
  • Click Create security group.

How do these security groups enforce isolation?

Each ENI gets exactly one of these security groups. The primary ENI (web traffic) accepts HTTP/HTTPS from anyone. The management ENI only accepts SSH from your IP. The backend ENI only accepts MySQL connections from the public subnet.

Even though all three ENIs attach to the same instance, traffic on each interface is governed by completely different rules.

Verify the architecture

Before moving on, confirm that all the networking pieces are in place and configured correctly.

  • In the left sidebar, click Your VPCs.
  • Confirm multi-tier-vpc appears with CIDR 10.0.0.0/16.
  • Click Subnets in the left sidebar.
  • Filter by your VPC and confirm all three subnets (Public, Management, Backend) show the same Availability Zone.
  • Click Internet gateways and confirm multi-tier-igw shows state Attached.
  • Click Route tables and confirm Public-RT has the 0.0.0.0/0 route pointing to your IGW.
  • Click Security groups and confirm all three groups (SG-Web, SG-Management, SG-Backend) exist in multi-tier-vpc.

Subnets showing different Availability Zones?

If one subnet is in a different AZ from the others, you will need to delete it and recreate it in the correct AZ. You cannot change a subnet's AZ after creation.

Help me fix my subnet Availability Zone mismatch.

Your network foundation is ready. You have three isolated subnets, internet routing for the public tier, and three security groups ready to enforce per-ENI traffic policies.

Next up, you will launch an EC2 instance and attach a management ENI to create your first dual-homed configuration.

Deploy the Dual-Homed Instance with Management ENI

Your VPC has three subnets with distinct security groups ready to enforce different traffic policies. Now you need an instance that lives in multiple network segments simultaneously.

In this step, you will launch an EC2 instance in the Public subnet and then hot-attach a second ENI from the Management subnet. This creates a dual-homed instance where web traffic and SSH administration flow through completely separate interfaces with independent security rules.

In this step, get ready to:
  • Launch a t3.micro instance in the Public subnet with SG-Web attached.
  • Create a management ENI in the Management subnet and hot-attach it to your instance.
  • Test traffic isolation by verifying that per-ENI security groups block unauthorized traffic.
Launch the primary instance

This instance will serve as your web server, receiving public HTTP traffic on its primary ENI (eth0). You will attach additional ENIs in later substeps to give it access to other network segments.

  • In the AWS Management Console, navigate to the EC2 service using the search bar at the top.
  • Click Launch instances.
  • Enter web-instance in the Name field.
  • Under Application and OS Images, select Amazon Linux 2023 AMI (it should be the default selection).
  • Under Instance type, select t3.micro (free tier eligible).
  • Under Key pair, select an existing key pair or click Create new key pair to create one. Save the .pem file somewhere you can find it later.
  • Under Network settings, click Edit.
  • Select your multi-tier-vpc VPC from the VPC dropdown.
  • Select your Public subnet (10.0.1.0/24) from the Subnet dropdown.
  • Set Auto-assign public IP to Enable.
  • Under Firewall (security groups), select Select existing security group.
  • Choose SG-Web from the list.
  • Click Launch instance.

Wait for the instance state to show Running and the status checks to pass (this takes 1-2 minutes). Note down the instance's public IPv4 address from the instance details panel.

Why auto-assign public IP?

Enabling auto-assign public IP gives the instance an internet-routable address on its primary ENI (eth0). This is the interface that will handle inbound web traffic on ports 80 and 443.

Later, you will replace this auto-assigned IP with an Elastic IP. For now, the auto-assigned IP lets you SSH into the instance to verify your setup.

Instance stuck in Pending?

If the instance does not reach Running after 3 minutes, check that you selected a subnet in your multi-tier-vpc VPC (not the default VPC). Also confirm you chose t3.micro as the instance type.

help me troubleshoot why my EC2 instance is stuck in Pending state

Create and attach the management ENI

Your instance currently has one ENI in the Public subnet. To create a dedicated management plane, you will create a second ENI in the Management subnet with its own security group that only allows SSH.

  • In the EC2 console left sidebar, scroll down to Network & Security and click Network Interfaces.
  • Click Create network interface.
  • Enter management-eni in the Description field.
  • Select your Management subnet (10.0.2.0/24) from the Subnet dropdown.
  • Under Security groups, select SG-Management.
  • Click Create network interface.

You should see the new ENI appear with status Available. Now attach it to your running instance.

  • Select the management-eni you just created.
  • Click Actions, then Attach.
  • Select your web-instance from the instance dropdown.
  • Click Attach.

The ENI status changes to In-use and the device index shows 1 (meaning it is eth1 on the instance).

What is hot-attaching an ENI?

Hot-attaching means adding a network interface to an instance that is already running. You do not need to stop the instance first.

Amazon Linux automatically configures the new interface, its private IPv4 address, and route tables when an ENI is hot-attached. No manual OS configuration is needed.

Why must the management ENI be in the same Availability Zone?

ENIs cannot be attached to instances across Availability Zones. This is why you created all three subnets in the same AZ during Step 1. If you see your instance missing from the attach dropdown, double-check that the Management subnet is in the same AZ as the Public subnet.

Don't see your instance in the attach dropdown?

This usually means the ENI and instance are in different Availability Zones. Go to the Subnets page in the VPC console and confirm that your Management subnet and Public subnet share the same AZ.

help me fix the ENI attachment issue

Test traffic isolation

The whole point of attaching separate ENIs with different security groups is to control what traffic can reach each interface independently. Now you will prove that SG-Management blocks everything except SSH on the management ENI.

  • SSH into your instance using its public IPv4 address by running this command:
ssh -i [[PEMPATH="path/to/your-key.pem"]] ec2-user@[[EC2_PUBLIC_IP="your instance public IPv4 address"]]

What does this command do?

This connects to your instance using SSH. The -i flag specifies your private key file, and ec2-user is the default username for Amazon Linux instances.

  • Once connected, verify that both network interfaces are visible by running:
ip addr show

You should see two interfaces: eth0 with an IP in the 10.0.1.x range (Public subnet), and eth1 with an IP in the 10.0.2.x range (Management subnet).

  • Note down the private IP address of eth1 (the management ENI). It will be something like 10.0.2.x.
  • Test that port 80 is blocked on the management ENI by running this command from inside the instance:
curl --connect-timeout 5 http://[[MGMT_ENI_IP="your eth1 private IP, e.g. 10.0.2.x"]]:80

This connection should time out or fail. SG-Management only allows inbound TCP port 22. Port 80 is blocked because the security group attached to eth1 does not permit it.

What does this prove?

Each ENI enforces its own security group independently. Even though both interfaces are on the same instance, SG-Web on eth0 allows HTTP traffic while SG-Management on eth1 blocks it completely.

This is the management network pattern from the AWS documentation. The primary ENI handles public traffic and the secondary ENI handles backend management traffic through a separate, more restrictive subnet.

SSH connection refused or timed out?

Make sure you are connecting to the instance's public IPv4 address (not the management ENI private IP). Check that SG-Web allows outbound traffic and that your key pair file has correct permissions (chmod 400 your-key.pem on macOS/Linux).

help me troubleshoot my SSH connection

Your instance is now dual-homed with completely isolated traffic planes. Next up, you will configure secondary private IPs and map multiple Elastic IPs to host several websites from a single instance.

Configure Secondary Private IPs and Multiple Elastic IPs

Your instance now has two ENIs handling separate traffic planes. The next step is to make a single ENI serve multiple websites, each on its own public IP address.

AWS lets you assign secondary private IPs to any ENI and map each one to a separate Elastic IP. This is the cost-optimized multi-site hosting pattern tested on the SAA-C03: one instance, multiple sites, separate public addresses.

In this step, get ready to:
  • Assign two secondary private IPs to your primary ENI.
  • Allocate three Elastic IPs and associate each with a different private IP.
  • Verify multi-site hosting by curling each Elastic IP for a distinct response.
Assign two secondary private IPs

Each ENI starts with one primary private IP. You can add secondary private IPs from the same subnet CIDR, and each one can independently receive its own Elastic IP.

  • In the EC2 console left navigation pane, click Network Interfaces.
  • Select the checkbox for your primary ENI (the one in the Public subnet with SG-Web attached).
  • Click Actions, then Manage IP addresses.
  • Expand the network interface section.
  • Under IPv4 addresses, click Assign new IP address. Leave the field blank to let AWS auto-assign from the 10.0.1.0/24 range.
  • Click Assign new IP address again to add a second secondary IP.
  • Click Save.

What did this do?

You now have three private IPs on your primary ENI (eth0): one primary and two secondary. All three come from the 10.0.1.0/24 CIDR of the Public subnet.

Each private IP can receive its own Elastic IP, meaning a single ENI can host multiple public-facing services on different addresses.

Alternatively, you can accomplish the same thing with the AWS CLI. Note the ENI ID of your primary interface from the console.

  • Assign two secondary private IPs using the CLI by running this command:
aws ec2 assign-private-ip-addresses --network-interface-id [[PRIMARY_ENI_ID="your primary ENI ID e.g. eni-0abc123"]] --secondary-private-ip-address-count 2

What does this command do?

The --secondary-private-ip-address-count 2 flag tells AWS to auto-assign two secondary IPs from the subnet's available pool. If the command succeeds, no output is returned.

Console or CLI?

You only need to use one method. If you already assigned the IPs via the console steps above, skip the CLI command. Both produce the same result.

  • Verify the secondary IPs appeared by selecting your primary ENI in the console and checking the Details tab. You should see three private IPv4 addresses listed.

Only seeing one IP?

Make sure you selected the correct ENI (the one in the Public subnet, not the Management ENI). The Management ENI is in 10.0.2.0/24.

If using the CLI, confirm you used the correct --network-interface-id value for the primary ENI. Help me troubleshoot my secondary IP assignment.

Allocate and associate Elastic IPs

Each private IP needs its own Elastic IP to be reachable from the internet. You will allocate three EIPs and map each to a specific private IP on your primary ENI.

  • In the EC2 console left navigation pane, click Elastic IPs (under Network & Security).
  • Click Allocate Elastic IP address.
  • Leave defaults (Amazon's pool of IPv4 addresses) and click Allocate.
  • Repeat the allocation two more times so you have three Elastic IPs total.

Now associate EIP-1 with the primary private IP of your ENI.

  • Select the first Elastic IP and click Actions, then Associate Elastic IP address.
  • For Resource type, choose Network interface.
  • For Network interface, select your primary ENI.
  • For Private IP address, select the primary private IP (the first one listed).
  • Click Associate.

Now associate EIP-2 and EIP-3 with the two secondary private IPs. For the secondary IPs, use the CLI for precision.

  • Associate EIP-2 with the first secondary private IP by running:
aws ec2 associate-address --allocation-id [[EIP2_ALLOC_ID="your second EIP allocation ID e.g. eipalloc-0abc123"]] --network-interface-id [[PRIMARY_ENI_ID="your primary ENI ID e.g. eni-0abc123"]] --private-ip-address [[SECONDARY_IP_1="your first secondary private IP e.g. 10.0.1.45"]]
  • Associate EIP-3 with the second secondary private IP by running:
aws ec2 associate-address --allocation-id [[EIP3_ALLOC_ID="your third EIP allocation ID e.g. eipalloc-0def456"]] --network-interface-id [[PRIMARY_ENI_ID="your primary ENI ID e.g. eni-0abc123"]] --private-ip-address [[SECONDARY_IP_2="your second secondary private IP e.g. 10.0.1.67"]]

What does the --private-ip-address flag do?

Without this flag, the Elastic IP defaults to the primary private IP. By specifying --private-ip-address, you target a specific secondary IP on the same ENI.

Each successful association returns an AssociationId in the output. Note these down for cleanup later.

Why use network-interface-id instead of instance-id?

Your instance has multiple ENIs attached. When multiple ENIs exist, you must specify the --network-interface-id to tell AWS which interface to associate the EIP with.

  • Go back to Elastic IPs in the console. All three EIPs should now show an Associated instance ID and distinct private IP addresses.

Association failed?

Confirm you are using the correct allocation ID (starts with eipalloc-). Also check that the private IP address you specified actually exists on the ENI.

If you see a "resource already associated" error, add --allow-reassociation to the command. Help me fix my Elastic IP association error.

EIP charges apply

AWS charges $0.005/hr for each public IPv4 address. With three EIPs, you will be charged approximately $0.015/hr. Make sure to release all EIPs during cleanup at the end of this project.

Verify multi-IP hosting setup

Now you will SSH into the instance, install Apache, and configure three virtual hosts. Each virtual host responds on a different private IP, which maps to its own Elastic IP.

  • SSH into your web instance using any of the three EIPs by running:
ssh -i [[PEMPATH="path to your .pem key file"]] ec2-user@[[EIP1_ADDRESS="your first Elastic IP address"]]
  • Confirm all three private IPs appear on eth0 by running:
ip addr show eth0

You should see one primary and two secondary inet addresses listed under eth0, all in the 10.0.1.x range.

Why are the secondary IPs already visible?

Amazon Linux automatically configures secondary private IPs on the interface. No manual OS configuration is needed.

  • Install and start Apache by running:
sudo yum install httpd -y && sudo systemctl start httpd

What does this do?

This installs the Apache HTTP server and starts it immediately. Apache listens on port 80 by default on all available IP addresses.

Next, create three virtual host configuration files so Apache serves a different page for each IP.

  • Create the virtual host config file by running:
sudo bash -c 'cat > /etc/httpd/conf.d/multisite.conf << EOF
<VirtualHost [[PRIMARY_PRIVATE_IP="your primary private IP e.g. 10.0.1.10"]]:80>
    DocumentRoot /var/www/site1
    ServerName site1.local
</VirtualHost>

<VirtualHost [[SECONDARY_IP_1="your first secondary private IP e.g. 10.0.1.45"]]:80>
    DocumentRoot /var/www/site2
    ServerName site2.local
</VirtualHost>

<VirtualHost [[SECONDARY_IP_2="your second secondary private IP e.g. 10.0.1.67"]]:80>
    DocumentRoot /var/www/site3
    ServerName site3.local
</VirtualHost>
EOF'

What do these virtual hosts do?

Each <VirtualHost> block binds Apache to a specific private IP address on port 80. When traffic arrives at that IP, Apache serves the corresponding DocumentRoot directory.

This is the SAA-C03 multi-site hosting pattern: one instance, three IPs, three websites.

  • Create the document root directories and unique index pages by running:
sudo mkdir -p /var/www/site1 /var/www/site2 /var/www/site3
echo '<h1>Site 1 - Primary IP</h1>' | sudo tee /var/www/site1/index.html
echo '<h1>Site 2 - Secondary IP 1</h1>' | sudo tee /var/www/site2/index.html
echo '<h1>Site 3 - Secondary IP 2</h1>' | sudo tee /var/www/site3/index.html
  • Restart Apache to load the new configuration by running:
sudo systemctl restart httpd

Now test from your local machine. Open a new terminal on your computer (not the SSH session).

  • Curl each Elastic IP from your local terminal to verify distinct responses:
curl http://[[EIP1_ADDRESS="your first Elastic IP address"]]
curl http://[[EIP2_ADDRESS="your second Elastic IP address"]]
curl http://[[EIP3_ADDRESS="your third Elastic IP address"]]

Each curl should return a different HTML heading: Site 1, Site 2, and Site 3.

Getting connection refused or timeout?

Confirm that SG-Web allows inbound TCP port 80 from 0.0.0.0/0. Also verify Apache is running with sudo systemctl status httpd on the instance.

If all three curls return the same page, double-check that the IP addresses in your multisite.conf match the actual private IPs shown by ip addr show eth0. Help me debug my Apache virtual host configuration.

SAA-C03 exam relevance

This pattern appears in Domain 4 (Cost-Optimized Architectures). Instead of running three instances for three websites, you use secondary private IPs with separate EIPs on one instance. This reduces compute costs while maintaining distinct public addresses for each site.

Your instance is now serving three websites on three separate public IPs from a single ENI. Next up, you will enable ENI-level VPC Flow Logs to monitor and filter traffic per interface.

Enable ENI-Level VPC Flow Logs

Your dual-homed instance now separates web traffic from management traffic using per-ENI security groups. But how do you prove that traffic is actually being blocked?

VPC Flow Logs can capture traffic at the individual ENI level, giving you per-interface visibility into what's accepted and rejected. In this step, you'll enable flow logs on the management ENI and use CloudWatch Logs to identify blocked traffic attempts.

In this step, get ready to:
  • Create an IAM role that allows VPC Flow Logs to publish to CloudWatch Logs.
  • Create a flow log on the management ENI capturing all traffic.
  • Generate traffic and identify ACCEPT and REJECT records in CloudWatch.
Create the IAM role for flow logs

VPC Flow Logs need permission to write log data into CloudWatch Logs. You'll create an IAM policy with the required permissions, then attach it to a role that the flow logs service can assume.

  • Open the AWS Management Console and navigate to the IAM service.
  • In the left sidebar, click Policies.
  • Click Create policy.
  • Switch to the JSON tab in the policy editor.
  • Replace the existing JSON with the following policy document:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams"
      ],
      "Resource": "*"
    }
  ]
}

What does this policy do?

This policy grants five CloudWatch Logs permissions. CreateLogGroup and CreateLogStream allow the flow logs service to set up the log destination.

PutLogEvents allows it to write the actual log records. The two Describe permissions let it check whether the log group and streams already exist before creating new ones.

  • Click Next.
  • Enter FlowLogPolicy as the policy name.
  • Click Create policy.

Now create the IAM role that the VPC Flow Logs service will assume.

  • In the left sidebar, click Roles.
  • Click Create role.
  • For Trusted entity type, select Custom trust policy.
  • Replace the default trust policy JSON with the following:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "vpc-flow-logs.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

What is a trust policy?

A trust policy defines which AWS service is allowed to "assume" this role. By specifying vpc-flow-logs.amazonaws.com as the principal, you're telling AWS that only the VPC Flow Logs service can use this role's permissions.

  • Click Next.
  • In the permissions search box, type FlowLogPolicy and select the checkbox next to it.
  • Click Next.
  • Enter FlowLogRole as the role name.
  • Click Create role.

You should see a success banner confirming the role was created. Copy the role ARN from the role details page. You'll need it in the next substep.

Don't see FlowLogPolicy in the permissions list?

Make sure you search for the exact name FlowLogPolicy. If it doesn't appear, go back to Policies in the sidebar and confirm the policy was created successfully.

Help me troubleshoot my FlowLogPolicy not appearing when creating the IAM role.

Create a flow log on the management ENI

VPC Flow Logs can be created at the VPC, subnet, or individual ENI level. By targeting a single ENI, you get granular visibility into exactly one traffic plane without the noise of unrelated traffic.

Why monitor at the ENI level?

A VPC-level flow log captures everything, which can be overwhelming to filter. An ENI-level flow log lets you focus on exactly the interface you care about. For security monitoring, this means you can watch the management ENI for unauthorized access attempts without sorting through web traffic noise.

  • Navigate to the EC2 service in the AWS Console.
  • In the left sidebar under Network & Security, click Network Interfaces.
  • Select the management ENI (the one in the Management subnet with SG-Management attached).
  • Click the Flow Logs tab at the bottom of the page.
  • Click Create flow log.

Configure the flow log with these settings:

  • Set Filter to All (captures both accepted and rejected traffic).
  • Set Maximum aggregation interval to 1 minute for faster log delivery.
  • For Destination, select Send to CloudWatch Logs.
  • In the Destination log group field, enter eni-management-flow-logs.
  • For IAM role, select FlowLogRole from the dropdown.
  • Click Create flow log.

You should see a success message confirming the flow log was created. The flow log now captures all traffic to and from the management ENI.

What does the filter 'All' mean?

The filter determines which traffic gets recorded. "All" captures both ACCEPT and REJECT records. "Accept" would only show successful connections, and "Reject" would only show blocked attempts. You want both so you can see the full picture of what's hitting your management ENI.

FlowLogRole not appearing in the dropdown?

Make sure the role's trust policy has vpc-flow-logs.amazonaws.com as the principal service. If you used a different service name, the console won't show it as a valid option for flow logs.

Help me fix my FlowLogRole so it appears in the flow log creation dropdown.

Generate and view traffic in CloudWatch

Flow logs take a few minutes to start delivering records to CloudWatch. While you wait, you'll generate some traffic on the management ENI to create both ACCEPT and REJECT records.

  • SSH into your web instance using the EIP-1 public IP (the same way you connected in the previous step).
  • Find the private IP of your management ENI (eth1). Run this command to confirm:
ip addr show eth1

Note the private IP address listed for eth1 (it will be in the 10.0.2.x range). You'll use this IP to generate traffic.

  • Generate an ACCEPT record by connecting to the management ENI on port 22 (SSH is allowed by SG-Management):
# This should succeed - SSH is allowed by SG-Management
ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no ec2-user@[[MGMT_ENI_IP="your management ENI private IP (10.0.2.x)"]] exit
  • Generate a REJECT record by attempting to connect on port 80 (HTTP is NOT allowed by SG-Management):
# This should time out - port 80 is blocked by SG-Management
curl --connect-timeout 5 http://[[MGMT_ENI_IP="your management ENI private IP (10.0.2.x)"]]

What should I see?

The SSH attempt may succeed or show a connection reset (both generate an ACCEPT in the flow log). The curl command will time out after 5 seconds. That's expected. SG-Management only allows port 22, so the port 80 attempt gets silently dropped and recorded as a REJECT.

Wait 2-3 minutes for the flow log records to appear in CloudWatch. Flow logs are not real-time.

Flow log delivery delay

It can take up to 10 minutes for records to appear in CloudWatch after creating a new flow log. If you don't see records after 3 minutes, wait a bit longer and refresh.

  • Open the CloudWatch service in the AWS Console.
  • In the left sidebar, expand Logs and click Log groups.
  • Click the eni-management-flow-logs log group.
  • Click the log stream (it will be named with the management ENI ID).

You should see log entries with the flow log record format. Each record contains these fields in order:

# Flow log record format:
# version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status

How do I read flow log records?

The key fields to focus on are srcaddr (where traffic came from), dstport (which port it targeted), and action (ACCEPT or REJECT).

Look for records where dstport is 80 and action is REJECT. Those are your blocked HTTP attempts. Records where dstport is 22 and action is ACCEPT confirm that SSH was allowed through.

Now filter for rejected traffic using a CloudWatch filter pattern.

  • Go back to the eni-management-flow-logs log group.
  • Click Search log group at the top.
  • Enter the following filter pattern to find all rejected traffic:
[version, account, eni, source, destination, srcport, destport, protocol, packets, bytes, windowstart, windowend, action="REJECT", flowlogstatus]
  • Click Search or press Enter.

You should see only the REJECT records, including your blocked port 80 attempt. This filter pattern uses CloudWatch's structured log syntax to match specific field values in the flow log format.

No log records appearing?

Flow log delivery can take up to 10 minutes. Wait and refresh the log stream page. If the log group itself doesn't exist, check that your FlowLogRole has the correct trust policy with vpc-flow-logs.amazonaws.com and that the permissions policy is attached.

Help me figure out why my VPC Flow Log records aren't appearing in CloudWatch.

You now have concrete proof that your security groups are working. The management ENI accepts SSH and rejects everything else. In the next step, you'll extend your web instance into the backend subnet to complete the three-tier architecture.

Build the Dual-Homed Application-Tier Pattern

Your web instance already separates public traffic from management traffic using two ENIs in different subnets. In a real production architecture, this instance also needs to communicate with backend resources like databases and application servers.

In this step, you will add a third ENI to create a fully dual-homed instance that spans front-end, management, and back-end network segments simultaneously. This three-tier isolation pattern is a core SAA-C03 architecture tested under Domain 1 (Secure Architectures) and Domain 2 (Resilient Architectures).

In this step, get ready to:
  • Launch a backend instance in the Backend subnet to simulate a database server.
  • Create and attach a third ENI to the web instance, connecting it to the Backend subnet.
  • Test cross-subnet connectivity and confirm backend isolation from the internet.
Launch a backend instance

The backend instance simulates a database or application-tier server sitting in a private subnet with no direct internet access. Only resources within the VPC that match the SG-Backend rules can reach it.

  • In the AWS Management Console, navigate to the EC2 service.
  • Click Launch instances.
  • Enter backend-instance in the Name field.
  • Under Application and OS Images, select Amazon Linux 2023 AMI (it should be pre-selected).
  • Under Instance type, select t3.micro.
  • Under Key pair, select the same key pair you used for the web instance.
  • Under Network settings, click Edit.
  • Select your multi-tier-vpc VPC.
  • Select the Backend subnet (10.0.3.0/24).
  • Set Auto-assign public IP to Disable.
  • Under Firewall (security groups), select Select existing security group.
  • Choose SG-Backend from the dropdown.
  • Click Launch instance.

Why no public IP?

The backend instance has no public IP and sits in a subnet with no route to the Internet Gateway. This makes it completely unreachable from the internet.

Only resources within the VPC that pass SG-Backend's rules (inbound TCP 3306 from 10.0.1.0/24) can reach it. This is how production databases are isolated.

  • Wait for the backend instance to reach the Running state in the EC2 Instances dashboard.
  • Note down the backend instance's private IP address (it will be in the 10.0.3.x range). You will need this later to test connectivity.

Instance stuck in Pending?

Make sure you selected the correct Backend subnet in your multi-tier-vpc. If you accidentally chose the default VPC, the subnet CIDRs will not match 10.0.3.0/24.

help me troubleshoot my backend instance launch

Create and attach a backend ENI to the web instance

Your web instance currently has two ENIs: eth0 in the Public subnet and eth1 in the Management subnet. Adding a third ENI in the Backend subnet makes the instance dual-homed across front-end and back-end network segments.

  • In the EC2 console left sidebar, click Network Interfaces under Network & Security.
  • Click Create network interface.
  • Enter backend-eni in the Description field.
  • Select the Backend subnet (10.0.3.0/24).
  • Under Security groups, select SG-Backend.
  • Click Create network interface.

Now attach this new ENI to your web instance.

  • Select the newly created backend-eni in the Network Interfaces list.
  • Click Actions, then Attach.
  • Select your web instance from the dropdown.
  • Click Attach.

What is the architecture now?

Your web instance now has three ENIs, each in a different subnet with a different security group:

  • eth0 (Public subnet, SG-Web): receives front-end web traffic on ports 80/443.
  • eth1 (Management subnet, SG-Management): accepts SSH from your IP only.
  • eth2 (Backend subnet, SG-Backend): connects to backend database resources on port 3306.

Front-end traffic arrives on eth0, the instance processes it, then reaches the backend database via eth2 on a completely separate subnet with different security rules. Each traffic plane is fully isolated.

  • Verify the attachment by selecting your web instance in the Instances dashboard, then clicking the Networking tab.
  • You should see three network interfaces listed: device index 0 (eth0), device index 1 (eth1), and device index 2 (eth2).

Attach failed?

ENIs cannot attach across Availability Zones. Confirm the Backend subnet is in the same AZ as your web instance. Also check that the t3.micro instance type supports three ENIs (it does, the maximum is 3 network interfaces for t3.micro).

help me troubleshoot my ENI attachment failure

Test cross-subnet connectivity

With all three ENIs attached, you can now prove that the web instance can reach the backend instance through the Backend subnet. You will also confirm that the backend instance is completely isolated from the internet.

  • SSH into your web instance using its public EIP (the one associated with the primary private IP on eth0).

Run this command to confirm all three interfaces are visible on the instance:

ip addr show

You should see three interfaces: eth0 with an IP in the 10.0.1.x range, eth1 with an IP in the 10.0.2.x range, and eth2 with an IP in the 10.0.3.x range.

Why does Amazon Linux auto-configure eth2?

Amazon Linux automatically configures interfaces, private IPv4 addresses, and route tables when network interfaces are hot-attached. You do not need any manual OS configuration for the new ENI to receive its IP and become reachable.

  • Test connectivity to the backend instance by attempting a TCP connection on port 3306 (the port SG-Backend allows). Replace the IP with the backend instance's private IP you noted earlier:
nc -zv [[BACKEND_IP="your backend instance private IP (10.0.3.x)"]] 3306 -w 5

What does this command do?

The nc -zv command attempts a TCP connection without sending any data. The -w 5 flag sets a 5-second timeout. If the connection succeeds, it means the security groups allow traffic from your web instance's Backend subnet IP to the backend instance on port 3306.

The connection attempt will show Connection refused or succeed depending on whether a service is listening on port 3306. Both outcomes prove the network path is open. The key indicator is that the connection is NOT timing out, which would indicate a security group block.

Now confirm that the backend instance has no internet access.

  • Go back to the EC2 console and select the backend instance.
  • In the Networking tab, confirm there is no public IPv4 address assigned.
  • Check the route table for the Backend subnet in the VPC console. It should only have the local route (10.0.0.0/16 to local) and no 0.0.0.0/0 route to the Internet Gateway.

Why is this the three-tier isolation pattern?

Your architecture now has three completely isolated network segments. Internet users reach the web instance via eth0. Administrators reach it via eth1 (SSH only). The web instance reaches backend databases via eth2.

Even if an attacker compromises the web server through port 80, they cannot pivot to the management plane (different security group on eth1) or directly expose the backend instance to the internet (no public IP, no IGW route). This is the dual-homed architecture pattern AWS tests on the SAA-C03 exam.

Connection timing out to the backend instance?

A timeout means the security group is blocking traffic. Make sure SG-Backend allows inbound TCP 3306 from 10.0.1.0/24 (the Public subnet CIDR where eth0 originates). Also verify that both the backend ENI on your web instance and the backend instance itself use SG-Backend.

help me debug connectivity between my web instance and backend instance

Your multi-tier architecture is complete. The web instance participates in three separate network segments simultaneously, with each ENI enforcing its own security policy. Next up, you will clean up all the resources to avoid any charges.

Secret mission

Create a CloudWatch Metric Filter and Alarm for Rejected SSH on the Management ENI

Your management ENI is isolated, but how do you know when someone is trying to break in? In this secret mission, you'll build an operational alarm that triggers when the management ENI receives 5 or more rejected SSH attempts within 10 minutes. This is real-world security monitoring at the per-interface level.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses Elastic IPs that cost $0.005/hr each and EC2 instances that count against your free tier hours, so cleanup is recommended if you are done experimenting.

Cost warning

Each Elastic IP costs $0.005/hr whether associated or not. With three EIPs allocated, you are spending roughly $0.36/day if you leave them running. The two t3.micro instances consume your 750 free tier hours per month.

If you are done with the project, choose Pause or Delete below to stop charges immediately.

Resources you used:

  • 3 Elastic IP addresses.
  • 2 EC2 instances (t3.micro).
  • 2 additional ENIs (management and backend).
  • 1 VPC with 3 subnets (Public, Management, Backend).
  • 1 Internet Gateway and 1 public route table.
  • 3 security groups (SG-Web, SG-Management, SG-Backend).
  • 1 IAM role and policy (FlowLogRole).
  • 1 VPC Flow Log and 1 CloudWatch Log Group.

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with ENI configurations.

  • Your Elastic IPs will continue to incur charges at $0.005/hr each.
  • Your EC2 instances will count against your 750 free tier hours per month.
  • CloudWatch Logs storage is minimal but will accumulate over time.

Pause - I'll come back to this later

Release EIPs and stop instances to eliminate ongoing charges while keeping your VPC architecture intact.

  • Disassociate all three Elastic IPs from the instance, then release them in the EC2 console under Elastic IPs.
  • Stop both EC2 instances (do not terminate). Stopped instances do not incur compute charges.
  • Your VPC, subnets, security groups, ENIs, IAM role, and flow log will remain for when you return.
  • When you resume, start the instances and allocate new EIPs to continue.

Delete - I don't want to use this again

Remove all project resources completely. Follow this order to avoid dependency errors.

  • Disassociate and release all three Elastic IPs.
  • Detach the management ENI and backend ENI from the web instance.
  • Terminate both EC2 instances.
  • Delete the management and backend ENIs (they will be in "available" state after detach).
  • Delete the VPC Flow Log from the management ENI.
  • Delete the CloudWatch Log Group eni-management-flow-logs.
  • Delete the three security groups (SG-Web, SG-Management, SG-Backend).
  • Delete the three subnets (Public, Management, Backend).
  • Detach the Internet Gateway from the VPC, then delete the Internet Gateway.
  • Delete the public route table (if it is not the main route table).
  • Delete the VPC.
  • In IAM, detach the policy from FlowLogRole, delete the policy, then delete the role.

Nice Work!

Nice Work!

You just built a production-grade multi-tier security architecture using ENI traffic separation. This project covers advanced ENI patterns that map directly to SAA-C03 Domain 1 (Secure Architectures), Domain 2 (Resilient Architectures), and Domain 4 (Cost-Optimized Architectures).

You've learned how to:

  • Deploy a dual-homed instance with isolated management and public traffic planes using per-ENI security groups for strict traffic isolation.
  • Configure multi-site hosting on a single instance using secondary private IPs mapped to individual Elastic IPs for cost-optimized hosting.
  • Enable ENI-level VPC Flow Logs publishing to CloudWatch Logs and identify accepted versus rejected traffic per interface.
  • Secret Mission: Built a CloudWatch metric filter and alarm that detects rejected SSH attempts on the management ENI for per-interface security monitoring.

Ready to quiz yourself?