Featured Post

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.

Richard Joseph Porter
15 min read
awsphplaravelcost-optimizationdevopscloud

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:

External Resources:

Richard Joseph Porter - Professional headshot

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.