What is AWS Elastic Beanstalk?
AWS Elastic Beanstalk is a Platform as a Service (PaaS) that abstracts away the complexity of provisioning and managing the underlying AWS infrastructure. Instead of manually configuring EC2 instances, load balancers, auto scaling groups, and deployment pipelines, you simply upload your application code, and Elastic Beanstalk handles the rest. It supports multiple platforms including Java, .NET, Node.js, PHP, Python, Ruby, Go, and Docker containers.
While Elastic Beanstalk dramatically simplifies deployment, the default settings are designed for "it just works" rather than "it works optimally." Without deliberate tuning, you can easily overspend on compute, expose vulnerabilities, or hit performance bottlenecks under load. This tutorial walks through battle-tested best practices across three critical pillars: cost, security, and performance.
Cost Optimization Best Practices
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Elastic Beanstalk environments can silently drain your budget if left on autopilot. Let's examine where costs accumulate and how to control them.
1. Choose the Right Instance Types
The default instance type for new environments is often a general-purpose t2.micro (for testing) or a previous-generation instance. For production workloads, newer generation instances like t3 or t4g (Graviton ARM-based) offer significantly better price-to-performance ratios. Always benchmark your application against the latest instance families.
Example: Creating an environment with a specific instance type via the CLI:
aws elasticbeanstalk create-environment \
--application-name my-app \
--environment-name my-env-prod \
--solution-stack-name "64bit Amazon Linux 2023 v6.1.0 running Node.js 20" \
--option-settings \
"Namespace=aws:autoscaling:launchconfiguration,OptionName=IInstanceType,Value=t3.medium" \
"Namespace=aws:ec2:instances,OptionName=InstanceTypes,Value=t3.medium,t3.large"
2. Leverage Auto Scaling with Tight Thresholds
Elastic Beanstalk's auto scaling group defines minimum and maximum instance counts. The default often sets a minimum of 1 and maximum of 4, but many production environments can safely scale down to 1 or 2 instances during off-peak hours. Use scaling policies based on CPU utilization or request count to add instances only when needed, and set a cooldown period to avoid rapid, costly scale-out events.
Configure scaling thresholds via .ebextensions configuration files:
# .ebextensions/autoscaling.config
option_settings:
aws:autoscaling:asg:
MinSize: '1'
MaxSize: '3'
Cooldown: '300'
aws:autoscaling:trigger:
MeasureName: CPUUtilization
Statistic: Average
Unit: Percent
Period: '5'
LowerThreshold: '20'
UpperThreshold: '70'
LowerBreather: '600'
UpperBreather: '300'
3. Use Spot Instances for Non-Critical Workloads
Spot instances can reduce EC2 costs by up to 90% compared to on-demand pricing. While not ideal for latency-sensitive production endpoints, they shine for worker environments, batch processing, staging, and development environments. Elastic Beanstalk supports mixed instance policies through the launch template configuration.
# .ebextensions/spot-instances.config
option_settings:
aws:ec2:instances:
EnableSpot: 'true'
SpotPercentage: '50'
SpotMaxPrice: '0.05'
OnDemandBaseCapacity: '1'
This configuration maintains at least one on-demand instance for baseline stability while filling the rest of the capacity with spot instances, capped at $0.05/hour bid price.
4. RDS: Decouple and Right-Size
When you create an RDS instance through the Elastic Beanstalk console, the database is tied to the environment lifecycle. If you terminate the environment, the database gets deleted. Always create RDS separately and reference it via environment variables. This decoupling lets you destroy and recreate environments without losing data, and allows independent scaling and snapshot scheduling.
# Set RDS connection details via environment properties
aws elasticbeanstalk update-environment \
--environment-name my-env-prod \
--option-settings \
"Namespace=aws:elasticbeanstalk:application:environment,OptionName=DB_HOST,Value=mydb.xxxx.us-east-1.rds.amazonaws.com" \
"Namespace=aws:elasticbeanstalk:application:environment,OptionName=DB_PORT,Value=5432" \
"Namespace=aws:elasticbeanstalk:application:environment,OptionName=DB_SSL,Value=true"
5. Scheduled Scaling and Environment Hibernation
For development and staging environments that don't need 24/7 availability, implement scheduled scaling policies or simply use the TimeBasedScaling option. You can scale down to zero during nights and weekends (though note: the load balancer still incurs charges unless you also disable it).
# .ebextensions/time-based-scaling.config
option_settings:
aws:autoscaling:scheduledaction:
- ScheduledActionName: 'ScaleDownNightly'
Recurrence: '0 22 * * *' # 10 PM UTC daily
MinSize: '0'
MaxSize: '0'
- ScheduledActionName: 'ScaleUpMorning'
Recurrence: '0 6 * * *' # 6 AM UTC daily
MinSize: '1'
MaxSize: '2'
6. Clean Up Unused Resources
Elastic Beanstalk creates snapshots, S3 buckets for application versions, and old AMI versions over time. Regularly prune application versions and clean up orphaned resources:
# List and delete old application versions
aws elasticbeanstalk describe-application-versions \
--application-name my-app \
--query "ApplicationVersions[?Status=='UNPUBLISHED'].VersionLabel" \
--output text
# Delete versions older than 30 days (script approach)
aws elasticbeanstalk delete-application-version \
--application-name my-app \
--version-label app-v123-old \
--delete-source-bundle
Security Best Practices
A default Elastic Beanstalk environment exposes an HTTP endpoint and uses broad IAM roles. Locking these down is essential for production.
1. Enforce HTTPS and TLS Termination
By default, Elastic Beanstalk environments serve traffic over HTTP. You must configure HTTPS termination on the Application Load Balancer and redirect HTTP to HTTPS. Use the aws:elbv2:listener namespace to set up secure listeners.
# .ebextensions/https-redirect.config
option_settings:
aws:elbv2:listener:443:
Protocol: HTTPS
SSLCertificateArns: arn:aws:acm:us-east-1:123456789012:certificate/uuid-here
DefaultProcess: default
ListenerEnabled: 'true'
aws:elbv2:listener:80:
Protocol: HTTP
DefaultProcess: redirect
ListenerEnabled: 'true'
Rules: |
default:
Priority: default
Actions:
RedirectConfig:
Protocol: HTTPS
Port: '443'
Host: '#{host}'
Path: '/#{path}'
Query: '#{query}'
StatusCode: HTTP_301
For ACM certificate validation, ensure your domain's DNS points to the Elastic Beanstalk environment or use a CNAME record for the environment URL.
2. Tighten Security Group Rules
The default security group allows inbound traffic on port 80 from anywhere. For production, restrict the load balancer security group to your expected IP ranges or CloudFront prefix lists, and ensure the instance security group only accepts traffic from the load balancer security group—never directly from the internet.
# .ebextensions/security-groups.config
option_settings:
aws:ec2:vpc:
ELBScheme: public
aws:ec2:instances:
SecurityGroups: sg-instance-group-id
aws:elbv2:loadbalancer:
SecurityGroups: sg-lb-group-id
In the AWS console or via CloudFormation, configure the instance security group to allow inbound traffic on the application port only from the load balancer security group's ID as the source.
3. Minimize IAM Role Permissions
Elastic Beanstalk creates an instance profile role (aws-elasticbeanstalk-ec2-role) that grants permissions to the EC2 instances. The default managed policy AWSElasticBeanstalkWebTier is broad. Create a custom, least-privilege policy that only grants what your application actually needs—for example, read access to a specific S3 bucket, PutObject to a logs bucket, or access to a single Secrets Manager secret.
# Custom instance role policy (least privilege example)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-app-bucket/*"
},
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-app-secret-*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:log-group:/aws/elasticbeanstalk/*"
}
]
}
4. Store Secrets Securely
Never hardcode API keys, database passwords, or tokens in your application code or .ebextensions files. Use AWS Secrets Manager or Systems Manager Parameter Store (SecureString) and inject them as environment variables at deployment time.
# .ebextensions/secrets.config — pulls secrets at deploy time
option_settings:
aws:elasticbeanstalk:application:environment:
APP_SECRET_KEY: '{{resolve:secretsmanager:/myapp/secret-key:SecretString}}'
DATABASE_PASSWORD: '{{resolve:secretsmanager:/myapp/db-password:SecretString}}'
For even stronger isolation, fetch secrets at runtime within your application code using the AWS SDK, rather than resolving them during environment creation, which stores them in the environment configuration (visible to anyone with describe-environment permissions).
5. Enable Managed Platform Updates and Patch Scheduling
Elastic Beanstalk managed platform updates automatically apply minor version patches and security fixes to your underlying AMI and platform components. Configure a maintenance window that aligns with your low-traffic periods.
# .ebextensions/managed-updates.config
option_settings:
aws:elasticbeanstalk:managedactions:
ManagedActionsEnabled: 'true'
PreferredStartTime: 'SUN:03:00'
aws:elasticbeanstalk:managedactions:platform:
UpdateLevel: minor
InstanceRefreshEnabled: 'true'
6. Protect Environment Variables and Configuration
Environment variables in Elastic Beanstalk are stored in plaintext within the environment configuration and are visible in the console and via DescribeEnvironment API calls. Restrict IAM permissions on these APIs to only authorized roles. Additionally, enable S3 server-side encryption on your application version bucket and block public access.
7. Enable GuardDuty and VPC Flow Logs
While not Elastic Beanstalk-specific, enabling GuardDuty on your account and VPC Flow Logs on the VPC hosting your Elastic Beanstalk environment provides essential threat detection and network visibility. These services alert you to suspicious traffic patterns, port scanning, or anomalous API calls originating from compromised instances.
Performance Best Practices
A performant Elastic Beanstalk environment requires careful tuning of the load balancer, instance configuration, and deployment strategy.
1. Optimize Load Balancer Configuration
Elastic Beanstalk defaults to an Application Load Balancer with a 60-second idle timeout. For WebSocket connections or long-polling APIs, increase this timeout. Also configure health checks to accurately reflect application readiness rather than simply checking port connectivity.
# .ebextensions/loadbalancer-performance.config
option_settings:
aws:elbv2:loadbalancer:
IdleTimeout: '300'
aws:elasticbeanstalk:environment:process:default:
HealthCheckInterval: '15'
HealthCheckTimeout: '10'
HealthyThresholdCount: '3'
UnhealthyThresholdCount: '5'
HealthCheckPath: '/health'
Port: '8080'
Protocol: HTTP
Set the health check path to an actual endpoint that validates database connectivity and downstream service health—not just a static 200 response. A shallow health check can route traffic to unhealthy instances.
2. Enable Enhanced Health Monitoring
Elastic Beanstalk offers basic and enhanced health monitoring. Enhanced health provides detailed metrics about instance CPU, memory, and application responsiveness, surfaced in the console and CloudWatch. Enable it for all production environments.
# .ebextensions/enhanced-health.config
option_settings:
aws:elasticbeanstalk:healthreporting:system:
SystemType: enhanced
ConfigDocument: |
{
"CloudWatchMetrics": {
"Interval": 60,
"Environment": {
"CPU": true,
"Memory": true,
"ApplicationRequests": true,
"ApplicationLatency": true
}
}
}
3. Use the Right Instance Family for Your Workload
Compute-optimized instances (c6i, c7i) suit CPU-bound APIs and real-time processing. Memory-optimized instances (r6i, r7i) are ideal for in-memory caches and large data transformations. The Graviton ARM-based instances (t4g, c7g) offer up to 40% better price-performance than comparable x86 instances and work seamlessly with Elastic Beanstalk when your application is compiled for ARM or runs on interpreted languages like Python, Node.js, or Ruby.
# Specify Graviton ARM instance type
option_settings:
aws:ec2:instances:
InstanceTypes: t4g.medium,t4g.large
ImageId: ami-0abcdef1234567890 # ARM-compatible AMI
4. Enable CloudFront CDN for Static Assets
Serving static assets (images, CSS, JavaScript bundles) directly from your Elastic Beanstalk instances wastes compute cycles. Offload these to an S3 bucket fronted by CloudFront, or configure CloudFront with the Elastic Beanstalk environment as a custom origin with caching behaviors for static paths.
# CloudFront distribution configuration (separate CloudFormation stack)
Resources:
StaticAssetsDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName: !Ref AssetBucketRegionalDomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${OAI}"
DefaultCacheBehavior:
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
DefaultTTL: 86400
MaxTTL: 31536000
Compress: true
AllowedMethods: [GET, HEAD]
5. Tune JVM, Container, or Runtime Settings
Elastic Beanstalk passes platform-specific settings through environment properties. For Java applications, tune the garbage collector and heap size. For Node.js, adjust the max old space size. For Python with Gunicorn, configure worker processes based on instance vCPU count.
# .ebextensions/java-performance.config
option_settings:
aws:elasticbeanstalk:application:environment:
JAVA_OPTS: '-Xms512m -Xmx2048m -XX:+UseG1GC -XX:MaxGCPauseMillis=200'
GC_LOG_OPTIONS: '-Xlog:gc*:file=/var/log/app/gc.log::filecount=5,filesize=10M'
# .ebextensions/nodejs-performance.config
option_settings:
aws:elasticbeanstalk:application:environment:
NODE_OPTIONS: '--max-old-space-size=2048'
6. Implement Connection Pooling and Keep-Alive
Applications that open a new database connection per request will bottleneck quickly. Use connection pooling libraries (pg-pool for Node.js, HikariCP for Java, SQLAlchemy pool for Python) configured to match your RDS instance's max connections. Also enable HTTP keep-alive between the load balancer and your instances to reduce the overhead of repeated TLS handshakes.
# Example: Python application with proper connection pooling
import os
from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool
engine = create_engine(
os.environ['DATABASE_URL'],
poolclass=QueuePool,
pool_size=10,
max_overflow=5,
pool_recycle=3600,
pool_pre_ping=True,
connect_args={
'keepalives': 1,
'keepalives_idle': 30,
'keepalives_interval': 10,
'keepalives_count': 5
}
)
7. Use Immutable Deployments for Zero-Downtime Updates
Elastic Beanstalk offers multiple deployment strategies. AllAtOnce and Rolling deployments modify instances in-place, which can lead to brief periods of mixed-version traffic and potential downtime if the new version fails. Immutable deployments create an entirely new auto scaling group, switch traffic atomically, and then terminate the old group—guaranteeing zero downtime and a clean rollback path.
# .ebextensions/deployment-policy.config
option_settings:
aws:elasticbeanstalk:command:
DeploymentPolicy: Immutable
BatchSize: '100'
aws:autoscaling:updatepolicy:rollingupdate:
RollingUpdateEnabled: 'false'
RollingUpdateType: Immutable
8. Monitor and Set CloudWatch Alarms
Beyond the default CPU alarm, create custom CloudWatch alarms for memory utilization (pushed via the CloudWatch agent), application latency (from the load balancer metrics), and error rates (5xx count). Proactive alerting prevents performance degradation from spiraling into outages.
# .ebextensions/cloudwatch-alarms.config
Resources:
HighLatencyAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${AWS::StackName}-HighLatency"
AlarmDescription: "Alert when p95 latency exceeds 2 seconds"
Namespace: AWS/ElasticLoadBalancingV2
MetricName: TargetResponseTime
Statistic: p95
Period: 300
EvaluationPeriods: 3
Threshold: 2
ComparisonOperator: GreaterThanThreshold
Dimensions:
- Name: LoadBalancer
Value: !Ref AWSEBV2LoadBalancerId
AlarmActions:
- !Ref AlertSnsTopicArn
Conclusion
Elastic Beanstalk abstracts infrastructure management, but abstraction does not absolve responsibility. Cost control demands deliberate instance selection, spot instance adoption for non-critical workloads, and vigilant cleanup of orphaned resources. Security requires explicit HTTPS enforcement, least-privilege IAM policies, secure secret handling, and locked-down security groups. Performance hinges on proper load balancer tuning, enhanced health monitoring, platform-appropriate instance families, CDN offloading, and immutable deployment strategies. By layering these practices into your .ebextensions configuration files, CLI commands, and operational workflows, you transform a naive Elastic Beanstalk environment into a cost-efficient, hardened, and high-performing application platform that scales gracefully with your user base.