# amazon-ec2 Best Practices
Treat every EC2 instance as versioned, secured, and continuously optimized code. Adhere strictly to the AWS Well-Architected Framework and Foundational Security Best Practices (FSBP).
## 1. Code Organization and Structure
**Always define EC2 resources using Infrastructure as Code (IaC).** CloudFormation is the standard for AWS-native deployments. Break down templates into modular, reusable components.
### 1.1. Modular CloudFormation Templates
Organize your CloudFormation templates logically. Separate networking, IAM, and EC2 instance definitions into distinct, composable stacks or modules.
❌ **BAD: Monolithic Template** ```yaml # all-in-one-stack.yaml Resources: VPC: # ... Subnet: # ... SecurityGroup: # ... IAMRole: # ... EC2Instance: # ... ```
✅ **GOOD: Modular Stacks** ```yaml # network-stack.yaml Resources: VPC: # ... Subnet: # ... Outputs: # Export VPC ID, Subnet IDs # iam-stack.yaml Resources: IAMRole: # ... Outputs: # Export Role ARN # ec2-instance-stack.yaml Resources: EC2Instance: Type: AWS::EC2::Instance Properties: ImageId: ami-0abcdef1234567890 InstanceType: !Ref InstanceTypeParam SecurityGroupIds: [!ImportValue MySecurityGroup] IamInstanceProfile: !ImportValue MyInstanceProfile # ... ```
### 1.2. Enforce Consistent Tagging
Implement a unified tagging strategy from your IaC templates. Tags are critical for cost allocation, resource discovery, and automation.
❌ **BAD: Missing or Inconsistent Tags** ```yaml EC2Instance: Type: AWS::EC2::Instance Properties: # ... no tags or only a few ```
✅ **GOOD: Mandatory Tags via IaC** ```yaml EC2Instance: Type: AWS::EC2::Instance Properties: # ... Tags: - Key: Environment Value: !Ref EnvironmentTag - Key: Project Value: !Ref ProjectTag - Key: Owner Value: !Ref OwnerTag - Key: CostCenter Value: !Ref CostCenterTag ```
## 2. Common Patterns and Anti-patterns
### 2.1. Right-Sizing Instances
**Analyze workloads to select the optimal instance family and size.** This is the single biggest driver for performance efficiency and cost optimization.
❌ **BAD: Defaulting to `t2.micro` or Over-provisioning** ```yaml EC2Instance: Type: AWS::EC2::Instance Properties: InstanceType: t2.micro # For a production database! ```
✅ **GOOD: Workload-Specific Instance Types** ```yaml EC2Instance: Type: AWS::EC2::Instance Properties: InstanceType: m5.large # General purpose, balanced CPU/memory # or c5.xlarge for compute-intensive, r5.large for memory-intensive ```
### 2.2. Least Privilege IAM Instance Profiles
**Attach IAM roles to EC2 instances via Instance Profiles, granting only the minimum necessary permissions.** Never embed AWS credentials directly on an instance.
❌ **BAD: Over-privileged IAM Role** ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "*", "Resource": "*" } ] } ```
✅ **GOOD: Tightly Scoped IAM Role** ```yaml # CloudFormation IAM Role for EC2 BastionHostRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: { Service: [ 'ec2.amazonaws.com' ] } Action: [ 'sts:AssumeRole' ] ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM # For SSM access Policies: - PolicyName: S3ReadAccess PolicyDocument: Statement: - Effect: Allow Action: [ 's3:GetObject' ] Resource: !Sub 'arn:${AWS::Partition}:s3:::my-config-bucket/*' ```
### 2.3. Strict Security Group Rules
**Security groups must enforce the principle of least privilege.** Restrict inbound and outbound traffic to only what is absolutely required.
❌ **BAD: Open Ingress/Egress** ```yaml SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0 # SSH open to the world! ```
✅ **GOOD: Restricted Access** ```yaml SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref RemoteAccessCIDR # Parameter for specific IP range - IpProtocol: tcp FromPort: 80 ToPort: 80 SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup # Only allow from LB ```
### 2.4. Require IMDSv2
**Always configure EC2 instances to require Instance Metadata Service Version 2 (IMDSv2).** This prevents SSRF vulnerabilities.
❌ **BAD: Relying on IMDSv1** ```yaml EC2Instance: Type: AWS::EC2::Instance Properties: # ... no MetadataOptions specified ```
✅ **GOOD: Enforce IMDSv2** ```yaml EC2Instance: Type: AWS::EC2::Instance Properties: # ... MetadataOptions: HttpPutResponseHopLimit: 1 HttpTokens: required ```
## 3. Performance Considerations
### 3.1. Leverage Launch Templates for Auto Scaling
**Use EC2 Launch Templates with Auto Scaling Groups.** This ensures consistent configuration, supports multiple instance types, and simplifies updates.
❌ **BAD: Launch Configurations (deprecated)** ```yaml AutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: LaunchConfigurationName: !Ref MyLaunchConfig # Avoid ```
✅ **GOOD: Launch Templates** ```yaml AutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: LaunchTemplate: LaunchTemplateId: !Ref MyLaunchTemplate Version: !GetAtt MyLaunchTemplate.LatestVersionNumber # ... ```
### 3.2. EBS Optimization and Encryption
**Use EBS-optimized instances for I/O-intensive workloads and encrypt all EBS volumes by default.**
❌ **BAD: Unencrypted Volumes, Non-EBS Optimized** ```yaml BlockDeviceMappings: - DeviceName: /dev/sdh Ebs: VolumeSize: 100 Encrypted: false # Never! ```
✅ **GOOD: Encrypted, EBS-Optimized** ```yaml EC2Instance: Type: AWS::EC2::Instance Properties: InstanceType: m5.large # Often EBS-optimized by default or configurable BlockDeviceMappings: - DeviceName: /dev/sdh Ebs: VolumeSize: 100 Encrypted: true # Always! KmsKeyId: !Ref MyEBSKMSKey # Use a customer-managed key ```
## 4. Common Pitfalls and Gotchas
### 4.1. Avoid Hardcoding Sensitive Information
**Never hardcode secrets, ARNs, or environment-specific values.** Use CloudFormation parameters, SSM Parameter Store, or AWS Secrets Manager.
❌ **BAD: Hardcoded S3 Bucket Name** ```bash #!/bin/bash aws s3 cp /var/log/app.log s3://my-prod-bucket-123/logs/ ```
✅ **GOOD: Parameterized Values** ```yaml # CloudFormation UserData UserData: Fn::Base64: !Sub | #!/bin/bash aws s3 cp /var/log/app.log s3://${LogBucketName}/logs/ Parameters: LogBucketName: Type: String Description: S3 bucket for application logs ```
### 4.2. Enable Termination Protection
**Enable termination protection for critical EC2 instances.** This prevents accidental deletion.
❌ **BAD: No Termination Protection** ```yaml EC2Instance: Type: AWS::EC2::Instance Properties: # ... no DisableApiTermination ```
✅ **GOOD: Termination Protection Enabled** ```yaml EC2Instance: Type: AWS::EC2::Instance Properties: # ... DisableApiTermination: true ```
## 5. Testing Approaches
### 5.1. IaC Linting and Validation
**Integrate IaC linting and validation into your CI/CD pipeline.** Catch policy violations and syntax errors *before* deployment.
❌ **BAD: Deploying without Pre-checks** ```bash aws cloudformation deploy --template-file my-ec2-stack.yaml --stack-name my-app ```
✅ **GOOD: CI/CD with CloudFormation Guard** ```bash # In CI/CD pipeline cfn-guard validate --template my-ec2-stack.yaml --rules policies/ec2-rules.guard aws cloudformation deploy --template-file my-ec2-stack.yaml --stack-name my-app ```
### 5.2. Automated Security Hub Compliance
**Ensure your CI/CD pipeline includes checks against AWS Foundational Security Best Practices (FSBP) via AWS Security Hub.**
❌ **BAD: Manual Security Audits Post-Deployment** ```bash # Hope for the best, check manually later ```
✅ **GOOD: Proactive Security Checks** ```bash # Example rule in CloudFormation Guard to enforce IMDSv2 rule imdsv2_required { EC2_Instance { Properties { MetadataOptions { HttpTokens == "required" HttpPutResponseHopLimit == 1 } } } } ```