Python 3 Boto 3, AWS S3 Get object URL
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
When developers ask for an S3 object URL, they usually mean one of two different things: a normal URL for an object that is already publicly readable, or a pre-signed URL that grants temporary access to a private object. In Boto3, those are different workflows, and choosing the wrong one is the main reason links fail.
Decide Which URL You Actually Need
Before writing code, answer this first:
- Is the object public to anonymous users
- Or is the object private and meant to be shared temporarily
If the bucket is private, a plain HTTPS URL is not enough. S3 will reject the request unless the caller is separately authenticated. In that case, the correct solution is a pre-signed URL.
Build a Plain Public Object URL
For a public object, the URL format is predictable. One detail worth handling is URL-encoding the key correctly:
Encoding matters for spaces and other characters that are valid in an S3 key but not valid as raw URL path characters.
This approach only works if the object is readable through bucket policy, object ACL settings, or another public-access configuration. If the object is private, generating the URL string alone does not grant access.
Generate a Pre-Signed URL for Private Objects
Boto3 provides generate_presigned_url for time-limited access:
This URL is signed with your AWS credentials and remains valid for the expiration window. ExpiresIn=900 means fifteen minutes.
For most production applications, this is the safer default because buckets should usually stay private.
Wrap the Logic in a Helper
Centralizing the behavior makes it easier to test and reuse:
This isolates region selection, error handling, and expiration rules in one place instead of scattering them across controllers or API routes.
Control Download Behavior
You can also pre-sign a URL that tells the browser how to handle the download:
This is useful when you want the browser to download a file instead of opening it inline, or when you want to suggest a clean filename to the client.
Validate Existence When Your API Needs a Clear Error
Pre-signing a URL does not prove the object exists at request time. If your application should fail early, do a metadata check first:
This is helpful in API endpoints where returning a clear “not found” response is better than giving the client a signed link that later fails.
Credentials and Region Still Matter
Boto3 resolves credentials from environment variables, local AWS profiles, or IAM roles. The signing operation uses those credentials, so missing or incorrect credentials cause errors before the URL is generated.
Region configuration matters too. S3 may redirect requests when a bucket lives in a different region than the client expects, and misconfigured signing can lead to confusing failures. For deployed systems, IAM roles are preferred over static keys stored in source code or config files.
Common Pitfalls
- Returning a plain S3 URL for a private object and expecting it to work for anonymous users.
- Forgetting to URL-encode keys when constructing public URLs manually.
- Generating very long-lived pre-signed URLs when short-lived access would be safer.
- Ignoring region mismatches, which can cause redirect or signature problems.
- Logging full pre-signed URLs, which can expose temporary access tokens in shared logs.
Summary
- Decide first whether you need a public object URL or a pre-signed private-access URL.
- For public objects, build the HTTPS URL carefully and encode the key.
- For private objects, use Boto3
generate_presigned_url. - Keep expiration windows short and prefer IAM roles for credentials.
- Optionally check object existence first when your API needs explicit not-found handling.
Related reading
- Python Async Azure Blob Upload
- python aws botocore.response.streamingbody to json
- Query all items by partition key in Dynamo using boto3
- Query condition missed key schema element Validation Error
- Python 3 How to submit an async function to a threadPool?
- Python 3 ImportError No module named 'ConfigParser
- Query DynamoDB with a hash key and a range key with Boto3
- Query DynamoDB with case-insensitive condition

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.