How can I securely download a private S3 asset onto a new EC2 instance with cloudinit?
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
The secure pattern is to let the EC2 instance authenticate to S3 through its IAM instance profile, not through embedded access keys inside cloud-init. Cloud-init then downloads the object using the AWS CLI or SDK over TLS during first boot. If you keep the bucket policy narrow and the instance role minimal, the instance gets only the asset it needs without shipping long-lived secrets in user data.
Use An IAM Role, Not Static Credentials
The first design rule is simple: do not hardcode AWS access keys into cloud-init. User data can leak through logs, snapshots, or operator mistakes. The instance should instead receive temporary credentials from the EC2 metadata service via an attached IAM role.
A minimal policy for one object might look like this:
Attach that policy to an IAM role, and attach the role to the EC2 instance profile.
Download In Cloud-Init
Once the role is attached, cloud-init can fetch the object during boot.
This works because the AWS CLI automatically uses the instance role credentials. There is no need to pass keys manually.
Keep The Bucket Policy Tight
The IAM role controls what the instance can request. The bucket policy can add another layer of restriction, such as allowing only that role.
That is much safer than granting broad bucket read access to every instance in the account.
Handle Encryption Normally
S3 downloads already use HTTPS in transit. For at-rest security, use standard S3 server-side encryption or KMS if required by policy. If the object is protected with KMS, the instance role also needs permission to decrypt with that key.
That often means adding kms:Decrypt for the specific KMS key ARN, not just s3:GetObject.
Think About Boot Timing
Cloud-init runs early, but not every dependency is guaranteed to be available instantly. If the artifact is critical to application startup, make sure the service that depends on it waits until the download is complete.
A common pattern is:
- cloud-init downloads the asset
- cloud-init writes a systemd unit or extracts the application
- the application service starts afterward
That is better than assuming the app can race the download and still succeed.
When A Pre-Signed URL Makes Sense
A pre-signed URL can work, but it is usually not the best default inside your own AWS account because the instance role is cleaner and easier to audit. Pre-signed URLs are more useful when the downloader cannot assume an AWS role directly or when access must be granted temporarily across trust boundaries.
Inside a normal EC2 bootstrap flow, IAM roles are the more maintainable design.
Common Pitfalls
- Embedding static AWS credentials in cloud-init user data.
- Giving the instance broad bucket read access when it only needs one object or prefix.
- Forgetting KMS permissions when the S3 object is encrypted with a customer-managed key.
- Starting the application before the artifact download and extraction have completed.
- Using pre-signed URLs by habit when an instance role would be simpler and safer.
Summary
- Use an EC2 instance role so cloud-init can download from S3 without embedded secrets.
- Keep both the IAM policy and the bucket policy narrowly scoped.
- Use
aws s3 cpor an SDK call from cloud-init to fetch the private asset. - Include KMS permissions if the object is encrypted with a customer-managed key.
- Coordinate boot order so application startup depends on the asset being present first.
Related reading
- How can I see AWS CloudFormation logs in CloudWatch?
- How can I set the AWS API Gateway timeout higher than 30 seconds?
- How can I specify persistent volumes when defining a Kubernetes replication controller in Google Cloud?
- How can I tell how many objects I've stored in an S3 bucket?
- How can I set up a letsencrypt SSL certificate and use it in a Spring Boot application?
- How can I specify my .keystore file with Spring Boot and Tomcat?
- How can I test AWS Lambda functions locally?
- How can I test lambda in local using python?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.