Back to Blog
Python

Python Boto3 S3 Presigned URLs: Generate and Secure

python boto3 s3 presigned urls: Generate secure S3 presigned URLs with Python boto3. Understand expiration limits, upload versus download methods, signature behavior,...

boto3AWS S3presigned URLsAWS SDK for Pythonfile uploadsIAM
A stylized S3 bucket with a signed link and a clock, illustrating temporary scoped access via a presigned URL.

Presigned URLs are the standard way to grant temporary, scoped access to a private S3 object without embedding AWS credentials in a client application. When you work with python boto3 s3 presigned urls, generation is a single client call, but the behavior of the resulting URL depends on the client method you choose, the parameters you sign, and the expiration window you set.

What a Presigned URL Actually Contains

A presigned URL is an S3 object URL with a set of authentication query parameters appended. When boto3 generates one, it signs the request using your AWS credentials and embeds the signature in the URL. The resulting URL works only for the exact HTTP method and parameters that were signed.

The query string typically includes:

  • X-Amz-Algorithm — always AWS4-HMAC-SHA256 for signature version 4
  • X-Amz-Credential — the access key ID and the signing scope
  • X-Amz-Date — the timestamp at which the URL was signed
  • X-Amz-Expires — the lifetime in seconds
  • X-Amz-Signature — the HMAC signature derived from your secret access key

Because the signature is derived from your secret access key, S3 can verify the URL without maintaining any server-side state. That is why expiration is enforced by the signature timestamp rather than by a stored record on the S3 side.

Generating a Presigned URL with boto3

The function you need is generate_presigned_url on a boto3 client. It is not available on the resource interface, so use boto3.client('s3').

import boto3 s3 = boto3.client('s3') url = s3.generate_presigned_url( ClientMethod='get_object', Params={ 'Bucket': 'my-bucket', 'Key': 'reports/2025/q1.pdf', }, ExpiresIn=3600, ) print(url)

ClientMethod must be the name of a method on the S3 client, such as get_object, put_object, or delete_object. Params must contain exactly the arguments that method accepts. ExpiresIn is the lifetime in seconds.

The returned URL is a full HTTPS endpoint. A browser or any HTTP client can fetch it directly, which is the point: the client never needs AWS credentials.

Downloads vs Uploads: Choosing the Client Method

For read access, sign get_object. For write access, sign put_object. The signed HTTP method is part of the signature, so a URL generated for get_object cannot be used to upload.

upload_url = s3.generate_presigned_url( ClientMethod='put_object', Params={ 'Bucket': 'my-bucket', 'Key': f'uploads/{user_id}/avatar.jpg', 'ContentType': 'image/jpeg', }, ExpiresIn=900, )

When you include ContentType in Params, the client that performs the upload must send the same Content-Type header. If the header differs, S3 rejects the request because the signature no longer matches. The same applies to any other parameter you sign, such as ContentLength or Metadata.

For browser-based form uploads, post_object is an alternative that returns form fields rather than a plain URL. It is useful when you want the browser to submit a multipart form directly to S3.

How Expiration and Signature Versioning Work

With signature version 4, the maximum ExpiresIn value is 604800 seconds, or seven days. If you need longer-lived access, you have to re-sign, use a different mechanism such as CloudFront signed URLs, or make the object public.

Expiration is enforced through the X-Amz-Date and X-Amz-Expires values. S3 compares the current time against the signing time plus the expiration window. If the client's clock is skewed, or if the request arrives after the window closes, S3 returns an AccessDenied error indicating that the request has expired.

This also means a presigned URL cannot be revoked early. There is no API call to invalidate one. The only ways to stop a leaked URL from working are to rotate the credentials that signed it or to change the object key so the URL points at nothing.

Security Considerations for Presigned URLs

A presigned URL grants exactly the permissions of the identity that signed it. If the signing IAM user has read access to the bucket, the URL allows reads. If the user has write access, the URL allows writes. The URL does not grant more than the signer's own permissions, and it does not change the bucket policy.

Because the URL contains a valid signature, treat it like a credential:

  • Keep expiration short, especially for uploads, to limit the window in which a leaked URL is usable.
  • Do not log the full URL in application logs or error reports.
  • Generate the URL server-side at request time rather than storing it in a database, unless you have a specific reason to persist it.
  • Use a dedicated IAM role with the narrowest policy that the use case requires.

A common mistake is generating a presigned URL with a long expiration and sending it to a client over an unencrypted channel. The URL itself is HTTPS, but if it is delivered through an insecure mechanism, the signature can be captured before the request reaches S3.

Common Failures and How to Diagnose Them

The most frequent failures with presigned URLs fall into a few categories.

Expired URL. The client waits too long before making the request. The fix is to generate the URL closer to the time of use and choose an appropriate ExpiresIn value.

Region mismatch. The boto3 client must be configured for the same region as the bucket. A URL signed with a client pointing at us-east-1 will not work for a bucket in eu-west-1. The region is part of the signature scope.

Parameter mismatch. If the signed Params include ContentType but the client sends a different header, the signature fails. The same happens if the client modifies the URL query string. Every signed parameter must match exactly.

Clock skew. If the machine generating the URL has a clock that is significantly off, the signature timestamp may be outside the accepted window. This is more common in containerized or virtualized environments where time synchronization is misconfigured.

When diagnosing, start by comparing the signed parameters with the actual request. The error response from S3 includes the expected signature and the one received, which makes it possible to verify whether the request matched what was signed.

Operational Considerations in Production

In a web application, generate presigned URLs at request time inside the request handler. The boto3 client should be created once and reused, since client construction performs credential resolution and HTTP session setup that does not need to repeat on every request.

Keep the signing identity's permissions minimal. If the application only needs to generate download URLs, the IAM role should allow s3:GetObject on the relevant bucket and nothing else. This limits the damage if the role's credentials are compromised.

For uploads, consider validating the object before it reaches S3. A presigned put_object URL does not enforce file size limits or content type checks beyond what you sign. If you sign ContentLength, the client must send exactly that length, which gives you a degree of control, but it also means the client must know the size in advance.

If you need to revoke access quickly, presigned URLs are the wrong tool. CloudFront signed URLs or a proxy layer that validates requests before forwarding them to S3 give you revocation control. Choose presigned URLs when the simplicity of a self-contained URL outweighs the need for revocation.

python boto3 s3 presigned urls: Practical Usage and Code Exa | RYUSLOG DEV