AWS Network Security Best Practices for Beginners
AWS network security means controlling which traffic can reach your resources, which paths that traffic takes through your infrastructure, and what happens when something looks suspicious. This page covers the practices that matter most: not an exhaustive list of every AWS feature, but the specific controls you should have in place for any production workload.
The goal is to control ingress, egress, and lateral movement. Tighten what can enter from the internet, limit what your own services can reach from inside the VPC, and reduce the blast radius if any one resource is compromised. Most of this comes down to applying AWS defaults in a deliberate, consistent way.
AWS network security in simple terms
When you deploy a workload in AWS, it lives inside a VPC (a virtual private network you control). Every resource in that VPC has at least one security group controlling what traffic it accepts. Subnets are either public (routable to the internet) or private (not). Route tables determine where packets go. VPC Flow Logs record what happened. GuardDuty watches for threats.
Think of it like a corporate office building. The VPC is the building itself. Subnets are the floors. Security groups are the key-card locks on individual office doors. NACLs are the security desk at each stairwell checking IDs before letting anyone up. VPC Flow Logs are the visitor log at the front desk. And GuardDuty is the camera system that flags if someone is wandering the halls at 3am. Each layer does something different, and no single layer is sufficient on its own.
How AWS network security works
Traffic entering your workload from the internet passes through several independent control points before it reaches your application code. The order matters: each control is independent, and a misconfiguration at any layer creates a gap.
A typical HTTPS request from a user to a web app follows this path:
- Internet to ALB (public subnet): The Application Load Balancer receives the request. Its security group allows inbound port 443 from
0.0.0.0/0. - ALB to EC2 (private subnet): The ALB forwards the request to an EC2 instance. The EC2 security group allows inbound only from the ALB’s security group on the application port (e.g., 8080).
- EC2 to RDS (isolated private subnet): The application queries the database. The RDS security group allows inbound only from the EC2 security group on port 5432.
- EC2 to AWS services (via VPC endpoint): The app calls Secrets Manager or S3. Traffic routes through a VPC endpoint and never touches the public internet.
- Monitoring layer: VPC Flow Logs record every connection. GuardDuty analyzes the logs for suspicious patterns in the background.
At no point in this flow does a private EC2 instance or database have a public IP or a direct route to the internet.
| Control | Applies at | What it protects | When to use it |
|---|---|---|---|
| Security group | Individual resource (EC2, RDS, Lambda) | Stateful inbound/outbound rules per resource | Always; every resource needs one |
| NACL | Subnet boundary | Coarse allow/deny before traffic reaches instances; stateless | Blocking IP ranges; compliance requirements; subnet-level safety nets |
| Private subnet + route table | Subnet routing | Eliminates direct internet reachability | Application tiers and databases; anything without a public IP |
| VPC endpoint (PrivateLink) | Service-to-service traffic | Keeps AWS API traffic off the public internet | Calling S3, SSM, Secrets Manager, KMS from private instances |
| VPC Flow Logs | ENI / subnet / VPC | Visibility into accepted and rejected traffic | Always; needed for incident investigation and monitoring |
| GuardDuty | Account/region-wide | Threat detection using ML across Flow Logs, DNS, CloudTrail | Always; enable in every region and account |
| AWS Network Firewall | VPC-wide egress path | Deep-packet inspection; domain and IP-based filtering for outbound | Higher-security or regulated environments with strict egress requirements |
When to use these practices
These practices apply broadly, but some scenarios make certain controls especially important:
- Public web app behind an ALB: ALB in public subnets, EC2 in private subnets. ALB security group allows port 443 from anywhere; EC2 security group allows traffic only from the ALB’s security group. Enable SSL certificates via ACM on the ALB rather than managing TLS on each instance.
- Private database tier: RDS or ElastiCache in isolated private subnets with no NAT route. Database security group allows inbound only from the application tier’s security group. No public endpoint.
- Admin access to EC2 instances: Use Systems Manager Session Manager instead of SSH. No inbound management ports need to be open at all.
- Sensitive workloads calling AWS services: Use VPC endpoints so traffic to S3, SSM, Secrets Manager, and KMS stays on the AWS private network and bypasses NAT Gateway entirely.
- Higher-security or regulated environments: Layer on NACLs for subnet-level blocking, enforce egress filtering via AWS Network Firewall, enable AWS Config rules to detect drift, and consider AWS Shield Overview for DDoS protection at the network edge.
Core best practices
Use private subnets for application and data tiers
Resources that do not need a direct internet route belong in private subnets. A private subnet has no Internet Gateway in its route table, which means inbound connections from the internet cannot reach it even if an attacker discovers the private IP. See Subnets in AWS for how subnet routing works and how to design a multi-tier layout.
Only two types of resources typically need public subnets: internet-facing load balancers and NAT Gateways. Everything else should be in private subnets: EC2 instances, ECS tasks, RDS, ElastiCache. For more on how public and private IP addressing interacts with subnet placement, see Private vs Public IP Addresses.
| Tier | Subnet type | Examples | Internet route? |
|---|---|---|---|
| Edge / load balancer | Public | ALB, NLB, NAT Gateway | Yes; needs to accept or route internet traffic |
| Application | Private | EC2, ECS tasks, Lambda in VPC | Outbound only, via NAT Gateway |
| Data | Private (isolated) | RDS, ElastiCache, DynamoDB DAX | No; databases should not initiate outbound connections |
Use least-privilege security groups and security-group references
Security groups should express the minimal access needed for a resource to function. Think of it like giving someone a key that only opens the one door they need, not a master key to the whole building. A database security group does not need to allow traffic from the whole VPC CIDR. It should allow inbound only from the specific application security group that queries it.
Use security group references (source = another security group ID) instead of CIDR ranges for inter-service traffic. This is more precise, adjusts automatically when instance IPs change, and scales cleanly as you add more instances. See Security Groups Explained for detailed configuration patterns.
# Allow the database to accept connections only from the app tier security group
aws ec2 authorize-security-group-ingress \
--group-id sg-0database123 \
--protocol tcp \
--port 5432 \
--source-group sg-0apptier456Use Session Manager instead of public SSH or RDP
Systems Manager Session Manager gives you a shell into any EC2 instance with the SSM Agent installed. It works over outbound HTTPS from the instance to SSM endpoints, so no inbound firewall rule is needed. No open port 22, no SSH keys to manage, and a full audit trail of every session.
To use Session Manager, the EC2 instance needs: the SSM Agent (pre-installed on most Amazon Linux and Windows AMIs), an IAM instance profile with AmazonSSMManagedInstanceCore, and outbound HTTPS to SSM endpoints. Use a VPC Interface Endpoint for SSM to avoid the public internet path entirely.
# Open a shell session via SSM (no open SSH port needed)
aws ssm start-session --target i-0abc123def456789aNever open port 22 or 3389 to the internet
SSH (port 22) or RDP (port 3389) open to 0.0.0.0/0 is one of the most common misconfigurations in AWS. Automated scanners find open SSH ports within minutes of creation and immediately begin brute-force attempts. There is no justification for this in production. Use Session Manager. If you must allow SSH in a specific environment, restrict it to a single known CIDR (your office IP), never to the open internet.
Put only the load balancer in public subnets
The Application Load Balancer is the only component that needs to accept connections from the internet. EC2 instances behind it receive traffic only from the ALB. Their security group should reference the ALB’s security group as the inbound source, not 0.0.0.0/0. This means that even if someone discovers the private IP of an EC2 instance, they cannot connect to it directly from outside the VPC.
Enable VPC Flow Logs and CloudTrail for visibility
VPC Flow Logs record metadata for every network connection in your VPC: source IP, destination IP, port, protocol, bytes transferred, and whether the connection was accepted or rejected. They do not capture packet contents, but they give you everything needed for security investigation and anomaly detection. Think of them as CCTV footage for your network: you may not need them every day, but when something goes wrong you will desperately wish they were on.
Enable Flow Logs at the VPC level and send them to CloudWatch Logs for real-time querying, or S3 for long-term storage with Athena. For API-level audit logging, AWS CloudTrail records every control-plane API call: who made it, from where, and what they changed.
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids vpc-0abc123def456789a \
--traffic-type ALL \
--log-destination-type cloud-watch-logs \
--log-group-name /aws/vpc/flowlogs \
--deliver-logs-permission-arn arn:aws:iam::123456789012:role/flow-logs-roleFlow Logs cost money at scale
You pay for data ingested into CloudWatch Logs or S3. For high-traffic VPCs, log volume can be significant. If cost is a concern, log only rejected traffic (—traffic-type REJECT) as a starting point. Rejected traffic is often the most security-relevant signal. You can always broaden to ALL traffic later.
Enable GuardDuty for threat detection
Amazon GuardDuty analyzes VPC Flow Logs, DNS query logs, and CloudTrail events using machine learning to identify threats: EC2 instances contacting known command-and-control IPs, unusual API calls from unexpected geolocations, cryptocurrency mining behavior, and port scanning originating from your instances.
GuardDuty requires no agents or manual configuration. You enable it per region in a few clicks or one CLI command. Enable it in every region and every account. In a multi-account setup, delegate GuardDuty administration to a central security account so findings aggregate in one place.
# Enable GuardDuty in the current region
aws guardduty create-detector --enableGuardDuty is low-effort, high-value GuardDuty takes about five minutes to enable and costs roughly $1–4 per month for a typical development workload. It runs continuously, requires no tuning, and generates findings automatically. There is almost no scenario where the cost of enabling it outweighs the value. Enable it now, before you need it.
Use VPC endpoints and PrivateLink for sensitive AWS service traffic
By default, traffic from your EC2 instances to AWS services like S3, Secrets Manager, SSM, and KMS routes through NAT Gateway and out to AWS public endpoints. PrivateLink (VPC Interface Endpoints) and VPC Gateway Endpoints reroute this traffic to stay entirely within the AWS network, with no public internet path.
Gateway Endpoints (for S3 and DynamoDB) are free with no per-hour cost. Interface Endpoints have a small per-hour charge but are worth it for sensitive operations like credential retrieval from Secrets Manager or key operations via KMS. You can also attach VPC Endpoint Policies to restrict which resources the endpoint can access.
Control egress intentionally
Most workloads default to allowing all outbound traffic. That is acceptable for application tiers that call external APIs, but database and data-processing tiers generally should not make arbitrary outbound connections. A compromised database with unrestricted outbound can exfiltrate data or connect to attacker-controlled infrastructure.
At minimum, restrict outbound on database security groups to block unnecessary destinations. For higher-security or regulated workloads, AWS Network Firewall sits in front of your NAT Gateway path and enforces domain-based or IP-based egress filtering across the entire VPC. See Network Egress Costs Explained for how VPC endpoint routing reduces both cost and egress exposure.
Use multi-AZ subnet design for resilient network boundaries
Create subnets in at least two Availability Zones for each tier: public, private application, and private data. This is not just for availability. It ensures that your security group rules and NACLs apply consistently across AZs. Single-AZ designs often lead to workarounds (larger CIDR ranges, looser rules) that weaken the security model over time.
Validate network exposure with AWS tooling
AWS provides tools to check for unintended network paths. VPC Reachability Analyzer tests whether a specific path exists between two resources and explains exactly which security group rules or route table entries allow or block it. AWS Config rules like vpc-sg-open-only-to-authorized-ports can flag security group drift automatically. Run these checks regularly and after any significant infrastructure change. See Troubleshooting Network Issues for how to use these tools in practice.
Security Groups vs Network ACLs vs AWS Network Firewall
Three controls handle traffic filtering in AWS and they solve different problems. Start with security groups and private subnets. Add NACLs for specific subnet-level requirements. Reach for AWS Network Firewall only when you need deep-packet inspection or centralized egress filtering. See Network ACLs for a deeper look at NACL configuration.
| Feature | Security Group | Network ACL | AWS Network Firewall |
|---|---|---|---|
| Applies at | Individual resource (ENI) | Subnet boundary | VPC-wide (dedicated subnet) |
| Stateful? | Yes; return traffic auto-allowed | No; must add rules for both directions | Yes (stateful rules supported) |
| Rule evaluation | All rules evaluated; most permissive wins | Rules evaluated by rule number; first match wins | Order-based; supports Suricata rule groups |
| IP blocking | Yes (CIDR-based) | Yes (CIDR-based; better for bulk blocking) | Yes, including domain name filtering |
| Deep packet inspection | No | No | Yes |
| Cost | Free | Free | Per-hour + data processing charges |
| Primary use | Fine-grained access control per resource; always configure | Subnet-level safety net; bulk IP blocking | Regulated workloads; strict egress filtering; domain blocking |
Decision logic Security groups are your primary control and should be configured carefully for every resource. NACLs are a supplement: use them to block large IP ranges or satisfy compliance requirements that mandate subnet-level ACLs. Do not try to replicate security group logic in NACLs; the stateless nature makes this error-prone. AWS Network Firewall is for environments that need domain-based egress filtering or deep-packet inspection at scale.
Common mistakes
- Leaving management ports open to the internet. Port 22 (SSH) or 3389 (RDP) open to
0.0.0.0/0is actively scanned within minutes. Use Session Manager. If you must allow SSH, restrict it to a specific known IP range and use key-based authentication only, never a password. - Using the default security group for production resources. The default security group allows all inbound traffic from other members of the same security group and all outbound. It is a broad, shared-access control that does not fit any specific workload. Create purpose-specific security groups with minimal rules instead.
- Giving databases or application servers public IPs. A public IP makes a resource directly reachable from the internet before any security group rule is evaluated. Private subnet placement is the first line of defense. Resources that do not need public IPs should not have them. See Private vs Public IP Addresses for how this works.
- Skipping VPC Flow Logs and GuardDuty. Without Flow Logs, you cannot investigate network incidents after the fact. Without GuardDuty, there is no automated detection for compromised instances or unusual traffic patterns. Both should be enabled from day one, before a security event occurs.
- Treating encryption as a substitute for network controls. TLS and encryption in transit are important, but they do not replace access controls. An attacker who can reach your database port over the network can potentially exploit software vulnerabilities even if the transport layer is encrypted. Encrypt data in transit and restrict network access.
- Allowing unnecessary outbound access everywhere. Unrestricted outbound on a compromised resource is a free ticket for data exfiltration. At a minimum, restrict outbound on database and data-tier security groups. Consider AWS Network Firewall if you need to enforce egress filtering at scale across the whole VPC.
Readiness checklist
Ingress
- No security group allows
0.0.0.0/0inbound on port 22, 3389, or any management port - Internet-facing ALB is the only resource in public subnets with direct internet exposure
- EC2 instances accept traffic only from the ALB security group, not from the internet directly
- Databases accept inbound only from the application tier security group
East-west / lateral traffic
- Security groups use security group references (not VPC CIDRs) for inter-tier access
- Database subnets have more restrictive rules than application subnets
- NACLs are custom (not default) on sensitive subnets; the default NACL allows all traffic
Egress
- Application tier uses NAT Gateway for necessary outbound internet access
- Database tier has no outbound internet route
- VPC Gateway Endpoints configured for S3 and DynamoDB (free; removes NAT path)
- VPC Interface Endpoints configured for SSM, Secrets Manager, and KMS
Visibility / detection
- VPC Flow Logs enabled at the VPC level, logs sent to CloudWatch Logs or S3
- AWS CloudTrail enabled in all regions (API-level audit logging)
- Amazon GuardDuty enabled in all regions and accounts
Admin access
- Systems Manager Session Manager configured for EC2 shell access
- No SSH key pairs distributed unless absolutely necessary
- If a bastion is used: inbound SSH restricted to specific known IP ranges only
Summary
- Defense in depth means security groups (per resource), NACLs (per subnet), private subnets (no internet route), VPC endpoints (private AWS service paths), and monitoring (Flow Logs, GuardDuty) all working together.
- Security groups are your primary control: configure least-privilege rules for every resource and use security group references for inter-tier access.
- Put application servers and databases in private subnets; only load balancers and NAT Gateways belong in public subnets.
- Replace SSH with Session Manager: no open ports, no key management, full audit trail.
- Enable VPC Flow Logs and GuardDuty from day one; visibility into what happened is essential for incident response.
- Use VPC endpoints for SSM, Secrets Manager, KMS, S3, and DynamoDB to keep sensitive traffic off the public internet and reduce NAT costs.
Frequently asked questions
What is the difference between a security group and a NACL?
Security groups are stateful firewalls that apply to individual resources (EC2, RDS, Lambda). They remember connection state, so return traffic is automatically allowed without an explicit outbound rule. NACLs are stateless firewalls that apply at the subnet boundary. You must add both inbound and outbound rules for every flow, including return traffic. Security groups are your primary control for fine-grained access. NACLs are a coarser subnet-level safety net, useful for blocking IP ranges before traffic reaches your instances.
Do private subnets make a resource secure by themselves?
No. A private subnet removes the direct internet route, which eliminates one attack path. But security groups on each resource still control what traffic reaches it from within the VPC. A misconfigured security group can still allow lateral movement from a compromised instance inside the same VPC. Private subnets are necessary but not sufficient. They work best in combination with least-privilege security groups, VPC Flow Logs, and GuardDuty.
Should I allow all outbound traffic in security groups?
For most application tiers, allowing all outbound is a reasonable starting point because instances need to reach AWS services and external APIs. However, for database and data-processing tiers, restrict outbound to known-necessary destinations only. A compromised database with unrestricted outbound can exfiltrate data or connect to attacker-controlled infrastructure. At minimum, block unnecessary egress on database security groups.
Do I need a bastion host to access EC2 instances?
No. AWS Systems Manager Session Manager gives you browser-based or CLI-based shell access to any EC2 instance with the SSM Agent installed, without any open ports, without SSH keys to manage, and with a full audit trail. This is the recommended approach for production environments. A bastion host is still an option but adds operational overhead and creates an additional attack surface. If you use a bastion, restrict its inbound SSH to specific known IP ranges only.
What AWS services help detect network threats?
Amazon GuardDuty is the primary threat detection service. It analyzes VPC Flow Logs, DNS logs, and CloudTrail events to detect things like EC2 instances communicating with known command-and-control IPs, unusual API call patterns, or port scanning from your instances. VPC Flow Logs provide the raw network data for investigation. AWS CloudTrail records all API calls for auditing. Together, these give you both automated detection and the historical data needed for incident investigation.