Production-ready AWS architecture guide for securing S3 with CloudFront and EC2 IAM Roles in Laravel applications.
This setup removes AWS access keys completely and uses IAM Role-based authentication for EC2 instances. It includes private S3 bucket configuration, CloudFront restriction, and Laravel Storage integration.
- Private S3 bucket (no public access)
- CloudFront as the only public entry point
- EC2 IAM Role (no static AWS credentials)
- Laravel AWS SDK integration using IAM Role
Go to: S3 → Bucket → Permissions → Block Public Access
Enable ALL:
- ❌ Block all public access = ON
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontOnly",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/YOUR_DISTRIBUTION_ID"
}
}
}
]
}- Public
s3:GetObject - Public
s3:ListBucket - Any IP-based restrictions
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
}
]
}Name:
ec2-your-project-s3-role-policy
- Trusted entity: AWS service
- Use case: EC2
Attach policy:
ec2-your-project-s3-role-policy
Create role:
ec2-your-project-s3-role
EC2 → Instance → Actions → Security → Modify IAM Role Attach:
ec2-your-project-s3-role
aws sts get-caller-identityExpected:
{
"Arn": "arn:aws:sts::ACCOUNT_ID:assumed-role/ec2-your-project-s3-role/i-xxxx"
}aws s3 ls s3://YOUR_BUCKET_NAMEExpected:
- Bucket contents listed
Remove from .env:
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKENKeep only:
AWS_DEFAULT_REGION=us-east-2
AWS_BUCKET=YOUR_BUCKET_NAME
AWS_USE_PATH_STYLE_ENDPOINT=falsesudo -u www-data php artisan config:clear
sudo -u www-data php artisan cache:clear
sudo -u www-data php artisan config:cachesudo systemctl restart apache2
sudo systemctl restart php8.*-fpmuse Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
Route::get('/aws-test', function () {
$result = Storage::disk('s3')->put('test.txt', 'hello from ec2 role');
return [
'uploaded' => $result,
'url' => Storage::disk('s3')->url('test.txt')
];
});curl http://localhost/aws-test{
"uploaded": true,
"url": "https://bucket.s3.region.amazonaws.com/test.txt"
}aws s3 ls s3://YOUR_BUCKET_NAMEExpected:
2026-06-30 09:54:13 19 test.txt