AWS Cost Optimization for PHP Apps: A Complete Guide
Reduce your AWS bill by 40-70% with proven cost optimization strategies for PHP and Laravel applications. Covers EC2, RDS, Lambda, S3, and more.
After 14 years of building and deploying PHP applications on AWS, I have learned that cloud costs can spiral out of control faster than your Laravel queue can process jobs. The good news? With the right strategies, you can reduce your AWS bill by 40-70% without sacrificing performance or reliability.
This guide distills practical cost optimization strategies I have implemented across dozens of production PHP and Laravel applications, from startup MVPs to enterprise platforms processing millions of requests daily. Whether you are running a simple LAMP stack or a complex microservices architecture, these techniques will help you spend less while getting more from AWS.
Why AWS Cost Optimization Matters for PHP Applications
PHP applications have unique characteristics that directly impact AWS costs. Understanding these patterns is the first step toward optimization.
Long-Running Processes: PHP-FPM workers, queue workers, and scheduled tasks often run continuously, making EC2 instance selection critical. An oversized instance wastes money every second it runs.
Database-Heavy Workloads: Laravel's Eloquent ORM makes database queries easy to write but also easy to over-execute. Inefficient queries translate directly to higher RDS costs and slower response times.
Session and Cache Storage: PHP applications traditionally rely on Redis or Memcached for sessions and caching. Misconfigured ElastiCache clusters can cost hundreds of dollars monthly for workloads that could run on much smaller instances.
File Storage: User uploads, logs, and temporary files accumulate quickly. Without proper S3 lifecycle policies, you pay to store data you will never access again.
The optimization strategies below address each of these areas systematically.
EC2 Optimization: Right-Sizing and Pricing Models
EC2 instances typically represent 40-60% of total AWS spend for PHP applications. Start your cost optimization here for maximum impact.
Right-Sizing Your Instances
The most common mistake I see is running on oversized instances. A Laravel application that processes 1,000 requests per minute rarely needs a c5.xlarge instance, yet I frequently encounter this configuration in production.
Use AWS Compute Optimizer to analyze your actual utilization patterns:
# Enable Compute Optimizer recommendations
aws compute-optimizer get-ec2-instance-recommendations \
--instance-arns arn:aws:ec2:us-east-1:123456789:instance/i-0abc123def
# Check current CPU utilization
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-0abc123def \
--start-time 2026-01-02T00:00:00Z \
--end-time 2026-01-09T00:00:00Z \
--period 3600 \
--statistics Average
If your average CPU utilization is below 40%, you are likely over-provisioned. Consider these optimizations:
Switch to Graviton Instances: AWS Graviton processors deliver up to 40% better price-performance for PHP workloads compared to x86 instances. Laravel and most PHP packages run flawlessly on ARM architecture. A t4g.medium instance costs approximately 20% less than an equivalent t3.medium while delivering comparable performance.
<?php
// In your deployment configuration or terraform
// Switch from x86 to Graviton
// Before: x86 instance
$instanceType = 't3.medium'; // $0.0416/hour
// After: Graviton instance
$instanceType = 't4g.medium'; // $0.0336/hour (20% savings)
Use Auto Scaling for Variable Traffic: Laravel applications with variable traffic patterns benefit significantly from Auto Scaling. Configure scaling policies based on CPU utilization or request count:
# Auto Scaling configuration example
AutoScalingGroup:
MinSize: 2
MaxSize: 10
DesiredCapacity: 2
ScalingPolicy:
- Type: TargetTrackingScaling
TargetValue: 70 # Target 70% CPU utilization
MetricType: ASGAverageCPUUtilization
# Scale down aggressively during off-peak hours
ScheduledAction:
- Schedule: "0 22 * * *" # 10 PM
MinSize: 1
DesiredCapacity: 1
- Schedule: "0 6 * * 1-5" # 6 AM weekdays
MinSize: 2
DesiredCapacity: 2
Savings Plans and Reserved Instances
For stable production workloads, Savings Plans offer up to 72% savings compared to On-Demand pricing. AWS recommends Savings Plans over Reserved Instances for most use cases due to their flexibility across instance types and regions.
Compute Savings Plans apply to EC2, Lambda, and Fargate, offering maximum flexibility. They provide discounts up to 66% and automatically apply to any instance family, size, OS, or region.
EC2 Instance Savings Plans offer higher discounts (up to 72%) but are limited to a specific instance family in a specific region. Choose these when you have predictable, stable workloads.
Payment Strategy: Start with a 1-year No Upfront commitment for new applications. This minimizes risk while capturing significant savings. Once you have established usage patterns, consider 3-year All Upfront commitments for maximum discounts.
# Analyze your savings plan recommendations
aws cost-explorer get-savings-plans-purchase-recommendation \
--savings-plans-type COMPUTE_SP \
--term-in-years ONE_YEAR \
--payment-option NO_UPFRONT \
--lookback-period-in-days SIXTY_DAYS
Spot Instances for Non-Critical Workloads
Spot Instances offer up to 90% savings for interruptible workloads. PHP applications can leverage Spot for:
- Queue workers processing background jobs
- Development and staging environments
- Batch processing and data imports
- Automated testing pipelines
<?php
// Configure Laravel queue workers for Spot Instance interruption handling
// In your queue worker supervisor configuration
// Implement graceful shutdown handling
public function handle(): void
{
// Check for Spot Instance termination notice
$terminationTime = $this->checkSpotTermination();
if ($terminationTime) {
// Stop accepting new jobs 2 minutes before termination
$this->info('Spot termination detected, stopping gracefully...');
$this->call('queue:restart');
}
}
private function checkSpotTermination(): ?string
{
try {
$response = file_get_contents(
'http://169.254.169.254/latest/meta-data/spot/termination-time',
false,
stream_context_create(['http' => ['timeout' => 2]])
);
return $response ?: null;
} catch (\Exception $e) {
return null;
}
}
RDS Cost Optimization: Database Efficiency
Database costs often surprise PHP developers. RDS can easily exceed EC2 costs, especially with Multi-AZ deployments and large storage volumes.
Right-Size Your Database Instances
Most Laravel applications are over-provisioned on the database tier. A typical CRUD application serving 500 concurrent users rarely needs more than a db.t4g.medium instance.
Use Performance Insights to identify actual database utilization:
-- Find expensive queries consuming database resources
-- Run in your MySQL/PostgreSQL client
-- MySQL: Identify slow queries
SELECT
query,
exec_count,
total_latency,
avg_latency,
rows_examined_avg
FROM sys.x$statement_analysis
ORDER BY total_latency DESC
LIMIT 20;
-- PostgreSQL: Find slow queries
SELECT
query,
calls,
total_time / 1000 as total_seconds,
mean_time / 1000 as avg_seconds,
rows
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 20;
Consider Aurora Serverless v2 for Variable Workloads
Aurora Serverless v2 automatically scales from 0.5 to 128 ACUs (Aurora Capacity Units), charging only for capacity used. This is ideal for:
- Development and staging environments with sporadic usage
- Applications with unpredictable traffic patterns
- Multi-tenant SaaS platforms with varying customer activity
At $0.12 per ACU-hour in us-east-1, the minimum cost is approximately $43/month (0.5 ACUs running continuously). Compare this to a db.t4g.medium at approximately $47/month---Aurora Serverless v2 becomes cost-effective when utilization varies significantly.
Important Caveat: Avoid RDS Proxy with Aurora Serverless v2 unless absolutely necessary. RDS Proxy incurs a minimum charge for 8 ACUs, which can add nearly $300/month to your costs. Laravel's connection pooling or a lightweight proxy like PgBouncer often suffice.
<?php
// config/database.php optimized for Aurora Serverless v2
return [
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE'),
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
// Optimize connection handling for serverless
'options' => [
PDO::ATTR_PERSISTENT => false, // Don't persist connections
PDO::ATTR_TIMEOUT => 5, // Fast connection timeout
],
// Enable read replicas for read-heavy workloads
'read' => [
'host' => [
env('DB_READ_HOST_1'),
env('DB_READ_HOST_2'),
],
],
'write' => [
'host' => [env('DB_HOST')],
],
'sticky' => true,
],
],
];
Reserved Instance Strategy for RDS
RDS Reserved Instances offer up to 66% savings with 3-year All Upfront commitments. For production databases with predictable usage, this is often the single largest cost reduction available.
Calculate your potential savings:
On-Demand db.r6g.large (MySQL): $0.26/hour = $189/month
1-Year Partial Upfront RI: ~$130/month (31% savings)
3-Year All Upfront RI: ~$77/month (59% savings)
Annual savings with 3-year RI: $1,344/year per instance
ElastiCache Optimization: Session and Cache Efficiency
Laravel applications commonly use Redis for sessions, caching, and queues. ElastiCache costs can be optimized significantly with proper configuration.
Use Graviton-Based Cache Nodes
ElastiCache Graviton nodes (cache.r6g, cache.m6g, cache.t4g) offer 5% lower pricing than equivalent x86 nodes while delivering better performance. For PHP applications using phpredis or predis, the switch is transparent.
Consider ElastiCache Serverless
For applications with variable traffic, ElastiCache Serverless eliminates capacity planning and scales automatically. You pay only for data stored and operations executed.
Key pricing considerations:
- Minimum billing: 1 GB of data stored
- Data storage: $0.125 per GB-hour
- ElastiCache Processing Units (ECPUs): $0.0034 per 1 million ECPUs
For applications with consistent traffic, provisioned nodes remain more cost-effective. Use Serverless for development environments or applications with highly variable traffic patterns.
Optimize Redis Usage in Laravel
Inefficient Redis usage often causes oversized cluster requirements:
<?php
// config/cache.php - Optimize cache configuration
return [
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
],
],
// Set appropriate TTLs to prevent memory bloat
'ttl' => 3600, // 1 hour default, adjust based on data volatility
];
// Efficient caching patterns in your application
// BAD: Caching large collections unnecessarily
$users = Cache::remember('all-users', 86400, function () {
return User::all(); // Could be thousands of records
});
// GOOD: Cache paginated or filtered results
$users = Cache::remember("users:page:{$page}", 3600, function () use ($page) {
return User::paginate(50, ['*'], 'page', $page);
});
// GOOD: Use cache tags for granular invalidation
$user = Cache::tags(['users', "user:{$id}"])->remember(
"user:{$id}:profile",
3600,
fn () => User::with('profile')->find($id)
);
// Clear specific user cache without affecting others
Cache::tags(["user:{$id}"])->flush();
Session Storage Optimization
Configure appropriate session lifetimes to reduce memory usage:
<?php
// config/session.php
return [
'driver' => env('SESSION_DRIVER', 'redis'),
// Reduce session lifetime for most applications
// 2 hours is sufficient for many use cases
'lifetime' => 120,
// Expire on browser close for sensitive applications
'expire_on_close' => false,
'connection' => 'session', // Use dedicated connection
];
S3 and Storage Cost Optimization
S3 storage costs accumulate silently. Without lifecycle policies, you pay indefinitely for data that may never be accessed again.
Implement Lifecycle Policies
Configure S3 lifecycle rules to automatically transition or delete objects:
{
"Rules": [
{
"ID": "TransitionToIA",
"Status": "Enabled",
"Filter": {
"Prefix": "uploads/"
},
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER_IR"
}
]
},
{
"ID": "DeleteOldLogs",
"Status": "Enabled",
"Filter": {
"Prefix": "logs/"
},
"Expiration": {
"Days": 90
}
},
{
"ID": "CleanupMultipartUploads",
"Status": "Enabled",
"Filter": {
"Prefix": ""
},
"AbortIncompleteMultipartUpload": {
"DaysAfterInitiation": 7
}
}
]
}
Use Intelligent-Tiering for Unpredictable Access Patterns
S3 Intelligent-Tiering automatically moves objects between access tiers based on usage patterns. There is no retrieval fee, making it ideal for user-uploaded content where access patterns are unpredictable.
<?php
// Configure Laravel filesystem for Intelligent-Tiering
// config/filesystems.php
return [
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'options' => [
// Default to Intelligent-Tiering for uploads
'StorageClass' => 'INTELLIGENT_TIERING',
],
],
],
];
// Or specify per-upload
Storage::disk('s3')->put('user-uploads/file.pdf', $contents, [
'StorageClass' => 'INTELLIGENT_TIERING',
]);
CloudFront for Static Asset Delivery
Serving static assets through CloudFront reduces S3 request costs and improves performance. For high-traffic PHP applications, CloudFront data transfer can be 40-50% cheaper than direct S3 access.
<?php
// Configure Laravel Mix to use CloudFront URLs in production
// webpack.mix.js
if (mix.inProduction()) {
mix.version()
.options({
cdnUrl: process.env.CDN_URL // Your CloudFront distribution URL
});
}
// Or configure in Laravel directly
// config/app.php
'asset_url' => env('ASSET_URL', env('CDN_URL')),
Lambda for Serverless PHP: When It Makes Sense
AWS Lambda does not natively support PHP, but frameworks like Bref make serverless PHP viable. For specific use cases, Lambda can dramatically reduce costs.
Cost-Effective Use Cases for PHP Lambda
Background Job Processing: Process queue jobs without maintaining always-on workers.
Scheduled Tasks: Replace cron workers with EventBridge-triggered Lambda functions.
API Endpoints with Sporadic Traffic: Pay only when requests arrive, eliminating idle server costs.
Image Processing: Process uploaded images on-demand rather than maintaining dedicated workers.
<?php
// serverless.yml for Bref PHP Lambda
service: laravel-queue-processor
provider:
name: aws
region: us-east-1
runtime: provided.al2
# Use ARM for 20% cost savings
architecture: arm64
functions:
queue:
handler: Bref\LaravelBridge\Queue\QueueHandler
timeout: 120
# Keep memory low for queue processing
memorySize: 512
layers:
- ${bref:layer.php-82}
events:
- sqs:
arn: !GetAtt Queue.Arn
batchSize: 10
scheduler:
handler: artisan
timeout: 120
memorySize: 256
layers:
- ${bref:layer.php-82}
- ${bref:layer.console}
events:
- schedule:
rate: rate(1 minute)
input: '"schedule:run"'
Lambda vs EC2 Cost Comparison
Calculate break-even points for your workloads:
Lambda Cost Calculation:
- 1 million requests/month
- 500ms average duration
- 512MB memory
- Cost: ~$4.20/month
EC2 t4g.small (24/7):
- $0.0168/hour = $12.26/month
- Can handle millions of requests
Break-even: Lambda is cheaper for sporadic workloads
EC2 wins for consistent traffic > 3-4 million requests/month
Use the Bref cost calculator (cost-calculator.bref.sh) to estimate serverless costs for your specific PHP workloads.
Monitoring and Continuous Optimization
Cost optimization is not a one-time effort. Implement continuous monitoring to catch cost anomalies early.
AWS Cost Management Tools
Cost Explorer: Analyze spending patterns and identify optimization opportunities. Enable hourly granularity for detailed analysis.
AWS Budgets: Set monthly spending alerts. Configure action-based alerts to automatically stop non-production resources when budgets are exceeded.
Cost Anomaly Detection: Machine learning-based alerts for unexpected spending patterns.
# Create a budget with alert thresholds
aws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "PHP-App-Monthly",
"BudgetLimit": {
"Amount": "500",
"Unit": "USD"
},
"TimeUnit": "MONTHLY",
"BudgetType": "COST"
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{
"SubscriptionType": "EMAIL",
"Address": "[email protected]"
}
]
}
]'
Implement Tagging Strategy
Consistent resource tagging enables cost allocation and accountability:
<?php
// Tag all AWS resources consistently
$tags = [
'Environment' => 'production',
'Application' => 'laravel-app',
'Team' => 'backend',
'CostCenter' => 'engineering',
];
// Apply tags when creating resources
$result = $ec2Client->runInstances([
'ImageId' => $amiId,
'InstanceType' => 't4g.medium',
'TagSpecifications' => [
[
'ResourceType' => 'instance',
'Tags' => array_map(
fn($key, $value) => ['Key' => $key, 'Value' => $value],
array_keys($tags),
array_values($tags)
),
],
],
]);
Schedule Non-Production Resources
Development and staging environments should not run 24/7:
#!/bin/bash
# scripts/stop-dev-resources.sh
# Run via cron at end of business day
# Stop development EC2 instances
aws ec2 stop-instances --instance-ids i-dev123 i-staging456
# Scale down RDS to minimum (Aurora Serverless)
aws rds modify-db-cluster \
--db-cluster-identifier dev-cluster \
--serverless-v2-scaling-configuration MinCapacity=0.5,MaxCapacity=1
# Scale down ElastiCache
aws elasticache modify-replication-group \
--replication-group-id dev-redis \
--node-group-count 1 \
--apply-immediately
Common Cost Pitfalls and How to Avoid Them
After optimizing dozens of PHP applications on AWS, these are the most expensive mistakes I encounter:
1. Idle Resources
Problem: Developers spin up resources for testing and forget to terminate them.
Solution: Implement automated cleanup with AWS Config rules and Lambda functions that terminate untagged or idle resources after a defined period.
2. Unoptimized Database Queries
Problem: N+1 queries and missing indexes cause database over-provisioning.
Solution: Enable Laravel Telescope or query logging in production (briefly) to identify expensive queries. Implement eager loading and proper indexing.
<?php
// Enable query logging in development
if (app()->environment('local')) {
DB::listen(function ($query) {
if ($query->time > 100) { // Log queries over 100ms
Log::warning('Slow query detected', [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time' => $query->time,
]);
}
});
}
3. Data Transfer Costs
Problem: Cross-region or cross-AZ data transfer adds up quickly.
Solution: Keep resources in the same region and AZ when possible. Use VPC endpoints for S3 and other AWS services to avoid NAT Gateway charges.
4. Snapshots and Backups Accumulation
Problem: Automated snapshots accumulate indefinitely.
Solution: Configure retention policies for RDS snapshots, EBS snapshots, and AMIs.
5. Oversized Logs
Problem: CloudWatch Logs ingestion and storage costs grow unbounded.
Solution: Implement log retention policies and filter logs before sending to CloudWatch.
<?php
// Log only what you need in production
// config/logging.php
return [
'channels' => [
'cloudwatch' => [
'driver' => 'custom',
'via' => App\Logging\CloudWatchLoggerFactory::class,
'level' => env('LOG_LEVEL', 'warning'), // Not 'debug' in production
'retention' => 14, // Days
],
],
];
Key Takeaways
AWS cost optimization for PHP applications requires a systematic approach across compute, database, caching, and storage layers. The strategies that deliver the highest ROI:
- Right-size EC2 instances using Compute Optimizer recommendations and switch to Graviton processors for 20-40% better price-performance
- Commit to Savings Plans for stable workloads, capturing 50-72% savings compared to On-Demand
- Optimize database costs with Aurora Serverless v2 for variable workloads or Reserved Instances for predictable usage
- Implement S3 lifecycle policies to automatically transition or delete unused data
- Use CloudFront for static assets to reduce both costs and latency
- Monitor continuously with Cost Explorer, Budgets, and Anomaly Detection
Start with the highest-impact areas first---typically EC2 right-sizing and Savings Plans---then progressively optimize database, caching, and storage costs. A methodical approach can reduce your AWS bill by 40-70% while maintaining or improving application performance.
Need help optimizing your PHP application's AWS costs? I specialize in AWS architecture optimization for PHP and Laravel applications, with over 14 years of experience building cost-efficient, scalable systems. My AWS Optimization service includes comprehensive infrastructure audits, right-sizing recommendations, Savings Plans analysis, and implementation of the strategies outlined in this guide. Schedule a free consultation to discuss your AWS cost challenges.
Related Reading:
- Migrating Legacy Laravel Apps: Lessons from 14 Years of PHP Development
- React Server Components and Serverless: Complete 2025 Guide
- Contact Form Security Best Practices
External Resources:

Richard Joseph Porter
Full-stack developer with expertise in modern web technologies. Passionate about building scalable applications and sharing knowledge through technical writing.
Need Help Upgrading Your Laravel App?
I specialize in modernizing legacy Laravel applications with zero downtime. Get a free codebase audit and upgrade roadmap.
Related Articles
Laravel API Development Best Practices: A Complete Guide
Master REST API development with Laravel. Learn authentication, versioning, error handling, rate limiting, and performance optimization from 14 years of PHP experience.
Migrating Legacy Laravel Apps: Lessons from 14 Years of PHP Development
A practical guide to upgrading Laravel 4.x-8.x applications to modern versions, with real-world strategies from enterprise migration projects.
React Server Components & Serverless: Complete 2025 Guide
Master React Server Components with serverless architecture. Achieve 60% faster cold starts, 40% cost savings, and improved Core Web Vitals.