S3 — DVA-C02 Mastery Guide
- Sources: course chapter 06 (S3). The slides serve as the syllabus only; all wording here is original.
- Written 2026-10-12; values checked against AWS docs on 2026-10-12. The marker
⚠️ Changed: <current>. Older material may say <old>.flags a value or behaviour that differs from the course or from older practice tests: learn the current value.⚠️ unverifiedmarks anything that could not be confirmed in AWS documentation. Every change is listed in Changed vs older material; rows enternotes/current-facts.mdonce you confirm them.- Tier 3 (supporting), bank target 30 questions. Domains: Development with AWS Services (primary), Security.
- Scope: buckets, objects, metadata and tags; IAM vs bucket policies, ACLs and Block Public Access; encryption (SSE-S3, SSE-KMS, DSSE-KMS, SSE-C, client-side, default encryption, TLS); versioning, MFA Delete and Object Lock; replication (CRR/SRR); storage classes and lifecycle rules; event notifications; performance (prefixes, multipart, byte ranges, Transfer Acceleration); presigned URLs, CORS and static websites; Access Points and Object Lambda. Covered elsewhere (one-line cross-references only): KMS key policies, request quotas and S3 Bucket Keys →
notes/security-encryption-guide.md§2 and §5; policy evaluation logic, STS and cross-account roles →notes/advanced-identity-guide.md§9 and §11; IAM policy grammar and condition operators →notes/iam-guide.md§4–§5; SigV4 signing and credential lifetimes →notes/cli-sdk-policies-guide.md§10; Lambda resource-based policies, async retries and recursive-loop detection →notes/lambda-guide.md§2, §4, §12; SQS/SNS access policies and fan-out →notes/sqs-sns-kinesis-guide.md§6 and §8; EventBridge rules and CloudTrail data events →notes/monitoring-guide.md§6 and §10; CloudFront in front of S3 (OAC, signed URLs) →notes/cloudfront-guide.md§3 and §4; VPC gateway endpoints →notes/vpc-guide.md§3; DynamoDB as an S3 metadata index →notes/dynamodb-guide.md§14.- How to use it:
/explain <topic>teaches from these sections (concept → exam facts → developer angle → traps → 3 check questions);/quiz s3asks bank questions whosesource_notelinks point at the## N.headings below;/drill s3fires short recall prompts fromnotes/s3-cheat-sheet.md. Suggested loop: read a section, answer its practice questions before opening the answers,/explainwhat is still unclear, then/quiz s3and/drill s3.
How S3 appears on DVA-C02
S3 is the default answer whenever an application needs durable object storage, so it shows up inside Lambda, API Gateway, CloudFront, DynamoDB and CI/CD scenarios as well as in its own questions. The exam treats it from the developer's chair:
- Writing and reading data correctly: keys and prefixes, metadata vs tags, single PUT vs multipart, byte-range GETs, conditional writes, strong consistency, presigned URLs for direct browser and mobile transfers.
- Securing it: identity policies vs bucket policies, ACLs being disabled, Block Public Access, enforcing TLS and encryption with policy conditions, choosing SSE-S3, SSE-KMS, DSSE-KMS, SSE-C or client-side encryption, Object Lock and MFA Delete.
- Reacting to it: event notifications to Lambda, SQS, SNS or EventBridge, and the permissions each destination needs.
- Running it cheaply and fast: storage classes, lifecycle rules, replication, prefix-based request scaling, Transfer Acceleration.
- Troubleshooting:
403 AccessDeniedvs404 NoSuchKey,503 Slow Down, KMSThrottlingException, CORS preflight failures, replication or notifications that silently do nothing.
Typical question shapes: a scenario with one deciding constraint ("LEAST operational overhead", "MOST secure", "MOST cost-effective", "fewest code changes"); an error code or symptom to explain; "Which TWO…" with five options; the exact header, condition key, API action or CLI flag that does the job.
Domain map: Development with AWS Services → §1, §5, §6, §7, §8, §9 · Security → §2, §3, §4, §9 (presigned URLs), §10. Troubleshooting framing appears throughout (§1 errors, §3 KMS throttling, §7 missing events, §8 503).
Out of scope here: network design (VPC endpoints appear only as the path a request takes), S3 on Outposts, S3 Tables and S3 Vectors, storage-administration tools beyond what a developer configures (Storage Lens dashboards, Inventory details).
| # | Topic | Why it is on the exam |
|---|---|---|
| 1 | Buckets objects keys and metadata | Naming and size limits, immutable metadata vs mutable tags, consistency and conditional writes, 403 vs 404 |
| 2 | Access control Block Public Access and access logs | Who may call what: IAM vs bucket policy, cross-account rules, ACLs off, BPA guardrails, request logs |
| 3 | Encryption at rest and in transit | Picking the encryption option under a key-control constraint; headers, KMS permissions and quotas, forcing TLS |
| 4 | Versioning MFA Delete and Object Lock | Recovering from deletes and overwrites; WORM retention that even administrators can't bypass |
| 5 | Replication CRR and SRR | What replicates, what doesn't, and the role, versioning and KMS settings it needs |
| 6 | Storage classes and lifecycle rules | The cheapest class that still meets the retrieval-time constraint; automating transitions and expirations |
| 7 | Event notifications and EventBridge | Event-driven processing, destination permissions, filters, fan-out and loop avoidance |
| 8 | Performance multipart uploads and Transfer Acceleration | Request-rate scaling per prefix, 503 Slow Down, big and distant uploads, partial reads |
| 9 | Presigned URLs CORS and static websites | Direct browser and mobile access without sharing credentials; preflight fixes; public website hosting |
| 10 | Access Points and Object Lambda | Per-application endpoints and policies for shared buckets; transforming data on read (and its availability change) |
1. Buckets objects keys and metadata
Concept
- A bucket is created in one Region and holds an unlimited number of objects with no size cap. Its name sits in a namespace shared by every AWS account in the partition, so the name, not the account, identifies it; name and Region can never change and ownership can't be transferred.
- An object is identified by bucket + key (+ version ID when versioning is on). There are no real directories: the key is the whole path, and the console or SDK only displays folders by splitting keys on the
/delimiter. A "folder" in the console is just a zero-byte object or a common prefix. - Each object carries its data plus three kinds of descriptive information: system metadata (S3-controlled or settable HTTP headers), user-defined metadata (
x-amz-meta-*, fixed at upload) and tags (a mutable sub-resource used by policies and lifecycle rules). - S3 is strongly consistent for object operations: a successful PUT or DELETE is immediately visible to GET and LIST. Concurrent writers to one key are last-writer-wins unless you use conditional writes.
Exam facts
- Bucket names: 3–63 characters; lowercase letters, digits, hyphens and periods; must begin and end with a letter or digit; no two adjacent periods; not formatted like an IP address. Reserved prefixes
xn--,sthree-,amzn-s3-demo-; reserved suffixes-s3alias,--ol-s3,.mrap,--x-s3,--table-s3. Buckets used with Transfer Acceleration can't contain periods. ⚠️ Changed: current docs list more reserved prefixes and suffixes than the course (which names onlyxn--and-s3alias). Older material may say the rules end there. - Account regional namespace (optional, current docs): names of the form
<prefix>-<account-id>-<region>-an, created with thex-amz-bucket-namespace: account-regionalheader (--bucket-namespace account-regional); only your account can ever own them, so a deleted name can't be squatted. Enforce it with thes3:x-amz-bucket-namespacecondition key. A deleted bucket in the shared namespace can be re-created by anyone. - ⚠️ Changed: 10,000 general purpose buckets per account by default, raisable to 1,000,000 through Service Quotas (since 2024-11-14). Older material may say 100 buckets per account. Accounts above 10,000 must use paginated
ListBuckets. - Create outside us-east-1 with
LocationConstraintset to the bucket's Region; errors:BucketAlreadyExists(409, another account owns the name) vsBucketAlreadyOwnedByYou(409). - ⚠️ Changed: maximum object size 50 TB (48.8 TiB, all Regions and storage classes, since 2025-12-02). Older material may say 5 TB. A single
PutObjectstill tops out at 5 GB; the console uploads up to 160 GB; beyond that use multipart (§8) through the CLI, SDK or REST API, and for objects above 5 TB AWS recommends the SDK Transfer Manager with the CRT. - Keys: up to 1,024 bytes of UTF-8, case-sensitive; the prefix and
/count toward the limit. Avoid characters such as\,{,^,%or backticks; URL-encode spaces and special characters in tools and event filters. - User-defined metadata: names start with
x-amz-meta-, S3 stores the names in lowercase, total ≤ 2 KB (UTF-8 bytes of names + values) inside the 8 KB limit for PUT request headers; returned onGetObjectandHeadObject. It can't be edited in place: to change it, copy the object onto itself withMetadataDirective=REPLACE, which writes a new object (a new version in a versioned bucket, newLast-Modified). - System metadata you can set:
Content-Type,Cache-Control,Content-Disposition,Content-Encoding,x-amz-storage-class,x-amz-server-side-encryption,x-amz-website-redirect-location,x-amz-tagging. S3-controlled:Content-Length,Last-Modified,ETag,x-amz-version-id,x-amz-delete-marker, the checksum headers. - ETag is the MD5 of the data only for single-part uploads that are unencrypted or SSE-S3; multipart and SSE-KMS objects get a different ETag. Integrity checks use checksums instead: CRC-64/NVME is the default algorithm, and current SDKs compute a checksum on every upload.
- Object tags: up to 10 per object, keys ≤ 128 and values ≤ 256 Unicode characters, case-sensitive, strongly consistent. Set at upload with
x-amz-tagging(URL-query format) or later withPutObjectTagging, which replaces the whole tag set;GetObjectreturns only the count (x-amz-tagging-countresponse header; one User Guide page still writesx-amz-tag-count). Used in IAM/bucket policy conditions (s3:ExistingObjectTag/<key>,s3:RequestObjectTag/<key>), lifecycle and replication filters, Storage Class Analysis filters and cost reporting. Buckets take up to 50 tags. - Finding objects by metadata:
ListObjectsV2filters only by prefix. ⚠️ Changed: S3 Metadata (GA 2025-01-27) maintains read-only Apache Iceberg metadata tables (system metadata, tags, user metadata) in a table bucket that you query with Athena. Older material may say the only option is your own index, such as an event-driven Lambda writing into DynamoDB, which remains a valid design (seenotes/dynamodb-guide.md§14). - Consistency: strong read-after-write for PUT, DELETE and LIST of objects; a single-key update is atomic (readers see old or new data, never partial). Bucket configuration is eventually consistent: after enabling versioning for the first time, wait about 15 minutes before writing.
- ⚠️ Changed: conditional writes:
If-None-Match: *(fail if the key exists, since 2024-08-20) andIf-Match: <ETag>(fail if the object changed, since 2024-11-25) onPutObjectandCompleteMultipartUpload, and onCopyObject(checked against the destination key) since 2025-10-29. A failed condition returns412 Precondition Failed; when a concurrent delete wins the race, anIf-None-Matchwrite gets409 Conflict(retry a PUT; restart a multipart upload) and anIf-Matchwrite gets404 Not Found. Requires SigV4;If-None-Matchneedss3:PutObject,If-Matchneedss3:PutObjectands3:GetObject. Bucket policies can require them withs3:if-none-match/s3:if-match. Older material may say S3 can't prevent overwrites, so concurrent writers need an external lock such as a DynamoDB table, or may list onlyPutObjectandCompleteMultipartUpload. - ⚠️ Changed: conditional deletes:
DeleteObject/DeleteObjectswithIf-Match: <ETag>on general purpose buckets (since 2025-09-16), enforceable withs3:if-match. Older material may say deletes are always unconditional. - Missing object vs missing permission:
GetObjecton a key that doesn't exist returns404 NoSuchKeyonly if the caller hass3:ListBucketon the bucket; without it S3 returns403 AccessDenied, so a 403 doesn't prove the object exists.
Developer angle
# Create a bucket outside us-east-1 and upload with metadata and tags
aws s3api create-bucket --bucket invoices-archive-7f3a --region eu-west-1 \
--create-bucket-configuration LocationConstraint=eu-west-1
aws s3api put-object --bucket invoices-archive-7f3a --key 2026/10/inv-1001.pdf \
--body inv-1001.pdf --content-type application/pdf \
--metadata customer-id=c-8812,source=billing \
--tagging "Classification=Internal&Project=Blue"
aws s3api head-object --bucket invoices-archive-7f3a --key 2026/10/inv-1001.pdf
# "Edit" metadata = rewrite the object with REPLACE
aws s3api copy-object --bucket invoices-archive-7f3a --key 2026/10/inv-1001.pdf \
--copy-source invoices-archive-7f3a/2026/10/inv-1001.pdf \
--metadata-directive REPLACE --content-type application/pdf \
--metadata customer-id=c-8812,source=billing,reviewed=yes
# Tags are replaced as a whole set
aws s3api put-object-tagging --bucket invoices-archive-7f3a --key 2026/10/inv-1001.pdf \
--tagging 'TagSet=[{Key=Classification,Value=Confidential},{Key=Project,Value=Blue}]'
# "Folders" are prefixes + a delimiter
aws s3api list-objects-v2 --bucket invoices-archive-7f3a --prefix 2026/ --delimiter /
Create-only-once with a conditional write (boto3):
import boto3
from botocore.exceptions import ClientError
s3 = boto3.client("s3")
def write_once(bucket: str, key: str, body: bytes) -> bool:
"""Return True if this call created the object, False if it already existed."""
try:
s3.put_object(Bucket=bucket, Key=key, Body=body, IfNoneMatch="*")
return True
except ClientError as err:
if err.response["ResponseMetadata"]["HTTPStatusCode"] == 412:
return False # someone else won the race
raise # 409 Conflict and other errors: let the caller retry
- The
aws s3commands (cp,sync,mv) are high-level and handle multipart automatically;aws s3apimaps one-to-one to API actions and exposes every header. - Treat event keys as URL-encoded (§7) and keys as case-sensitive when building prefixes in code.
Traps
- Trap: "Create the folder before uploading
reports/2026/q3.csv." → Reality: prefixes need no folder object; the key alone defines the path. - Trap: "Update
x-amz-meta-*values with a metadata API call." → Reality: user metadata is fixed at upload; copy the object withREPLACE(new version, newLast-Modified). - Trap: "Use object tags or metadata as a search index." → Reality: LIST filters by prefix only; query S3 Metadata tables with Athena or maintain a DynamoDB index.
- Trap: "Bucket names are unique per account." → Reality: unique across all accounts in the partition, unless created in your account regional namespace.
- Trap: "A single
PutObjectcan carry a 10 GB file." → Reality: 5 GB per PUT; use multipart (required above 5 GB, recommended from 100 MB). - Trap: "A
403onGetObjectmeans the object exists." → Reality: withouts3:ListBucket, a missing key also returns403. - Trap: "
PutObjectTaggingadds one tag." → Reality: it replaces the entire set; read, merge, then write. - Trap: "Comparing ETags to MD5 works for every object." → Reality: not for multipart or SSE-KMS objects; use the stored checksum.
Practice questions
-
A developer runs the same nightly report job on two EC2 instances for high availability. Both instances sometimes finish at the same moment and upload
reports/2026-10-12.csv, and the first completed upload must never be replaced. No new AWS service may be added. Which solution meets these requirements with the fewest code changes?- A. Enable S3 Versioning so that both uploads are kept as separate versions of the key.
- B. Add the
If-None-Match: *header to each upload and treat an HTTP412as already written. - C. Acquire a lock item with a conditional
PutItemon a DynamoDB table before each instance callsPutObject. - D. Apply an Object Lock legal hold to the report as soon as the upload finishes.
-
A developer must correct the
Content-Typeof 3,000 existing objects that were uploaded asbinary/octet-stream, and must label each object withReviewed=yesso that an IAM policy condition can match it. Which TWO actions accomplish this? (Select TWO.)- A. Copy each object onto itself with
--metadata-directive REPLACEand the correct--content-type. - B. Call
head-objectwith the new--content-typevalue for each key. - C. Call
put-object-taggingfor each object with the complete new tag set. - D. Add a lifecycle rule whose filter matches the objects and rewrites their
Content-Typeto the correct value. - E. Call
put-object-aclon each object with anx-amz-meta-content-typeheader.
- A. Copy each object onto itself with
Answers
- B. The conditional header makes S3 reject the second writer with
412 Precondition Failed, which is one parameter in existing code. A keeps both uploads but the later one becomes current, so the first result is replaced as the visible object; C works but adds a service; D acts after the fact and needs Object Lock, so it doesn't stop the race. - A, C. Content-Type is system metadata that is changed only by rewriting the object, and tags are set with
PutObjectTagging(include any existing tags because the set is replaced). B only reads metadata; D isn't something lifecycle can do (it transitions and expires); E changes ACLs, not metadata, and ACL calls fail when ACLs are disabled.
2. Access control Block Public Access and access logs
Concept
- Buckets and objects are private by default. A request is allowed only if some policy grants it and nothing denies it. The policy types involved are identity-based policies (IAM users, roles), resource-based policies (bucket policy, access point policy), ACLs (only if enabled), Block Public Access, plus organization guardrails (SCPs, RCPs) and VPC endpoint policies.
- Same account: an Allow in the IAM policy or in the bucket policy is enough. Cross-account: the caller's identity policy and the bucket policy (and, for SSE-KMS, the KMS key policy) must all allow. An explicit Deny anywhere wins. Full evaluation logic:
notes/advanced-identity-guide.md§11. - Object Ownership decides whether ACLs exist: the default Bucket owner enforced setting disables ACLs, so the bucket owner owns every object and access is controlled purely by policies.
- Block Public Access (BPA) is a guardrail that overrides any policy or ACL that would make data public; it doesn't change those policies, it neutralises them.
- Server access logs record each request (requester, operation, key, status, error code) for audits; CloudTrail data events are the API-audit alternative (see
notes/monitoring-guide.md§10).
Exam facts
- Bucket policy: JSON resource policy, ≤ 20 KB, attached only by the bucket owner, must name a
Principal. Bucket-level actions (s3:ListBucket,s3:GetBucketLocation,s3:GetBucketPolicy) needarn:aws:s3:::bucket; object-level actions (s3:GetObject,s3:PutObject,s3:DeleteObject) needarn:aws:s3:::bucket/*or a prefix. Using the wrong ARN form yieldsAccessDenied. - Typical bucket-policy jobs: grant public read for a website (§9), grant another account access, require TLS or encryption headers, restrict callers to a VPC endpoint or an organization.
- Condition keys to recognise:
aws:SecureTransport(false= plain HTTP),aws:SourceIp,aws:SourceVpce/aws:SourceVpc,aws:PrincipalOrgID,aws:MultiFactorAuthAge,s3:x-amz-server-side-encryption,s3:x-amz-server-side-encryption-aws-kms-key-id,s3:x-amz-acl,s3:prefix(onListBucket),s3:ExistingObjectTag/<key>,s3:signatureAge(presigned URLs, milliseconds),s3:if-none-match. - Code on EC2, Lambda or ECS gets S3 access through its IAM role, never through access keys (see
notes/ec2-mastery-guide.md§1). The console additionally needss3:ListAllMyBucketsands3:GetBucketLocation. - ⚠️ Changed: ACLs are disabled by default (Object Ownership = Bucket owner enforced) for every new bucket since April 2023; uploads that carry any ACL other than
bucket-owner-full-controlfail with400 AccessControlListNotSupported, and ACL-based log-delivery grants are impossible. Older material may say new buckets created through the API, CLI or CloudFormation have ACLs enabled with Object writer ownership. The other settings: Bucket owner preferred (writer's ACLbucket-owner-full-controlhands ownership to the bucket owner) and Object writer (uploader owns the object). Change it withPutBucketOwnershipControls. - BPA settings:
BlockPublicAcls(reject PUTs that carry public ACLs),IgnorePublicAcls(ignore existing public ACLs),BlockPublicPolicy(rejectPutBucketPolicy/PutAccessPointPolicywith a public policy),RestrictPublicBuckets(a bucket with a public policy is reachable only by AWS service principals and the owner's account, which also cuts off cross-account grants). - BPA levels: account (
aws s3control put-public-access-block, applies to every Region), bucket, access point (fixed at creation). ⚠️ Changed: BPA can also be set at the organization level through an AWS Organizations S3 policy (all four settings as one unit, overriding member-account settings). Older material may say account level is the highest. S3 applies the most restrictive combination; removing a setting re-exposes whatever public policy or ACL still exists. - ⚠️ Changed: all four BPA settings are on by default for every new bucket, whatever the creation method (console, API, CLI, SDK, CloudFormation), since April 2023; existing buckets were not changed. Older material may say BPA is on by default only for buckets created in the console. Code that needs a public bucket must call
DeletePublicAccessBlock/PutPublicAccessBlockafter creating it. - "Public" means: a policy that grants access to
"Principal": "*"without fixed-value limiting conditions such asaws:SourceVpce,aws:SourceVpc,aws:PrincipalOrgID,aws:SourceArn,aws:SourceAccountor a narrowaws:SourceIprange (CIDRs broader than /8 for IPv4 or /32 for IPv6 count as public). ACLs are public if they grantAllUsersorAuthenticatedUsers. - IAM Access Analyzer for S3 reports buckets that are public or shared outside the account or organization.
- Server access logs to S3: the destination bucket must be in the same Region and same account as the source; delivery is best effort, usually within a few hours; grant
s3:PutObjectto the service principallogging.s3.amazonaws.comin the destination bucket policy (scope it withaws:SourceArnandaws:SourceAccount); the destination must use SSE-S3 (SSE-KMS can produce unreadable logs), can't have Object Lock enabled (nor a default retention) or Requester Pays, and shouldn't be the source bucket itself (an endless log loop). Log delivery is free; you pay for storage. - ⚠️ Changed: server access logs can also be delivered to CloudWatch Logs (structured JSON, Logs Insights queries, cross-account and cross-Region aggregation, KMS encryption). Older material may say logs go only to an S3 bucket in the same Region.
Developer angle
Bucket policy that lets one role from a partner account read a prefix, refuses plain HTTP and keeps everyone else inside the organization:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PartnerReadExports",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::222233334444:role/report-reader" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::partner-exports/exports/*"
},
{
"Sid": "PartnerListExports",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::222233334444:role/report-reader" },
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::partner-exports",
"Condition": { "StringLike": { "s3:prefix": "exports/*" } }
},
{
"Sid": "DenyPlainHttp",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::partner-exports", "arn:aws:s3:::partner-exports/*"],
"Condition": { "Bool": { "aws:SecureTransport": "false" } }
}
]
}
aws s3api put-bucket-policy --bucket partner-exports --policy file://policy.json
aws s3api get-bucket-policy-status --bucket partner-exports # IsPublic true/false
aws s3api put-public-access-block --bucket partner-exports \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
aws s3control put-public-access-block --account-id 111122223333 \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
aws s3api get-bucket-ownership-controls --bucket partner-exports
# Server access logging: destination policy for logging.s3.amazonaws.com, then enable
aws s3api put-bucket-policy --bucket orders-logs --policy file://log-delivery-policy.json
aws s3api put-bucket-logging --bucket orders-prod \
--bucket-logging-status '{"LoggingEnabled":{"TargetBucket":"orders-logs","TargetPrefix":"orders-prod/"}}'
- The partner's role still needs its own IAM policy allowing
s3:GetObjecton that ARN; one side alone isn't enough across accounts. - Debug
AccessDeniedby checking, in order: explicit denies (bucket policy, SCP/RCP, VPC endpoint policy), the identity policy, the bucket policy, BPA, the KMS key policy for SSE-KMS objects, and Object Ownership if the request sends an ACL.
Traps
- Trap: "The IAM policy allows
s3:GetObject, so the cross-account read works." → Reality: the bucket policy in the owning account must also allow it. - Trap: "Grant
s3:ListBucketonarn:aws:s3:::bucket/*." → Reality:ListBucketis a bucket-level action; use the bucket ARN (withs3:prefixto narrow it). - Trap: "Use object ACLs to share data with another account." → Reality: ACLs are disabled by default; use bucket policies or access points.
- Trap: "Disabling BPA on the bucket makes it public." → Reality: a public bucket policy is still required, and account- or organization-level BPA can override the bucket setting.
- Trap: "
RestrictPublicBucketsonly affects anonymous users." → Reality: once a policy is public, it also blocks cross-account access granted by that policy. - Trap: "Logs for many Regions can go to one central S3 bucket." → Reality: an S3 destination must be in the same Region and account; aggregate through CloudWatch Logs delivery or replication.
- Trap: "Log the bucket into itself to keep things simple." → Reality: each delivered log object is itself a logged write, so the bucket keeps feeding itself.
- Trap: "Grant the Log Delivery group
WRITEin the destination ACL." → Reality: with ACLs disabled, use a bucket policy forlogging.s3.amazonaws.com.
Practice questions
-
An application role in account 222233334444 must read objects under
exports/in a bucket owned by account 111122223333. The role's IAM policy allowss3:GetObjectonarn:aws:s3:::partner-exports/exports/*, but every request fails withAccessDenied. Objects use SSE-S3 and the bucket uses the Bucket owner enforced setting. What must the bucket owner add?- A. A bucket ACL grant of
READto the canonical user ID of account 222233334444 for the bucket. - B. An IAM role in account 111122223333 whose trust policy allows
s3.amazonaws.com. - C. A change to the bucket's Block Public Access settings that turns off
RestrictPublicBuckets. - D. A bucket policy statement that allows that role
s3:GetObjecton objects underexports/.
- A. A bucket ACL grant of
-
A developer enables server access logging on bucket
orders-prodin us-east-1 withput-bucket-logging, choosing destination bucketorders-logs. A day later no log objects exist.orders-logsis in eu-west-1, uses the Bucket owner enforced setting, and its bucket policy allows only the company's administrator role. Which TWO actions will make log delivery work? (Select TWO.)- A. Enable versioning and MFA Delete on the destination bucket.
- B. Use a destination bucket in us-east-1 that is owned by the same account.
- C. Grant the S3 Log Delivery group
WRITEandREAD_ACPpermissions through the destination bucket's access control list. - D. Turn on S3 Transfer Acceleration for the destination bucket.
- E. Add a bucket policy statement that lets
logging.s3.amazonaws.comcalls3:PutObjecton the destination.
Answers
- D. Cross-account access needs an Allow on both sides: the identity policy exists, so the bucket policy must grant the role. A fails because ACLs are disabled; B describes a service role, which a reader doesn't need; C is unrelated because nothing here is public.
- B, E. An S3 log destination must be in the same Region and account, and the logging service principal needs
s3:PutObjectthrough the bucket policy. A doesn't grant anything; C is impossible while ACLs are disabled; D only speeds up client uploads.
3. Encryption at rest and in transit
Concept
- At rest there are two families. Server-side encryption (S3 encrypts on write and decrypts on read for any caller allowed to read) with three key sources: keys owned by S3 (SSE-S3), a KMS key (SSE-KMS, or DSSE-KMS for two layers), or a key you send with every request (SSE-C). Client-side encryption means your code encrypts before upload, so S3 only ever stores ciphertext.
- Since 2023-01-05 every new object gets at least SSE-S3, so the real decision is who controls the key, whether key use must be audited, and what it costs in KMS calls.
- In transit, clients talk TLS to the HTTPS endpoints; a bucket policy can refuse anything else.
- Rule of thumb: SSE-S3 when "encrypted" is enough; SSE-KMS when you need key policies, CloudTrail records of key use, separation of duties or cross-account sharing; DSSE-KMS when a standard demands two independent layers; SSE-C when keys must never be stored in AWS; client-side when even S3 must not see plaintext.
Exam facts
| Option | Key control | Request header(s) | Notes |
|---|---|---|---|
| SSE-S3 | S3 owns and rotates the keys; AES-256 (GCM) | x-amz-server-side-encryption: AES256 |
Default for all buckets, free, no per-key audit trail |
| SSE-KMS | KMS key: AWS managed aws/s3 or customer managed |
x-amz-server-side-encryption: aws:kms, x-amz-server-side-encryption-aws-kms-key-id, optional x-amz-server-side-encryption-context |
Key policy control, CloudTrail logs every GenerateDataKey/Decrypt, KMS request charges and quotas |
| DSSE-KMS | KMS key + a second, S3-managed layer | x-amz-server-side-encryption: aws:kms:dsse |
Two independent AES-256 layers for compliance; costs more; no S3 Bucket Keys |
| SSE-C | You supply the key on every request | x-amz-server-side-encryption-customer-algorithm: AES256, -customer-key (base64 256-bit key), -customer-key-MD5 |
HTTPS only; S3 keeps only a salted HMAC of the key; lose the key, lose the data |
| Client-side | You (Amazon S3 Encryption Client, AWS Encryption SDK) | none | S3 stores ciphertext (and still adds SSE-S3 on top) |
- ⚠️ Changed: SSE-S3 is applied to every new object upload in every bucket (new buckets and existing buckets that had no default configured) since 2023-01-05, at no cost; you can switch the default to SSE-KMS or DSSE-KMS but can't turn encryption off; existing unencrypted objects were not re-encrypted. Older material may say objects stay unencrypted unless default encryption is enabled or SSE headers are sent. The encryption used comes back in the
x-amz-server-side-encryptionresponse header, and CloudTrail shows"SSEApplied":"Default_SSE_S3"for defaulted uploads. - ⚠️ Changed: there are five methods: DSSE-KMS was added on 2023-06-13. Older material may say S3 offers four encryption methods.
- Default encryption (
PutBucketEncryption): SSE-S3, SSE-KMS (optionally withBucketKeyEnabled) or DSSE-KMS; SSE-C can't be a default. It applies to uploads that carry no encryption headers; explicit headers on a request win. S3 doesn't validate the KMS key ID you configure, so a typo surfaces only when uploads fail. - ⚠️ Changed: re-encrypting existing objects no longer always needs a rewrite.
UpdateObjectEncryption(aws s3api update-object-encryption, or a Batch Operations Update object encryption job in bulk) switches an SSE-S3 or SSE-KMS object to SSE-KMS, to another KMS key or to a Bucket Key in place: no data copy, no restore of archived objects, and ETag,Last-Modified, storage class and checksums are kept. It needss3:UpdateObjectEncryptionpluskms:Encrypt,kms:Decrypt,kms:GenerateDataKey,kms:ReEncrypt*, a full key ARN from the bucket owner's account, and no Object Lock retention or legal hold on the version; it doesn't trigger replication. Unencrypted, SSE-C and DSSE-KMS objects, or a move to DSSE-KMS or SSE-C, still needCopyObjectin place or a Batch Operations Copy job (which doesn't support SSE-C). Older material may say every re-encryption means copying the object. - SSE-KMS rules: the key must be a symmetric key in the same Region as the bucket;
aws/s3is created on first use and can't be used across accounts, so sharing SSE-KMS data with another account needs a customer managed key whose key policy grants that account; specify keys by full ARN (an alias resolves in the requester's account); uploads needkms:GenerateDataKey, downloadskms:Decrypt, multipart uploads both; the default encryption context is the object ARN (the bucket ARN when a Bucket Key is used); GET and PUT requests for KMS-encrypted objects must use HTTPS and be signed (anonymous requests fail). - ⚠️ Changed: every SSE-KMS PUT/GET without a Bucket Key is a KMS call that counts against the shared symmetric cryptographic request quota: 10,000 per second by default, 20,000 in seven larger Regions, 100,000 in us-east-1, us-west-2 and eu-west-1 (adjustable). Exceeding it returns
ThrottlingException. Older material may say 5,500, 10,000 or 30,000 requests per second (a narrative paragraph on the same KMS page still says 5,500 / 10,000 / 50,000; the quota table is the current value). Fixes: enable an S3 Bucket Key (cuts KMS request costs by up to 99 percent, new objects only), request a quota increase, retry with backoff (details:notes/security-encryption-guide.md§5). - Don't send encryption headers on GET or HEAD for SSE-S3, SSE-KMS or DSSE-KMS objects; S3 rejects them with
400 Bad Request. (SSE-C is the opposite: the key headers are mandatory on GET, HEAD and asx-amz-copy-source-server-side-encryption-customer-*when copying.) - SSE-C details: HTTP requests are rejected (treat a key sent over HTTP as compromised); not available in the console; presigned URLs work only programmatically because the client must still send the key headers; multipart parts repeat the same key headers; S3 Replication supports SSE-C objects.
- ⚠️ Changed: SSE-C is blocked by default (since April 2026) for new general purpose buckets in all Regions except Middle East (Bahrain) and Middle East (UAE), and for existing buckets in accounts that had no SSE-C objects; enable it with
PutBucketEncryptionandBlockedEncryptionTypesset toNONE(s3:PutEncryptionConfiguration); while blocked, SSE-C writes fail with403 AccessDenied. Older material may say SSE-C works on any bucket as long as the key headers arrive over HTTPS. - Forcing a method or key: a bucket policy that denies
s3:PutObjectwhens3:x-amz-server-side-encryptionisn'taws:kms, or whens3:x-amz-server-side-encryption-aws-kms-key-idisn't your key ARN. The policy judges the request as sent: an upload without the header is denied even though default encryption would have encrypted it, so use default encryption alone when "always encrypted" is the only requirement. - In transit: the regional REST endpoints answer both HTTP and HTTPS, website endpoints are HTTP only (§9), access points are HTTPS only; SDKs and the CLI use HTTPS by default. Refuse plain HTTP with
"Bool": {"aws:SecureTransport": "false"}in a Deny statement (§2). - A bucket that receives server access logs must use SSE-S3 (§2).
Developer angle
# One object with a customer managed KMS key
aws s3api put-object --bucket finance-docs --key q3/ledger.csv --body ledger.csv \
--server-side-encryption aws:kms \
--ssekms-key-id arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab
# Bucket default: SSE-KMS with an S3 Bucket Key (fewer KMS calls)
aws s3api put-bucket-encryption --bucket finance-docs --server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
},
"BucketKeyEnabled": true
}]
}'
aws s3api get-bucket-encryption --bucket finance-docs
# SSE-C with the high-level CLI: raw 32-byte key file, same key to read it back
aws s3 cp scan.tif s3://legacy-scans/scan.tif --sse-c AES256 --sse-c-key fileb://customer.key
aws s3 cp s3://legacy-scans/scan.tif ./scan.tif --sse-c AES256 --sse-c-key fileb://customer.key
Bucket policy that rejects uploads not encrypted with one specific KMS key:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "RequireLedgerKey",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::finance-docs/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption-aws-kms-key-id": "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
}
}
}]
}
import boto3
s3 = boto3.client("s3")
KEY_ARN = "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
s3.put_object(Bucket="finance-docs", Key="q3/ledger.csv", Body=b"id,amount\n1,10\n",
ServerSideEncryption="aws:kms", SSEKMSKeyId=KEY_ARN, BucketKeyEnabled=True)
obj = s3.get_object(Bucket="finance-docs", Key="q3/ledger.csv") # no SSE parameters on GET
print(obj["ServerSideEncryption"], obj["SSEKMSKeyId"])
AccessDeniedon an SSE-KMS upload whiles3:PutObjectis allowed usually means a missingkms:GenerateDataKey(orkms:Decryptfor multipart) in the IAM or key policy.
Traps
- Trap: "SSE-S3 lets auditors see every use of the key in CloudTrail." → Reality: only KMS keys (SSE-KMS, DSSE-KMS) produce per-request key-usage events.
- Trap: "Share SSE-KMS objects with another account using
aws/s3." → Reality: AWS managed keys can't be used cross-account; use a customer managed key and grant the other account in its key policy. - Trap: "Switching the default to SSE-KMS re-encrypts what is already stored." → Reality: only new uploads change; convert existing SSE-S3/SSE-KMS objects with
UpdateObjectEncryption, and rewrite unencrypted or SSE-C ones withCopyObject(or a Batch Operations Copy job, which doesn't support SSE-C). - Trap: "Send
x-amz-server-side-encryption: aws:kmson downloads too." → Reality: GET/HEAD with that header fails with400; S3 decrypts automatically. - Trap: "SSE-C over HTTP is acceptable inside a private network." → Reality: S3 rejects SSE-C requests over HTTP.
- Trap: "A new bucket accepts SSE-C uploads out of the box." → Reality: since April 2026 SSE-C is blocked by default; set
BlockedEncryptionTypestoNONEfirst. - Trap: "Add a Bucket Key to DSSE-KMS to cut costs." → Reality: Bucket Keys aren't supported with DSSE-KMS.
- Trap: "High upload rates on an SSE-KMS bucket only hit S3 limits." → Reality: KMS request quotas throttle first; use Bucket Keys.
Practice questions
-
A finance application must store reports encrypted with a key whose use is recorded in CloudTrail and whose key policy is controlled by the security team, and auditors require that any upload not using that particular key is rejected. Which approach meets the requirements with the LEAST operational overhead?
- A. Use SSE-C with keys kept in AWS Secrets Manager, and have the application pass the key with every request.
- B. Keep the default SSE-S3 encryption and turn on CloudTrail data events to audit every request to the bucket.
- C. Make the customer managed key the SSE-KMS default and deny uploads in the bucket policy unless they name that key.
- D. Encrypt each file with the AWS Encryption SDK before uploading it, and store the encrypted data keys in DynamoDB.
-
After switching a busy bucket's default encryption from SSE-S3 to SSE-KMS with a customer managed key, a developer sees intermittent upload and download failures caused by KMS
ThrottlingExceptionerrors. Which TWO actions reduce or remove the failures? (Select TWO.)- A. Switch the default encryption to DSSE-KMS so that S3 batches the KMS calls.
- B. Enable an S3 Bucket Key in the bucket's default encryption configuration.
- C. Add the
x-amz-server-side-encryption-contextheader to every upload request. - D. Request a higher KMS symmetric cryptographic request quota in Service Quotas.
- E. Enable S3 Transfer Acceleration so that uploads and downloads enter AWS through the nearest edge locations.
Answers
- C. SSE-KMS with a customer managed key gives key-policy control and CloudTrail records of key use, and the bucket policy rejects uploads that use another key. A works but leaves key storage, rotation and delivery to you on every call; B gives no control over the key and logs only S3 API calls; D adds custom key management.
- B, D. A Bucket Key lets S3 derive data keys locally, removing most KMS calls, and a higher quota raises the ceiling. A adds a second layer and doesn't support Bucket Keys; C changes the context, not the call rate; E speeds up long-distance transfers and adds no KMS capacity.
4. Versioning MFA Delete and Object Lock
Concept
- Versioning keeps every write to a key as its own version. Overwrites add a new current version; a simple delete only places a delete marker on top, so the data can still be recovered. It is the safety net against accidental deletes and buggy overwrites, and it is a prerequisite for replication (§5) and Object Lock.
- MFA Delete adds a second factor, the bucket owner's (root user's) MFA code, to the two most destructive operations: permanently deleting a version and changing the bucket's versioning state.
- Object Lock applies write-once-read-many (WORM) protection to individual object versions: a retention period (governance or compliance mode) and/or a legal hold. It satisfies regulations such as SEC 17a-4 and protects against ransomware or rogue administrators.
Exam facts
- Versioning states: unversioned (default), enabled, suspended. Once enabled, a bucket can never return to unversioned, only to suspended. The state applies to the whole bucket.
- Objects already in the bucket when versioning is first switched on keep the version ID
nulluntil they are overwritten. Each version is a full copy billed at normal rates; clean up withNoncurrentVersionExpiration(§6). - Requests in a versioned bucket: PUT on an existing key → new version (the old one becomes noncurrent); GET without
versionId→ current version; GET withversionId→ that version; DELETE withoutversionId→ delete marker (GET then returns404withx-amz-delete-marker: true; a GET that names the marker's own version ID returns405 Method Not Allowed); DELETE withversionId→ permanent removal of that version (no marker). - Undo a delete: delete the delete marker (DELETE with the marker's version ID). Roll back an overwrite: copy the old version over the key (preferred, keeps history) or permanently delete the newer version.
- Suspended: new writes get version ID
nulland overwrite any existingnullversion; older versions are kept; deletes add anulldelete marker. - After enabling versioning for the first time, wait about 15 minutes before writing (bucket configuration is eventually consistent).
- IAM actions:
s3:PutBucketVersioning,s3:ListBucketVersions(forListObjectVersions),s3:GetObjectVersion,s3:DeleteObjectVersion. Denys3:DeleteObjectVersion(ands3:PutLifecycleConfiguration) to stop users from destroying history. - MFA Delete: once it is on, two kinds of call need a current MFA code: changing the bucket's versioning state (suspending versioning or turning MFA Delete off) and removing a specific version for good; listing versions, normal uploads and simple deletes (delete markers) need no MFA. Switching it on or off is reserved for the root user of the account that owns the bucket, through the CLI or API only (
put-bucket-versioning ... --mfa "<device-serial-or-ARN> <code>", headerx-amz-mfa), never in the console, and it can't be combined with lifecycle configurations. - Don't confuse MFA Delete with MFA-protected API access, which is an IAM/bucket policy condition (
aws:MultiFactorAuthAge) requiring callers to use MFA-backed temporary credentials. - Object Lock works only with versioning. ⚠️ Changed: Object Lock can be enabled on existing buckets (since 2023-11-20) with
PutObjectLockConfigurationor the console. Older material may say it can only be turned on when the bucket is created (or through AWS Support). Once enabled, Object Lock can't be disabled and versioning can't be suspended. - Governance mode: most users can't delete, overwrite or shorten the lock; principals with
s3:BypassGovernanceRetentioncan, if they sendx-amz-bypass-governance-retention: true(the console adds it automatically). Good for testing retention settings. - Compliance mode: nobody, including the root user, can delete or overwrite the version or shorten the period or change the mode until the retain-until date passes; you can only extend it. The only escape is closing the account.
- Retention period: explicit retain-until date per version, or a bucket default retention in days or years (individual settings override the default); maximum 100 years; extend with
s3:PutObjectRetention; cap allowed periods with thes3:object-lock-remaining-retention-dayscondition key. Current docs also describe variable retention: an event hold keeps the version locked until you release it, then a configured duration starts. - Legal hold: on/off flag with no expiry, independent of retention, set by anyone with
s3:PutObjectLegalHold; bypassing governance doesn't lift a legal hold. - Locks protect versions: a simple DELETE still succeeds and adds a delete marker (markers aren't WORM-protected); a permanent delete of a locked version returns
403 AccessDenied. Lifecycle can't expire locked versions. - A PUT that sets retention must include
Content-MD5or anx-amz-sdk-checksum-algorithmchecksum header (SDKs add one). Replication of locked objects needs Object Lock on the destination too (§5). A bucket with Object Lock can't receive server access logs.
Developer angle
# Versioning
aws s3api put-bucket-versioning --bucket contracts-store --versioning-configuration Status=Enabled
aws s3api list-object-versions --bucket contracts-store --prefix contracts/acme.pdf
aws s3api get-object --bucket contracts-store --key contracts/acme.pdf \
--version-id 3HL4kqtJlcpXroDTDmJ.rmSpXd3dIbrHY acme-old.pdf
# Undo an accidental delete: remove the delete marker (its own version ID)
aws s3api delete-object --bucket contracts-store --key contracts/acme.pdf \
--version-id UfKXp1i9e3cmZ8Uyw9GS2s0C8dCQWVdG
# MFA Delete: root user's credentials and MFA device, CLI only
aws s3api put-bucket-versioning --bucket contracts-store \
--versioning-configuration Status=Enabled,MFADelete=Enabled \
--mfa "arn:aws:iam::111122223333:mfa/root-account-mfa-device 123456"
# Object Lock on an existing versioned bucket, 7-year compliance default
aws s3api put-object-lock-configuration --bucket trade-records --object-lock-configuration \
'{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Years":7}}}'
aws s3api put-object-legal-hold --bucket trade-records --key 2026/t-889.json \
--legal-hold Status=ON
aws s3api get-object-retention --bucket trade-records --key 2026/t-889.json
# Governance mode in a separate test bucket: set a lock, then remove the version anyway
aws s3api put-object-retention --bucket retention-sandbox --key test/t-1.json \
--retention '{"Mode":"GOVERNANCE","RetainUntilDate":"2027-01-01T00:00:00Z"}'
aws s3api delete-object --bucket retention-sandbox --key test/t-1.json \
--version-id 3sL4kqtJlcpXroDTDmJ.rmSpXd3dIbrHY --bypass-governance-retention # needs s3:BypassGovernanceRetention
Traps
- Trap: "Suspending versioning deletes the old versions." → Reality: existing versions stay (and keep costing) until you delete them or a lifecycle rule expires them.
- Trap: "A delete in a versioned bucket is permanent." → Reality: without a version ID it only adds a delete marker; remove the marker to restore.
- Trap: "Turn on MFA Delete in the console." → Reality: CLI or API only, by the root user, and not alongside lifecycle rules.
- Trap: "Governance mode stops administrators." → Reality: anyone with
s3:BypassGovernanceRetentioncan override it; compliance mode is the one nobody can bypass. - Trap: "Object Lock prevents the object from appearing deleted." → Reality: simple deletes still add delete markers; the locked version itself survives.
- Trap: "You need a new bucket to use Object Lock." → Reality: existing buckets can enable it now (versioning required, and it can't be turned off).
- Trap: "A legal hold expires with the retention period." → Reality: it stays until someone with
s3:PutObjectLegalHoldremoves it.
Practice questions
-
A user deleted
contracts/acme.pdffrom a versioning-enabled bucket with a plainDeleteObjectcall that didn't include a version ID. The developer must make the latest content available again under the same key as quickly as possible. What should the developer do?- A. Delete the delete marker by calling
delete-objectwith the marker's version ID. - B. Restore the object from S3 Glacier Flexible Retrieval with
restore-object. - C. Suspend versioning on the bucket so that the previous version becomes current again.
- D. Run an S3 Batch Replication job that copies the previous version back to the same key.
- A. Delete the delete marker by calling
-
A company must keep trade confirmations immutable for 7 years so that no user, including administrators with full S3 permissions, can delete them or shorten the protection. The existing bucket already has versioning enabled and contains data. Which TWO steps meet the requirements? (Select TWO.)
- A. Create a new bucket, because Object Lock can be turned on only when a bucket is created.
- B. Enable Object Lock on the existing bucket with
put-object-lock-configuration. - C. Set a default retention of 7 years in COMPLIANCE mode.
- D. Set a default retention of 7 years in GOVERNANCE mode and deny
s3:BypassGovernanceRetentionin the bucket policy. - E. Enable MFA Delete on the bucket by using the root user's MFA device.
Answers
- A. Removing the delete marker makes the previous version current again immediately. B applies to archived objects, not deletes; C changes how future writes are versioned and doesn't remove the marker; D adds a job for something a single call fixes.
- B, C. Object Lock can now be enabled on an existing versioned bucket, and compliance mode can't be bypassed or shortened by anyone. A is outdated; D can be undone by an administrator who edits the bucket policy; E only adds MFA to permanent deletes and doesn't enforce a retention period.
5. Replication CRR and SRR
Concept
- Live replication asynchronously copies new objects (with their metadata, tags, ACL updates and Object Lock retention) from a source bucket to one or more destination buckets. Cross-Region Replication (CRR) targets another Region; Same-Region Replication (SRR) stays in the Region. Buckets can belong to different accounts.
- Replication is configured on the source bucket as rules: a filter (prefix and/or tags), a priority, a destination (bucket, optional storage class, account, KMS key) and options such as delete marker replication, Replication Time Control and metrics.
- S3 Batch Replication is the on-demand counterpart that copies objects live replication never touched: existing objects, failed ones, and replicas.
- Why teams use it: CRR puts a disaster-recovery or regulator-mandated copy in a distant Region and serves readers or compute clusters in another Region from a local copy; SRR keeps extra copies inside one country for data-residency rules, gathers logs from many buckets into one, or feeds a test account with live production data.
Exam facts
- Requirements: versioning enabled on the source and every destination; an IAM role that trusts
s3.amazonaws.com; the person configuring replication needsiam:PassRolefor it; the source bucket owner must be able to read the objects (objects written by other accounts with ACLs needREADandREAD_ACPfor the owner); both Regions enabled for the account(s). - Role permissions: on the source
s3:GetReplicationConfiguration,s3:ListBucket,s3:GetObjectVersionForReplication,s3:GetObjectVersionAcl,s3:GetObjectVersionTagging; on the destinations3:ReplicateObject,s3:ReplicateDelete,s3:ReplicateTags; pluss3:GetObjectRetentionands3:GetObjectLegalHoldwith Object Lock. - Cross-account: the destination bucket policy must allow the source role to replicate; the destination can't be Requester Pays. Replica ownership moves to the destination account if the destination uses Bucket owner enforced (or with the owner override option and
s3:ObjectOwnerOverrideToBucketOwner). - Live replication only covers writes made after the rule is in place. Existing objects, previously
FAILEDobjects and objects already replicated elsewhere need Batch Replication. - Not replicated by default: delete markers (enable delete marker replication, not possible on tag-based rules); deletes that specify a version ID (never, so a compromised source can't wipe the copies); actions taken by lifecycle rules; replicas created by another rule (replicas aren't re-replicated: with A→B and B→C, objects written to A stop at B); objects in S3 Glacier Flexible Retrieval, Glacier Deep Archive or the Intelligent-Tiering archive tiers; bucket-level settings (lifecycle, notifications, policies); objects tagged only after upload under a tag-based rule.
- Encryption: unencrypted and SSE-S3 objects replicate by default. ⚠️ Changed: SSE-C objects replicate too (since 2022-10-24). Older material may say SSE-C objects are never replicated. SSE-KMS and DSSE-KMS objects replicate only after you opt in with
SourceSelectionCriteria→SseKmsEncryptedObjectsEnabledand give aReplicaKmsKeyIDthat lives in the destination Region; the role needskms:Decrypton the source key andkms:Encrypton the destination key (pluskms:GenerateDataKeywhen plaintext objects land in an SSE-KMS-default destination).PutBucketReplicationdoesn't validate the KMS key: a wrongReplicaKmsKeyIDstill returns200 OK, then replication fails. Cross-account SSE-KMS replication needs a customer managed key. Heavy SSE-KMS replication can hit KMS quotas. - Unencrypted source objects are encrypted with the destination bucket's default encryption, so their ETags differ from the source.
- Replicas can use a different storage class (for example Glacier Deep Archive or One Zone-IA for a backup copy). Lifecycle rules on the destination honour the original creation time.
- Limits: up to 1,000 rules per configuration (fixed) and 28 destination buckets per source (adjustable).
- Replication Time Control (RTC): predictable, SLA-backed replication of new objects within 15 minutes (most finish in seconds). ⚠️ Changed: the RTC SLA commits to 99.9 % of objects per Region pair per month within 15 minutes (service credits start below 99.9 %). Older material may say 99.99 %, which the replication overview page still uses as the design figure. RTC adds replication metrics and the events
s3:Replication:OperationMissedThreshold/OperationReplicatedAfterThreshold; default transfer-rate quota 1 Gbps (adjustable; the SLA doesn't apply above it or above the request-rate guidelines); extra cost; not available for Batch Replication. - Replica modification sync replicates metadata changes made on replicas back to the source, enabling two-way replication (for Multi-Region Access Point failover set-ups).
- Status:
x-amz-replication-statusisPENDING,COMPLETEDorFAILEDon the source andREPLICAon the copy. Lifecycle doesn't transition or expire source objects while they arePENDINGorFAILED. - Disabling versioning on the source is refused while a replication configuration exists; suspending it on a destination makes replication fail. SRR has no inter-Region data transfer charge.
Developer angle
Role trust policy (S3 assumes the role):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "s3.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
Replication configuration with KMS opt-in, delete marker replication and a cheaper storage class for the copy:
{
"Role": "arn:aws:iam::111122223333:role/s3-replication-role",
"Rules": [{
"ID": "invoices-to-eu",
"Priority": 1,
"Status": "Enabled",
"Filter": { "Prefix": "invoices/" },
"DeleteMarkerReplication": { "Status": "Enabled" },
"SourceSelectionCriteria": { "SseKmsEncryptedObjects": { "Status": "Enabled" } },
"Destination": {
"Bucket": "arn:aws:s3:::invoices-replica-eu",
"StorageClass": "STANDARD_IA",
"EncryptionConfiguration": {
"ReplicaKmsKeyID": "arn:aws:kms:eu-west-1:111122223333:key/9f1e2d3c-4b5a-6789-0abc-def123456789"
}
}
}]
}
aws s3api put-bucket-versioning --bucket invoices-replica-eu --versioning-configuration Status=Enabled
aws s3api put-bucket-replication --bucket invoices-us --replication-configuration file://replication.json
aws s3api get-bucket-replication --bucket invoices-us
aws s3api head-object --bucket invoices-us --key invoices/2026/inv-77.pdf --query ReplicationStatus
Traps
- Trap: "Turning on replication copies everything already in the bucket." → Reality: only new writes; use S3 Batch Replication for existing objects.
- Trap: "Deleting a version in the source removes it in the destination too." → Reality: version-ID deletes are never replicated; only delete markers, and only if enabled.
- Trap: "Chain A→B→C to fan data out." → Reality: replicas aren't re-replicated; add both destinations to A's configuration.
- Trap: "SSE-KMS objects replicate like SSE-S3 objects." → Reality: they need
SseKmsEncryptedObjects, a destination-Region key and KMS permissions on the role. - Trap: "Lifecycle expirations in the source show up in the destination." → Reality: lifecycle actions aren't replicated; configure lifecycle on both buckets.
- Trap: "Replication works without versioning on the destination." → Reality: both sides need versioning.
- Trap: "Use an access point as the replication destination." → Reality: access points can't be replication destinations.
Practice questions
-
A team enables Cross-Region Replication from bucket
media-usto bucketmedia-eu. New uploads appear inmedia-eu, but the 4 million objects that existed before the rule was created don't. Which solution copies those objects with the LEAST operational overhead while keeping their version IDs and metadata?- A. Run
aws s3 syncfrom an EC2 instance in the destination Region. - B. Create an S3 Batch Replication job for the existing objects.
- C. Add a second replication rule with a higher priority for the same prefix.
- D. Enable S3 Replication Time Control on the existing rule.
- A. Run
-
Objects encrypted with SSE-KMS under a customer managed key in us-east-1 aren't replicating to a destination bucket in us-west-2, while SSE-S3 objects replicate correctly. Which TWO changes are required? (Select TWO.)
- A. Enable delete marker replication on the replication rule.
- B. Turn on S3 Bucket Keys for the destination bucket's default encryption.
- C. Add
SourceSelectionCriteriawithSseKmsEncryptedObjectsset toEnabledto the rule. - D. Change the replication role's trust policy so that the role trusts
kms.amazonaws.cominstead ofs3.amazonaws.com. - E. Set a
ReplicaKmsKeyIDin us-west-2 and grant the rolekms:Decryptandkms:Encrypton the two keys.
Answers
- B. A Batch Replication job copies the pre-existing objects on demand, keeping their metadata and version IDs. A needs compute and creates new versions rather than true replicas; C still applies only to new writes; D only adds a 15-minute target for new objects.
- C, E. KMS-encrypted objects need the explicit opt-in plus a destination-Region key and KMS permissions for the role. A concerns deletes; B changes how the destination calls KMS but doesn't enable replication of KMS objects; D is wrong because S3, not KMS, assumes the replication role.
6. Storage classes and lifecycle rules
Concept
- Every object has a storage class that sets its price, availability, minimum billing rules and how fast the first byte comes back. Durability is the same eleven nines (99.999999999 %) for all current classes; what changes is availability, the number of Availability Zones and the retrieval model.
- Pick the class from the access pattern: frequent → S3 Standard; unknown or changing → Intelligent-Tiering; infrequent but instant → Standard-IA (One Zone-IA if the data can be re-created); archive but instant → Glacier Instant Retrieval; archive you can wait for → Glacier Flexible Retrieval or Deep Archive.
- Lifecycle rules automate the journey: they move ageing objects to colder classes and finally delete them, and they also clean up noncurrent versions, unfinished multipart uploads and orphaned delete markers.
- Storage Class Analysis watches access patterns to tell you when Standard data could move to Standard-IA.
Exam facts
Class (x-amz-storage-class) |
AZs | Designed availability | Minimum duration | Minimum billable size | Access |
|---|---|---|---|---|---|
S3 Standard (STANDARD, default) |
≥ 3 | 99.99 % | none | none | milliseconds |
Intelligent-Tiering (INTELLIGENT_TIERING) |
≥ 3 | 99.9 % | none | none (objects < 128 KB aren't monitored) | milliseconds; opt-in archive tiers need a restore |
Standard-IA (STANDARD_IA) |
≥ 3 | 99.9 % | 30 days | 128 KB | milliseconds, per-GB retrieval fee |
One Zone-IA (ONEZONE_IA) |
1 | 99.5 % | 30 days | 128 KB | milliseconds, retrieval fee, lost if the AZ is destroyed |
Glacier Instant Retrieval (GLACIER_IR) |
≥ 3 | 99.9 % | 90 days | 128 KB | milliseconds, higher retrieval fee |
Glacier Flexible Retrieval (GLACIER) |
≥ 3 | 99.99 % after restore | 90 days | 40 KB metadata overhead per object | restore first: minutes to hours |
Glacier Deep Archive (DEEP_ARCHIVE) |
≥ 3 | 99.99 % after restore | 180 days | 40 KB metadata overhead per object | restore first: within 12 or 48 hours |
Express One Zone (EXPRESS_ONEZONE) |
1 (you pick it) | 99.95 % | none | none | single-digit milliseconds, directory buckets only |
- ⚠️ Changed: S3 Express One Zone (2023-11-28) is an eighth current class: single-AZ, single-digit-millisecond access, stored in directory buckets (names end in
--x-s3, 100 per account by default, public access always disabled). Older material may list only seven storage classes. Reduced Redundancy Storage still exists but AWS recommends against it. - ⚠️ Changed: archived Glacier Flexible Retrieval and Deep Archive objects have no minimum billable size; each one adds 40 KB of overhead (8 KB billed at the S3 Standard rate, 32 KB at the archive rate). Older material may list a 40 KB minimum billable object size. Either way, aggregate many tiny files before archiving.
- Deleting, overwriting or transitioning an object before its minimum duration is billed as if it had stayed for the full period.
- Restoring archives:
RestoreObjectwithDays(how long the temporary copy lives) andGlacierJobParameters.Tier. Glacier Flexible Retrieval: Expedited 1–5 minutes (objects under 250 MB), Standard 3–5 hours, Bulk 5–12 hours (free). Deep Archive: Standard within 12 hours, Bulk within 48 hours, no Expedited. Batch Operations restores start faster (minutes for Flexible Retrieval, 9–12 hours for Deep Archive). Provisioned capacity guarantees Expedited capacity. The restored copy is billed at the Standard rate on top of the archive, the storage class staysGLACIER/DEEP_ARCHIVE,HeadObjectshows progress inx-amz-restore, and restore requests are limited to 1,000 per second per account (ThrottlingException). Glacier Instant Retrieval needs no restore. - Intelligent-Tiering: objects start in Frequent Access, move to Infrequent Access after 30 days without access and to Archive Instant Access after 90 days; optional Archive Access (from 90 days) and Deep Archive Access (from 180 days) tiers are activated per bucket and need a restore. ⚠️ Changed: the activation thresholds can be raised to at most 730 days. Older material may say "700+ days". A per-object monthly monitoring and automation charge replaces retrieval fees; no minimum duration; objects under 128 KB aren't monitored and always stay in Frequent Access.
- Lifecycle rules: one configuration per bucket with up to 1,000 rules (not adjustable);
PutBucketLifecycleConfigurationreplaces the whole configuration; filters by prefix, tags (Andfor several) and object size (ObjectSizeGreaterThan/LessThan, exclusive); rules apply to existing objects too. - Actions:
TransitionandExpiration(current versions);NoncurrentVersionTransitionandNoncurrentVersionExpiration(NoncurrentDays, optionalNewerNoncurrentVersions1–100, which requires aFilter);AbortIncompleteMultipartUpload(DaysAfterInitiation);ExpiredObjectDeleteMarker. The last two can't be used in rules with tag filters. - Transitions only go downhill (waterfall): Standard → any colder class; Standard-IA → Intelligent-Tiering, One Zone-IA or a Glacier class; Intelligent-Tiering → One Zone-IA or a Glacier class (depending on its current tier); One Zone-IA → Glacier Flexible Retrieval or Deep Archive only (not Glacier IR); Glacier IR → Glacier Flexible Retrieval or Deep Archive; Glacier Flexible Retrieval → Deep Archive only. Nothing moves back up by lifecycle; out of Deep Archive only by restore + copy.
- ⚠️ Changed: by default objects smaller than 128 KB aren't transitioned to any class (since September 2024); override per rule with an
ObjectSizeGreaterThan/LessThanfilter, or per configuration withx-amz-transition-default-minimum-object-size: varies_by_storage_class(lets small objects go to Glacier Flexible Retrieval and Deep Archive again; the default value isall_storage_classes_128K). Configurations created before September 2024 keep the old behaviour until a rule is added, edited or deleted. Older material may say objects under 128 KB are skipped only for transitions to the IA classes and are still archived to Glacier Flexible Retrieval or Deep Archive. - ⚠️ Changed: since 2026-07-16 there is no minimum object age before a lifecycle transition to Standard-IA or One Zone-IA:
Daysmay be 0 or any positive integer, and withDays: 0objects become eligible at the first midnight UTC after creation; the 30-day minimum storage charge in those classes still applies. Older material may say objects must stay 30 days in S3 Standard before a lifecycle rule can move them to an IA class. A single rule can't transition out of a class before its minimum duration (for example, Glacier IR at day 4 and Deep Archive before day 94). - Timing: days are counted from creation (or from when a version became noncurrent) and rounded up to the next midnight UTC; actions run asynchronously (possibly days later), but billing changes as soon as the rule is satisfied (except transitions into Intelligent-Tiering).
- Conflicts on the same day: permanent deletion beats transition, transition beats delete-marker creation, and Glacier Flexible Retrieval beats an IA transition.
- Versioned buckets:
Expirationonly adds delete markers, so object count rises; addNoncurrentVersionExpiration, thenExpiredObjectDeleteMarkerto clean up markers with no versions left. Expiration never removes incomplete multipart uploads; useAbortIncompleteMultipartUpload. - A bucket policy can't stop lifecycle deletions or transitions, not even a
Denyfor every principal; disable or delete the rule instead. Objects with replication statusPENDINGorFAILEDaren't transitioned or expired, and locked versions are never expired. Lifecycle actions run inside S3, so CloudTrail data events don't show them; track them with server access logs (S3.EXPIRE.OBJECT,S3.TRANSITION_SIA.OBJECT, …) or thes3:LifecycleTransitionands3:LifecycleExpiration:*event types. - Storage Class Analysis: recommendations only for Standard → Standard-IA (nothing for One Zone-IA or the Glacier classes); filters by prefix and/or tags (up to 1,000 per bucket); first charts in 24–48 hours, a result after about 30 days of observation; optional daily CSV export to a bucket in the same Region.
Developer angle
lifecycle.json for the CLI (JSON, not XML): tier and expire logs, cap noncurrent versions, clean up multipart leftovers and orphaned delete markers:
{
"Rules": [
{
"ID": "logs-tiering",
"Status": "Enabled",
"Filter": { "Prefix": "logs/" },
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" }
],
"Expiration": { "Days": 365 }
},
{
"ID": "versions-and-uploads",
"Status": "Enabled",
"Filter": { "Prefix": "" },
"NoncurrentVersionTransitions": [{ "NoncurrentDays": 30, "StorageClass": "GLACIER_IR" }],
"NoncurrentVersionExpiration": { "NoncurrentDays": 365, "NewerNoncurrentVersions": 3 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
},
{
"ID": "orphan-delete-markers",
"Status": "Enabled",
"Filter": { "Prefix": "" },
"Expiration": { "ExpiredObjectDeleteMarker": true }
}
]
}
aws s3api put-bucket-lifecycle-configuration --bucket app-data \
--lifecycle-configuration file://lifecycle.json
aws s3api get-bucket-lifecycle-configuration --bucket app-data
# Choose the class at upload time
aws s3 cp dataset.parquet s3://app-data/hot/dataset.parquet --storage-class INTELLIGENT_TIERING
# Restore an archived object for 3 days with the cheapest tier, then check progress
aws s3api restore-object --bucket app-data --key archive/2019/report.pdf \
--restore-request '{"Days":3,"GlacierJobParameters":{"Tier":"Bulk"}}'
aws s3api head-object --bucket app-data --key archive/2019/report.pdf --query Restore
# Activate the optional Intelligent-Tiering archive tiers
aws s3api put-bucket-intelligent-tiering-configuration --bucket app-data --id archive-cold \
--intelligent-tiering-configuration '{"Id":"archive-cold","Status":"Enabled","Tierings":[{"Days":90,"AccessTier":"ARCHIVE_ACCESS"},{"Days":180,"AccessTier":"DEEP_ARCHIVE_ACCESS"}]}'
Traps
- Trap: "Glacier Instant Retrieval objects must be restored first." → Reality: they are read with a normal GET in milliseconds; only Flexible Retrieval, Deep Archive and the Intelligent-Tiering archive tiers need
RestoreObject. - Trap: "Expedited retrieval works for Deep Archive." → Reality: Deep Archive offers only Standard (12 h) and Bulk (48 h).
- Trap: "One Zone-IA is fine for the only copy of critical data." → Reality: it lives in one AZ; use it for re-creatable data or secondary copies.
- Trap: "Lifecycle can move objects back to S3 Standard." → Reality: transitions only go down the waterfall; restore and copy to move up.
- Trap: "Expiring current versions in a versioned bucket frees space." → Reality: it adds delete markers; expire noncurrent versions too.
- Trap: "A lifecycle
Denyin the bucket policy protects objects from expiration." → Reality: lifecycle ignores bucket policies; change the rule. - Trap: "Every object will transition once the rule matches." → Reality: objects under 128 KB stay put by default since September 2024.
- Trap: "Calling
put-bucket-lifecycle-configurationwith one new rule adds it." → Reality: it replaces the whole configuration; send all rules.
Practice questions
-
An application stores scanned invoices that are read often during their first week and rarely afterwards, but when an auditor asks for one it must be returned within milliseconds. Invoices must be kept for 5 years at the lowest storage cost. Which lifecycle design meets these requirements?
- A. Transition objects to S3 Glacier Deep Archive after 7 days and expire them after 1,825 days.
- B. Transition objects to S3 One Zone-IA after 7 days and expire them after 1,825 days.
- C. Store objects in S3 Intelligent-Tiering with the Deep Archive Access tier activated after 180 days.
- D. Transition objects to S3 Glacier Instant Retrieval after 7 days and expire them after 1,825 days.
-
A versioning-enabled bucket has a single lifecycle rule that expires current versions 30 days after creation, yet the object count and the storage bill keep growing. Which TWO additions stop the growth? (Select TWO.)
- A. A
NoncurrentVersionExpirationaction that deletes noncurrent versions after a set number of days. - B. Suspending versioning on the bucket so that the existing expiration action deletes the objects permanently from now on.
- C. A bucket policy statement that denies
s3:PutObjectfor objects older than 30 days. - D. A change to the rule's filter from a key prefix to an object tag.
- E. A separate rule with
ExpiredObjectDeleteMarkerset totrue.
- A. A
Answers
- D. Glacier Instant Retrieval keeps millisecond access at archive storage prices after the busy first week. A needs a restore of up to 12 hours; B costs more per GB and isn't resilient to AZ loss; C's Deep Archive Access tier also needs a restore.
- A, E. Expirations in a versioned bucket only create delete markers, so the noncurrent versions must be expired and the leftover markers removed. B doesn't delete the versions already kept; C blocks new uploads instead of deleting anything; D changes which objects match but not what the rule does.
7. Event notifications and EventBridge
Concept
- S3 can push an event when something happens to an object (created, removed, restored, replicated, transitioned, tagged…) so that code reacts instead of polling: malware scans, search indexing, ETL steps, audit trails, media processing.
- Two delivery paths: classic event notifications straight to an SNS topic, SQS queue or Lambda function (filter by key prefix and suffix), or Amazon EventBridge, which receives every event from the bucket and lets rules filter on any field and route to many targets.
- S3 must be allowed to deliver: the destination's resource-based policy (topic policy, queue policy, Lambda permission) must trust
s3.amazonaws.comfor that bucket. - Delivery is at least once and not ordered, so consumers must be idempotent.
Exam facts
- Destinations: SNS standard topics, SQS standard queues, Lambda functions and EventBridge. SNS FIFO topics and SQS FIFO queues aren't supported as direct destinations; to reach an SQS FIFO queue route through EventBridge. Topic, queue and function must be in the same Region as the bucket. Each configuration has exactly one destination; up to 100 notification configurations per bucket.
- Event types:
s3:ObjectCreated:*(Put,Post,Copy,CompleteMultipartUpload),s3:ObjectRemoved:*(Delete,DeleteMarkerCreated),s3:ObjectRestore:*(Post,Completed,Delete),s3:Replication:*(failed, missed or after the 15-minute threshold, not tracked),s3:LifecycleExpiration:*,s3:LifecycleTransition,s3:IntelligentTiering,s3:ObjectTagging:*,s3:ObjectAcl:Put,s3:ObjectRetention:Put,s3:ReducedRedundancyLostObject.s3:ObjectRemoved:*doesn't fire for lifecycle deletions; subscribe tos3:LifecycleExpiration:*for those. - Filters: key prefix and suffix only (no wildcards, case-sensitive); spaces become
+and other special characters must be URL-encoded. Two configurations for the same event type may not have overlapping prefixes or suffixes (a configuration without a filter overlaps everything), so S3 rejects the second one as ambiguous. For several consumers of the same events, fan out through one SNS topic (seenotes/sqs-sns-kinesis-guide.md§8) or use EventBridge. - Delivery: latency is normally a few seconds, but don't build on a fixed bound (delays beyond a minute happen); at least once, occasionally duplicated, not guaranteed in order. The
sequencerfield orders events for the same key (compare after left-padding); it can't order events across keys. Event messages can be up to 64 KB (set an SQS queue'sMaximumMessageSizeto at least 64 KB). Object keys arrive URL-encoded (red flower.jpg→red+flower.jpg). Saving a configuration sends ans3:TestEventmessage that has noRecordsarray. - Permissions: Lambda needs a resource-based policy statement allowing
s3.amazonaws.comlambda:InvokeFunction(scoped withSourceArn= bucket ARN andSourceAccount), SNS a topic policy allowingSNS:Publish, SQS a queue policy allowingSQS:SendMessage, both conditioned onaws:SourceArnandaws:SourceAccount. If the topic or queue is encrypted with a customer managed KMS key, the key policy must lets3.amazonaws.comusekms:GenerateDataKeyandkms:Decrypt. When you save the configuration, S3 validates every destination (a test message for SNS and SQS, a permission check for Lambda), and the wholePutBucketNotificationConfigurationcall fails if one check fails; the error text commonly reads "Unable to validate the following destination configurations" (⚠️ unverified wording). Configuring notifications requiress3:PutBucketNotification, and the call replaces the bucket's whole notification configuration. - Recursive loops: a function that writes its output back into the bucket (and prefix) that triggers it invokes itself. Write to another bucket or prefix and filter the trigger; Lambda's recursive-loop detection covers S3 as a safety net (
notes/lambda-guide.md§12). S3 invokes Lambda asynchronously, so retries and destinations follow Lambda's async rules (notes/lambda-guide.md§2). - EventBridge integration: switch it on per bucket (
"EventBridgeConfiguration": {}); then all event types go to the default event bus with detail types such asObject Created,Object Deleted,Object Restore Completed,Object Storage Class Changed,Object Tags Added; S3 needs no extra permissions to deliver. Rules can match on any field (key prefix, suffix or wildcard, object size, reason, requester) and send each event to up to five targets per rule (SQS FIFO queues, API destinations, other event buses, state machines, data streams…); the bus adds archive and replay. Rule and target mechanics:notes/monitoring-guide.md§6. - Choose classic notifications for the simplest single consumer with prefix/suffix filtering; choose EventBridge for advanced filtering, many targets, FIFO queues, cross-account buses or replay.
Developer angle
{
"LambdaFunctionConfigurations": [{
"Id": "extract-pdf-text",
"LambdaFunctionArn": "arn:aws:lambda:eu-west-1:111122223333:function:extract-text",
"Events": ["s3:ObjectCreated:*"],
"Filter": { "Key": { "FilterRules": [
{ "Name": "prefix", "Value": "inbox/" },
{ "Name": "suffix", "Value": ".pdf" }
] } }
}],
"QueueConfigurations": [{
"Id": "audit-deletes",
"QueueArn": "arn:aws:sqs:eu-west-1:111122223333:object-deletes",
"Events": ["s3:ObjectRemoved:*"]
}],
"EventBridgeConfiguration": {}
}
aws lambda add-permission --function-name extract-text --statement-id s3-docs-in \
--action lambda:InvokeFunction --principal s3.amazonaws.com \
--source-arn arn:aws:s3:::docs-in --source-account 111122223333
aws s3api put-bucket-notification-configuration --bucket docs-in \
--notification-configuration file://notification.json
aws s3api get-bucket-notification-configuration --bucket docs-in
import urllib.parse
import boto3
s3 = boto3.client("s3")
def handler(event, context):
for record in event.get("Records", []): # the s3:TestEvent message has no Records
bucket = record["s3"]["bucket"]["name"]
key = urllib.parse.unquote_plus(record["s3"]["object"]["key"]) # keys arrive URL-encoded
if not key.startswith("inbox/"):
continue # never react to our own output
size = s3.head_object(Bucket=bucket, Key=key)["ContentLength"]
print(f"processing s3://{bucket}/{key} ({size} bytes)")
EventBridge rule pattern that matches only large uploads under one prefix:
{
"source": ["aws.s3"],
"detail-type": ["Object Created"],
"detail": {
"bucket": { "name": ["docs-in"] },
"object": {
"key": [{ "prefix": "inbox/" }],
"size": [{ "numeric": [">", 1048576] }]
}
}
}
Traps
- Trap: "Add a second notification with the same prefix for another consumer." → Reality: overlapping filters for the same event type are rejected; fan out with SNS or use EventBridge.
- Trap: "Point the notification at a FIFO queue to keep order." → Reality: FIFO queues and topics aren't direct destinations; use EventBridge and the
sequencerfield. - Trap: "Each event is delivered exactly once, in order." → Reality: at least once, possibly duplicated, unordered.
- Trap: "The Lambda execution role must allow S3 to invoke it." → Reality: invocation is granted in the function's resource-based policy, not its execution role.
- Trap: "
s3:ObjectRemoved:*reports lifecycle expirations." → Reality: uses3:LifecycleExpiration:*. - Trap: "Use the raw key from the event in
GetObject." → Reality: decode it first (unquote_plus), or keys with spaces fail withNoSuchKey. - Trap: "Write the processed file back next to the original." → Reality: if the trigger covers that prefix, the function triggers itself; separate buckets or prefixes.
Practice questions
-
A developer wants every
.csvfile uploaded underincoming/to be processed by two independent services: a Lambda function and an application that consumes an SQS queue. Creating a second event notification with the same prefix and suffix fails with an error about overlapping configurations. Which solution meets the requirements with the LEAST operational overhead?- A. Publish the notification to one SNS standard topic that both the function and the queue subscribe to.
- B. Change the Lambda function so that it also forwards a copy of every event it receives to the SQS queue.
- C. Replicate the objects to a second bucket so that each bucket triggers one of the consumers.
- D. Change the second configuration's suffix to
.CSVso that the two filters no longer overlap.
-
A developer configures an S3 event notification that invokes a Lambda function for
s3:ObjectCreated:*, butput-bucket-notification-configurationfails because S3 can't validate the Lambda destination. For another bucket, the team wants events delivered to an SQS FIFO queue to keep per-key ordering. Which TWO statements are correct? (Select TWO.)- A. The Lambda execution role must include the
s3:PutBucketNotificationpermission for the bucket before S3 can call it. - B. The function's resource-based policy must allow
s3.amazonaws.comto calllambda:InvokeFunctionfor that bucket. - C. The bucket must have versioning enabled before notifications can target Lambda.
- D. S3 can't notify an SQS FIFO queue directly, so the events should be routed through Amazon EventBridge.
- E. The SQS FIFO queue must be created in a different Region from the bucket.
- A. The Lambda execution role must include the
Answers
- A. One notification to an SNS topic fans the same event out to both subscribers with no code. B couples the services and adds code; C doubles storage and adds replication lag; D is case-sensitive, so lowercase
.csvfiles would reach only one consumer. - B, D. S3 validates that it can invoke the function, which the function's resource-based policy provides, and FIFO queues are reachable only through EventBridge. A puts a configuration permission in the wrong place (the caller needs it, not the function); C isn't a requirement; E is wrong because destinations must be in the bucket's Region.
8. Performance multipart uploads and Transfer Acceleration
Concept
- S3 scales request capacity per prefix, so throughput grows by spreading keys across prefixes and issuing requests in parallel; temporary
503 Slow Downresponses mean S3 is still scaling up. - Large objects move faster and more reliably as parallel parts: multipart upload for writes, byte-range GETs for reads. Only failed parts are retried.
- Transfer Acceleration lets distant clients hand their bytes to the closest CloudFront edge location; AWS carries them the rest of the way over its own optimized network.
- For latency below what S3 Standard offers (roughly 100–200 ms for small objects), cache in front (CloudFront, ElastiCache) or use S3 Express One Zone.
Exam facts
- Request rates: each partitioned prefix sustains 3,500 or more writes (PUT, COPY, POST, DELETE) and 5,500 or more reads (GET, HEAD) per second, and a bucket can hold any number of prefixes (10 prefixes ≈ 55,000 reads per second). Scaling is gradual; while it happens S3 can return
503 Slow Down, which SDKs retry with backoff. Requests made by replication on your behalf count too. Monitor 5xx with CloudWatch request metrics or server access logs. - Latency: consistent small-object (and first-byte) latency of roughly 100–200 ms; keep compute in the same Region as the bucket; SSE-KMS adds KMS quotas (§3).
- Multipart upload: recommended from 100 MB, required above 5 GB; up to 10,000 parts, part numbers 1–10,000, each part 5 MiB to 5 GiB except the last; maximum object 50 TB (48.8 TiB). Parts can be uploaded in any order and in parallel; re-uploading a part number overwrites it; the upload never expires: you must complete it (sending every part number with its ETag) or abort it, and until then the parts are billed. Add a lifecycle
AbortIncompleteMultipartUploadrule. Object metadata, tags, storage class and encryption are given when the upload is created. - Multipart permissions:
s3:PutObjectcovers create, upload part and complete;s3:AbortMultipartUpload,s3:ListMultipartUploadPartsands3:ListBucketMultipartUploadsare separate; SSE-KMS multipart needskms:GenerateDataKeyandkms:Decrypt.ListPartsandListMultipartUploadsreturn at most 1,000 entries per call. - Tooling:
aws s3 cp/syncswitch to multipart automatically (multipart_threshold8 MB,multipart_chunksize8 MB,max_concurrent_requests10 by default); SDK transfer managers (boto3upload_file, JavaS3TransferManager, the CRT-based client) parallelise parts and byte ranges; AWS recommends the Transfer Manager with the CRT for objects above 5 TB. - Byte-range fetches:
Range: bytes=start-endonGetObjectreturns only that slice, so code can split one big download across parallel connections, peek at a file header or footer without fetching the body, and resume just the slice that failed.GetObjectwithpartNumberreads one uploaded part, so align ranges with the original part size. - Transfer Acceleration: enable per bucket (
Enabled/Suspended), then use<bucket>.s3-accelerate.amazonaws.com(or.s3-accelerate.dualstack.amazonaws.com); virtual-hosted-style requests only; bucket name DNS-compliant without periods; up to 20 minutes after enabling before speeds improve; only the bucket owner (or someone granteds3:PutAccelerateConfiguration) can set it; works with multipart;CreateBucket,ListBucketsandDeleteBucketaren't sent to it; supported for buckets in a listed set of Regions (15 at the time of writing). Best for gigabytes to terabytes moved across continents or many global clients uploading to one bucket; useless for same-Region traffic. The Speed Comparison tool shows the gain per Region. - ⚠️ Changed: S3 Select is no longer available to new customers (since 2024-07-25); existing users can keep it. Older material may recommend S3 Select to filter CSV or JSON rows on the server. Alternatives: byte-range GETs, Amazon Athena, or filtering in the application.
- Other accelerators for read-heavy or latency-critical workloads: CloudFront in front of S3 (
notes/cloudfront-guide.md§1), ElastiCache for hot objects, S3 Express One Zone for single-digit-millisecond access in one AZ.
Developer angle
import boto3
from boto3.s3.transfer import TransferConfig
from botocore.config import Config
# Parallel multipart upload through the Transfer Acceleration endpoint
s3 = boto3.client("s3", config=Config(s3={"use_accelerate_endpoint": True}))
transfer = TransferConfig(multipart_threshold=100 * 1024 * 1024,
multipart_chunksize=64 * 1024 * 1024,
max_concurrency=10)
s3.upload_file("survey-2026.zip", "field-uploads", "asia/survey-2026.zip", Config=transfer)
# Read only the first KiB of the object (for example, a file header)
part = s3.get_object(Bucket="field-uploads", Key="asia/survey-2026.zip", Range="bytes=0-1023")
print(part["ContentRange"], len(part["Body"].read()))
aws s3api put-bucket-accelerate-configuration --bucket field-uploads \
--accelerate-configuration Status=Enabled
aws configure set default.s3.use_accelerate_endpoint true
aws configure set default.s3.multipart_chunksize 64MB
# The low-level multipart flow that the tools run for you
aws s3api create-multipart-upload --bucket field-uploads --key raw/big.bin # returns UploadId
aws s3api upload-part --bucket field-uploads --key raw/big.bin --part-number 1 \
--body part-001 --upload-id "$UPLOAD_ID" # returns ETag
aws s3api complete-multipart-upload --bucket field-uploads --key raw/big.bin \
--upload-id "$UPLOAD_ID" --multipart-upload file://parts.json
aws s3api list-multipart-uploads --bucket field-uploads # find leftovers
aws s3api abort-multipart-upload --bucket field-uploads --key raw/big.bin --upload-id "$UPLOAD_ID"
aws s3api get-object --bucket field-uploads --key raw/big.bin --range bytes=0-1023 first-kib.bin
parts.json lists every part number with the ETag returned by upload-part: {"Parts":[{"PartNumber":1,"ETag":"\"8a2a1f6c...\""}]}.
Traps
- Trap: "
503 Slow Downmeans the bucket is out of capacity for good." → Reality: S3 is scaling; retry with backoff and spread keys over more prefixes. - Trap: "Request limits apply per bucket." → Reality: they apply per partitioned prefix, with unlimited prefixes.
- Trap: "Transfer Acceleration speeds up an EC2 instance uploading to a bucket in its own Region." → Reality: it helps long distances only, and needs a bucket name without periods.
- Trap: "Multipart parts can be 1 MB each." → Reality: minimum 5 MiB per part (except the last), maximum 10,000 parts.
- Trap: "Aborted or abandoned uploads cost nothing." → Reality: uploaded parts are billed until the upload is completed or aborted; add
AbortIncompleteMultipartUpload. - Trap: "Use S3 Select to filter rows server-side in a new application." → Reality: closed to new customers; use byte ranges, Athena or application-side filtering.
- Trap: "Byte-range GETs need a special bucket setting." → Reality: any GET can send a
Rangeheader.
Practice questions
-
An analytics job running in the bucket's Region reads about 40,000 small objects per second, all stored under the single prefix
data/, and receives many HTTP503 Slow Downresponses. Which change most directly raises the sustainable read rate?- A. Enable S3 Transfer Acceleration on the bucket and send all reads through the
s3-accelerateendpoint instead. - B. Move the objects to the S3 Standard-IA storage class.
- C. Spread the objects across several prefixes such as
data/0/todata/9/and read them in parallel. - D. Increase the multipart upload part size that the writer uses to 100 MB.
- A. Enable S3 Transfer Acceleration on the bucket and send all reads through the
-
Field teams in Asia upload survey files of 2–20 GB to a bucket in us-east-1 over unreliable links. Uploads are slow and often restart from zero after a dropped connection. Which TWO changes improve speed and resilience? (Select TWO.)
- A. Use multipart upload (for example, the SDK transfer manager) so parts go in parallel and only failed parts are retried.
- B. Upload each file with a single
PutObjectcall to keep the number of requests low. - C. Create a new bucket whose name contains periods, so that it can match the company's domain name for uploads.
- D. Enable S3 Transfer Acceleration on the bucket and send uploads to the
s3-accelerateendpoint. - E. Store the files in S3 One Zone-IA so that S3 spends less time copying data across Availability Zones during the upload.
Answers
- C. About 5,500 GETs per second are supported per prefix, so 40,000 reads per second need around eight or more prefixes read in parallel. A only helps long-distance transfers; B changes price, not request capacity; D affects uploads, not reads.
- A, D. Multipart upload parallelises the transfer and retries only failed parts, and Transfer Acceleration shortens the long-distance path. B can't exceed 5 GB and restarts entirely on failure; C breaks Transfer Acceleration, which doesn't allow periods; E changes storage resilience, not upload speed.
9. Presigned URLs CORS and static websites
Concept
- A presigned URL is one S3 request (method, bucket, key, optional headers) signed in advance with the creator's credentials and carried in the query string. Whoever holds it can perform exactly that operation until it expires, without AWS credentials and without any bucket-policy change. It is the standard way to let browsers and mobile apps download private objects or upload directly to S3.
- CORS is a browser rule: JavaScript loaded from one origin may call another origin only if that other origin answers with the right
Access-Control-Allow-*headers. For S3, those answers come from the CORS configuration of the bucket being called. - Static website hosting turns a bucket into a public HTTP website with index and error documents; it needs public read access, which Block Public Access blocks by default.
Exam facts
- Creating presigned URLs: SDKs (any operation, for example
get_objectorput_objectin boto3), the CLI'saws s3 presign(GET only;--expires-indefault 3,600 s, maximum 604,800 s = 7 days) and the console (1 minute to 12 hours). - Lifetime is capped by the credentials: up to 7 days with SigV4 and IAM user keys; with temporary credentials the URL dies with them (an
AssumeRolesession defaults to 1 hour, EC2 instance-profile credentials last at most about 6 hours, ECS task credentials rotate within 1–6 hours). Deactivating or deleting the creator's key also kills its URLs. S3 checks expiry when a request starts, so a download that began in time finishes. Signing details:notes/cli-sdk-policies-guide.md§10. - Permissions: the URL carries the creator's permissions; if the creator can't do the operation (or an explicit deny applies), the URL returns
403. It is a bearer token, reusable until it expires: keep lifetimes short, hand it out over HTTPS. - Uploads: a presigned PUT targets one key and overwrites an existing object with that key; the bucket owner owns the result. The client must send the same signed headers (for example the
Content-Typeused when signing), otherwiseSignatureDoesNotMatch. For HTML form uploads, a presigned POST returns a URL plus form fields with a policy whose conditions can limit, for example, the size (content-length-range). - Errors:
AccessDeniedwith "Request has expired" (past the expiry),ExpiredToken(the temporary credentials behind the URL expired),SignatureDoesNotMatch(clock skew, a proxy that changed headers, a differentContent-Type, wrong Region). The CLI signs every presigned URL with SigV4, so the Region must be configured correctly. - Restricting them: a bucket policy can cap signature age with
s3:signatureAge(milliseconds);aws:SourceIporaws:SourceVpceconditions also apply to presigned requests. SSE-C objects need the key headers in addition to the URL, so they can be used only programmatically. - Typical uses: an authenticated customer fetches an invoice or report; a browser or mobile app uploads large files straight to S3 instead of through API Gateway (10 MB payload limit,
notes/api-gateway-guide.md§11) or Lambda; a partner gets a short-lived link. For CDN-cached content use CloudFront signed URLs or cookies instead (notes/cloudfront-guide.md§4). - CORS basics: an origin is scheme + host + port (
https://app.example.comandhttp://app.example.comdiffer). Before non-simple requests (for example a PUT withContent-Type: application/json) the browser sends a preflightOPTIONSwithOrigin,Access-Control-Request-MethodandAccess-Control-Request-Headers. S3 uses the firstCORSRulewhose origins, methods and headers all match and replies withAccess-Control-Allow-Origin,-Allow-Methods,-Allow-Headers,-Expose-Headersand-Max-Age; with no match, no CORS headers come back and the browser blocks the call. - CORS configuration: up to 100 rules;
AllowedOrigins(one*wildcard per string, or*for everyone),AllowedMethods(GET,PUT,POST,DELETE,HEAD),AllowedHeaders(one*per string, for examplex-amz-*),ExposeHeaders(response headers JavaScript may read, such asETagorx-amz-request-id),MaxAgeSeconds(how long the browser caches the preflight), optionalID. The console accepts JSON only; the CLI wraps the rules in{"CORSRules": [...]}. - Put the CORS rule on the bucket that receives the cross-origin request (the resource), not on the bucket or site that served the page. CORS never grants access: policies and signatures still decide.
- Website hosting: enable it with an index document (required, case-sensitive) and optionally an error document, routing rules or "redirect all requests" to another host; single objects can redirect through
x-amz-website-redirect-location. - Website endpoints:
http://<bucket>.s3-website-<region>.amazonaws.com(dash form) orhttp://<bucket>.s3-website.<region>.amazonaws.com(dot form), whichever the Region uses; HTTP only,GET/HEADonly, no access points, no Requester Pays buckets. For HTTPS or a custom certificate put CloudFront in front (and keep the bucket private behind origin access control,notes/cloudfront-guide.md§3); the website-endpoint docs now also list AWS Amplify Hosting as the recommended managed HTTPS option. - Making it public: turn off
BlockPublicPolicyandRestrictPublicBucketsat the bucket and account level, then add a bucket policy allowings3:GetObjectfor"Principal": "*"onarn:aws:s3:::<bucket>/*.403 Forbiddenfrom the website endpoint means objects aren't publicly readable (missing policy or BPA still on);404 Not Foundmeans the key or index document is missing. For a custom domain through a Route 53 alias record, the bucket name must equal the host name (www.example.com); periods are fine here because the website endpoint is HTTP only.
Developer angle
import boto3
s3 = boto3.client("s3", region_name="eu-west-1")
# Private download for 15 minutes
download_url = s3.generate_presigned_url(
"get_object", Params={"Bucket": "billing-docs", "Key": "invoices/2026/inv-1001.pdf"}, ExpiresIn=900)
# Direct upload of one key for 10 minutes; the client must send the same Content-Type
upload_url = s3.generate_presigned_url(
"put_object", ExpiresIn=600,
Params={"Bucket": "user-uploads", "Key": "uploads/u-42/avatar.png", "ContentType": "image/png"})
# Browser form upload limited to 20 MiB
form = s3.generate_presigned_post(
"user-uploads",
"uploads/u-42/report.pdf",
Conditions=[["content-length-range", 1, 20 * 1024 * 1024]],
ExpiresIn=600,
)
print(form["url"], form["fields"])
aws s3 presign s3://billing-docs/invoices/2026/inv-1001.pdf --expires-in 900
curl -X PUT -T avatar.png -H "Content-Type: image/png" "$UPLOAD_URL"
aws s3api put-bucket-cors --bucket user-uploads --cors-configuration file://cors.json
# Static website: index/error documents, relax BPA for policies, public-read policy
aws s3 website s3://www.example.com/ --index-document index.html --error-document error.html
aws s3api put-public-access-block --bucket www.example.com \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=false,RestrictPublicBuckets=false
aws s3api put-bucket-policy --bucket www.example.com --policy file://public-read.json
cors.json for a single-page app that uploads with presigned PUTs and needs the ETag:
{
"CORSRules": [{
"ID": "spa-uploads",
"AllowedOrigins": ["https://app.example.com"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["Content-Type", "x-amz-*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}]
}
public-read.json for the website bucket:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicReadForWebsite",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::www.example.com/*"
}]
}
Traps
- Trap: "Generate an upload URL with
aws s3 presign." → Reality: the CLI command signs GET only; use an SDK for PUT or POST. - Trap: "A presigned URL made by a Lambda function lasts the full 7 days." → Reality: it expires with the function's temporary role credentials.
- Trap: "The person using the URL needs S3 permissions." → Reality: only the creator's permissions matter.
- Trap: "A presigned URL works once." → Reality: it can be reused until it expires; keep it short-lived.
- Trap: "Add the CORS rule to the bucket that hosts the web page." → Reality: configure it on the bucket the JavaScript calls.
- Trap: "CORS settings make objects readable." → Reality: CORS only relaxes the browser check; authorization still comes from policies or signatures.
- Trap: "The website endpoint serves HTTPS." → Reality: HTTP only; use CloudFront for HTTPS.
- Trap: "A bucket policy is enough to publish a website." → Reality: Block Public Access must also allow public policies, otherwise the website returns
403.
Practice questions
-
A mobile app lets signed-in users upload profile videos of up to 2 GB directly to S3 without routing the bytes through API Gateway. Each upload must be possible for only 10 minutes and only to that user's own key. What should the backend return to the app?
- A. The access key of an IAM user that has
s3:PutObjectpermission on the bucket. - B. A presigned URL for
PutObjectonuploads/<user-id>/video.mp4that expires after 600 seconds. - C. A URL created with
aws s3 presign --expires-in 600for the user's key. - D. The bucket's static website endpoint, after enabling website hosting and adding a CORS rule that allows
PUT.
- A. The access key of an IAM user that has
-
A single-page app served from
https://app.example.comuploads files with JavaScript straight tohttps://media-bucket.s3.eu-west-1.amazonaws.comusing presigned PUT URLs. The browser reports that the preflight request was rejected, and the app also needs to read theETagresponse header after each upload. Which TWO configuration items fix this? (Select TWO.)- A. A CORS rule on the bucket that hosts the single-page app, allowing
PUTrequests fromhttps://app.example.com. - B. Turning off Block Public Access on
media-bucket. - C. A CORS rule on
media-bucketthat allowsPUTand the app's headers fromhttps://app.example.com. - D. Enabling static website hosting on
media-bucket. - E.
ETaglisted in theExposeHeaderselement of that CORS rule.
- A. A CORS rule on the bucket that hosts the single-page app, allowing
Answers
- B. A presigned PUT limits the upload to one key and 10 minutes while the backend keeps its own credentials. A hands out long-term credentials (least secure); C produces a GET URL, so uploads fail; D's website endpoint doesn't accept uploads and would need a public bucket.
- C, E. The bucket being called must answer the preflight for that origin, method and headers, and
ETagmust be exposed for JavaScript to read it. A is the wrong bucket; B is unrelated, because the presigned signature authorizes the request; D serves public HTTP pages and has nothing to do with CORS for the REST endpoint.
10. Access Points and Object Lambda
Concept
- An access point is a named network endpoint attached to one bucket (or an FSx volume) with its own policy, own Block Public Access settings and own network origin (internet or one VPC). Instead of one huge bucket policy for every application, each application or team gets its own access point with a small, focused policy, and a VPC-only access point keeps an application's traffic private.
- The bucket policy usually just delegates to the account's access points, so day-to-day permissions live on the access points.
- An Object Lambda Access Point puts a Lambda function in the read path: GET, HEAD and LIST responses pass through your code (redact, convert, resize), so one stored copy can serve many views. Its availability changed in 2025.
Exam facts
- Identity: names 3–50 characters, lowercase, unique only per account and Region (not globally). ARN
arn:aws:s3:<region>:<account>:accesspoint/<name>; hostname<name>-<account>.s3-accesspoint.<region>.amazonaws.com; an automatically generated alias ending in-s3aliascan be used wherever a bucket name is expected. Most SDK and CLI object calls accept the access point ARN as theBucketparameter. - Limits: up to 10,000 access points per account per Region (adjustable); access point policy ≤ 20 KB; each access point is tied to exactly one bucket for life; network origin (VPC or internet) and BPA settings are fixed at creation.
- Restrictions: object operations only (no bucket management such as lifecycle or replication configuration, and an access point can't be a replication destination); HTTPS only, SigV4 only, no anonymous access; virtual-hosted-style addressing; website endpoints don't support access points; management APIs such as
PutAccessPointPolicycan't be called cross-account. - Two policies must agree: a request through an access point needs an Allow in the access point policy and in the bucket policy. Recommended: a bucket policy that allows everything for access points owned by your account (
s3:DataAccessPointAccount= your account ID), then grant per access point. Object resources in access point policies look likearn:aws:s3:<region>:<account>:accesspoint/<name>/object/<prefix>*. - Condition keys:
s3:DataAccessPointArn,s3:DataAccessPointAccount,s3:AccessPointNetworkOrigin(VPCorInternet); for example an SCP can denys3:CreateAccessPointunless the network origin isVPC, and a bucket policy can deny object access unless it arrives through a VPC-origin access point. - VPC origin: S3 rejects any request through the access point that doesn't come from the named VPC; the traffic flows through a VPC endpoint for S3 whose endpoint policy must allow both the access point and the bucket. A VPC-origin access point always counts as non-public.
- Cross-account access points: another account can create an access point for your bucket, but it grants nothing until the bucket owner's policy authorizes it.
- Object Lambda Access Point: created on top of a standard (supporting) access point; the function receives
getObjectContextwithinputS3Url(a presigned URL to the original),outputRouteandoutputToken, and must return data by callingWriteGetObjectResponse(execution-role permissions3-object-lambda:WriteGetObjectResponse, included in the managed policyAmazonS3ObjectLambdaExecutionRolePolicy) within 60 seconds; if the function ends without that call, the caller gets500. HEAD and LIST transformations return JSON instead.RangeandpartNumberrequests are rejected with501unless enabled in the configuration and handled by the function. Callers needs3-object-lambda:Get*on the Object Lambda Access Point (plus permissions on the supporting access point andlambda:InvokeFunctionviaaws:CalledVia). - ⚠️ Changed: S3 Object Lambda is available only to existing customers and selected APN partners since 2025-11-07; no new capabilities are planned. Older material may present it as the default way to transform objects on read. Alternatives that keep one stored copy: a Lambda function invoked through a function URL or API Gateway (optionally behind CloudFront), the Dynamic Image Transformation for Amazon CloudFront solution, or processing in the client.
- Typical jobs for either design: stripping card numbers from exported orders, decompressing archives on the fly, turning CSV files into JSON lines, or filtering out rows a caller isn't entitled to see.
Developer angle
# One access point per consumer; one restricted to a VPC
aws s3control create-access-point --account-id 111122223333 --name billing-ap \
--bucket data-lake-prod
aws s3control create-access-point --account-id 111122223333 --name reporting-vpc-ap \
--bucket data-lake-prod --vpc-configuration VpcId=vpc-0a1b2c3d4e5f67890
aws s3control put-access-point-policy --account-id 111122223333 --name billing-ap \
--policy file://billing-ap-policy.json
aws s3control get-access-point --account-id 111122223333 --name reporting-vpc-ap
# Object calls through the access point: pass its ARN (or alias) as the bucket
aws s3api get-object --bucket arn:aws:s3:eu-west-1:111122223333:accesspoint/billing-ap \
--key billing/2026/q3.csv q3.csv
billing-ap-policy.json (access point side):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:role/billing-app" },
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:eu-west-1:111122223333:accesspoint/billing-ap/object/billing/*"
}]
}
Bucket policy that delegates access control to the account's own access points:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DelegateToOwnAccessPoints",
"Effect": "Allow",
"Principal": { "AWS": "*" },
"Action": "*",
"Resource": ["arn:aws:s3:::data-lake-prod", "arn:aws:s3:::data-lake-prod/*"],
"Condition": { "StringEquals": { "s3:DataAccessPointAccount": "111122223333" } }
}]
}
Object Lambda function shape (for accounts that still use the feature):
import urllib.request
import boto3
s3 = boto3.client("s3")
def handler(event, context):
ctx = event["getObjectContext"]
with urllib.request.urlopen(ctx["inputS3Url"]) as resp: # presigned URL to the original object
original = resp.read().decode("utf-8")
redacted = original.replace("@", "[at]") # stand-in for real PII redaction
s3.write_get_object_response(RequestRoute=ctx["outputRoute"],
RequestToken=ctx["outputToken"],
Body=redacted.encode("utf-8"))
return {"statusCode": 200}
Traps
- Trap: "The access point policy alone grants access." → Reality: the bucket policy must allow it too; delegate with
s3:DataAccessPointAccount. - Trap: "Switch an existing access point from internet to VPC origin." → Reality: network origin and BPA are fixed at creation; create a new access point.
- Trap: "Configure lifecycle or replication through an access point." → Reality: access points serve object operations only.
- Trap: "Access point names must be globally unique like bucket names." → Reality: unique per account and Region.
- Trap: "A VPC-origin access point works from any network with valid credentials." → Reality: only from that VPC, through an endpoint whose policy allows both the access point and the bucket.
- Trap: "Object Lambda modifies the stored object." → Reality: it changes only the response; the original stays as is.
- Trap: "The function can simply return the transformed body." → Reality: it must call
WriteGetObjectResponsewith the route and token, or the caller gets500. - Trap: "Recommend S3 Object Lambda for a new account's on-read transformation." → Reality: it is closed to new customers since 2025-11-07; use Lambda behind a function URL, API Gateway or CloudFront.
Practice questions
-
A data lake bucket is shared by 40 teams and its bucket policy has reached the 20 KB size limit. Each team needs its own permissions on its own prefix, and one team must be able to access data only from its VPC. Which solution scales with the LEAST operational overhead?
- A. Create one access point per team with its own policy and a VPC origin for the restricted team, delegating from the bucket policy.
- B. Create a separate bucket for each team and use S3 Replication to copy the shared datasets into every team's bucket.
- C. Replace the bucket policy with object ACLs that grant each team access to its prefix.
- D. Create an IAM user for each team, attach a prefix-scoped policy, and keep the access keys in AWS Secrets Manager.
-
A company's customer-support tool must show order records with card numbers masked, while S3 keeps only the original, unmasked files. The account has never used S3 Object Lambda. Which TWO approaches keep a single stored copy? (Select TWO.)
- A. Create an S3 Object Lambda Access Point backed by a supporting access point.
- B. Invoke a Lambda function through a function URL or API Gateway that returns the masked record.
- C. Store a masked second copy of each file in another bucket and keep it in sync with S3 Same-Region Replication.
- D. Use S3 Select to drop the card-number fields on the server side.
- E. Have the support tool download the original object and mask the field in its own code before display.
Answers
- A. Access points give each team a small policy of its own, a VPC origin restricts one of them, and the bucket policy shrinks to a single delegation statement. B multiplies storage and pipelines; C relies on ACLs, which are disabled by default and can't express VPC restrictions; D swaps roles for long-term keys and solves nothing about policy size.
- B, E. Calling Lambda through another front door or masking in the client both keep one stored copy and are available to new customers. A is closed to new customers since November 2025; C stores a second copy; D is no longer available to new customers either.
Quick decision rules
Objects, access and encryption (§1–§3)
- If you see "prevent two writers from overwriting the same key" without new services, think conditional write
If-None-Match: *(412 on conflict). - If you see "change metadata of existing objects", think
CopyObjectonto itself withMetadataDirective=REPLACE. - If you see "search objects by tag or metadata", think S3 Metadata tables with Athena, or an event-driven DynamoDB index.
- If you see
403on a key that may not exist, think missings3:ListBucket(404 needs it). - If you see cross-account
AccessDeniedalthough the caller's IAM policy allows it, think missing bucket policy (and KMS key policy for SSE-KMS). - If you see
AccessControlListNotSupported, think ACLs disabled (Bucket owner enforced): drop the ACL or usebucket-owner-full-control. - If you see "make sure no bucket in the account can ever become public", think account-level Block Public Access (organization-level for many accounts).
- If you see "force HTTPS", think Deny with
aws:SecureTransport=false. - If you see "audit every use of the encryption key" or "control the key policy", think SSE-KMS with a customer managed key.
- If you see "two layers of encryption for compliance", think DSSE-KMS (no Bucket Keys).
- If you see "keys must stay outside AWS" with server-side encryption, think SSE-C over HTTPS (unblock it with
BlockedEncryptionTypes). - If you see KMS
ThrottlingExceptionon a busy SSE-KMS bucket, think S3 Bucket Keys (then a quota increase). - If you see "move existing SSE-S3 objects to a KMS key without rewriting them", think
UpdateObjectEncryption(a Batch Operations job for many objects).
- If you see "recover from accidental deletes or overwrites", think versioning (remove the delete marker or copy an old version).
- If you see "no one, not even root, may delete for N years", think Object Lock compliance mode.
- If you see "only specially authorized users may remove protection", think Object Lock governance mode with
s3:BypassGovernanceRetention. - If you see "require MFA to permanently delete versions", think MFA Delete (root, CLI only).
- If you see "existing objects didn't replicate", think S3 Batch Replication.
- If you see SSE-KMS objects not replicating, think
SseKmsEncryptedObjectsopt-in plusReplicaKmsKeyIDand KMS permissions. - If you see "replicate within 15 minutes with an SLA", think S3 Replication Time Control.
Classes, events and speed (§6–§8)
- If you see "unknown or changing access pattern", think S3 Intelligent-Tiering.
- If you see "archive but millisecond access", think S3 Glacier Instant Retrieval.
- If you see "cheapest archive, hours are fine", think Glacier Deep Archive (12 h Standard, 48 h Bulk).
- If you see "storage keeps growing in a versioned bucket", think
NoncurrentVersionExpirationplusExpiredObjectDeleteMarker. - If you see "abandoned multipart uploads cost money", think lifecycle
AbortIncompleteMultipartUpload. - If you see several consumers for the same S3 event, think SNS fan-out or EventBridge.
- If you see an SQS FIFO queue or advanced filtering for S3 events, think EventBridge.
- If you see a Lambda function triggering itself, think separate output bucket or prefix.
- If you see
503 Slow Down, think more prefixes plus retries with backoff. - If you see large uploads over long distances, think multipart upload plus Transfer Acceleration.
- If you see "read only part of a large object", think a byte-range GET.
Browser access and shared buckets (§9–§10)
- If you see "let a user upload or download one private object temporarily", think a presigned URL (SDK for PUT).
- If you see a browser preflight error calling S3, think CORS on the bucket being called.
- If you see "HTTPS for an S3 static website", think CloudFront in front of the bucket.
- If you see "many teams, one bucket, policy too big", think S3 access points with a delegating bucket policy.
- If you see "transform objects on read" in a new account, think Lambda behind a function URL, API Gateway or CloudFront (Object Lambda is closed to new customers).
Changed vs older material
Learn the Current column; older practice material may still show the Older material may say value. Rows enter notes/current-facts.md only after you confirm them; the rows marked pre-verified are already there and were re-checked on 2026-10-12.
| Topic | Older material may say | Current (verified 2026-10-12) | Source |
|---|---|---|---|
| §1 · Maximum object size | 5 TB | 50 TB (48.8 TiB) in all Regions and storage classes since 2025-12-02; single PUT still 5 GB | What's New |
| §1 · Buckets per account | 100 (soft limit) | 10,000 by default, raisable to 1,000,000 (2024-11-14) | What's New |
| §1 · Bucket naming rules | Only xn-- prefix and -s3alias suffix are reserved |
Also reserved: sthree-, amzn-s3-demo- prefixes and --ol-s3, .mrap, --x-s3, --table-s3 suffixes; must begin and end with a letter or digit; optional account regional namespace (-an suffix) |
AWS docs |
| §1 · Conditional writes (pre-verified) | Not supported; last writer wins | If-None-Match: * (2024-08-20) and If-Match (2024-11-25) on PutObject, CompleteMultipartUpload; on CopyObject (destination key) since 2025-10-29; 412 on failure; s3:if-none-match / s3:if-match |
AWS docs · What's New |
| §1 · Conditional deletes (pre-verified) | Deletes are unconditional | DeleteObject / DeleteObjects with If-Match on general purpose buckets since 2025-09-16; s3:if-match |
What's New |
| §1 · Searching object metadata | Not possible in S3; build an index (for example in DynamoDB) | S3 Metadata (GA 2025-01-27) keeps queryable Iceberg metadata tables (Athena); an own index is still valid | AWS docs |
| §2 · Object Ownership / ACLs (pre-verified) | ACLs enabled (Object writer) for buckets created by API, CLI or CloudFormation | Bucket owner enforced (ACLs disabled) for all new buckets since April 2023; other ACLs fail with 400 AccessControlListNotSupported |
AWS docs |
| §2 · Block Public Access default (pre-verified) | On by default only for console-created buckets | All four settings on for every new bucket, whatever the creation method, since April 2023; existing buckets unchanged | What's New |
| §2 · Block Public Access levels | Bucket and account level | Also organization level (AWS Organizations S3 policy, all four settings together); most restrictive level wins | AWS docs |
| §2 · Server access log destinations | An S3 bucket in the same Region | An S3 bucket in the same Region and account, or CloudWatch Logs (structured, cross-account/Region, KMS) | AWS docs |
| §3 · Default encryption (pre-verified) | Objects unencrypted unless default encryption or headers are set | SSE-S3 on all new uploads in every bucket since 2023-01-05, free, can't be disabled; existing objects unchanged | AWS docs |
| §3 · Number of encryption methods | Four (SSE-S3, SSE-KMS, SSE-C, client-side) | Five: DSSE-KMS added 2023-06-13 (aws:kms:dsse) |
AWS docs |
| §3 · KMS request quota hit by SSE-KMS | 5,500 / 10,000 / 30,000 requests per second by Region | 10,000 default, 20,000 in seven Regions, 100,000 in us-east-1, us-west-2, eu-west-1 (shared symmetric quota) | AWS docs |
| §3 · SSE-C availability (pre-verified) | Usable on any bucket over HTTPS | Blocked by default since April 2026 for new buckets (except Bahrain, UAE) and accounts without SSE-C objects; enable with BlockedEncryptionTypes = NONE; otherwise 403 |
AWS docs |
| §3 · Re-encrypting existing objects | Always rewrite them with CopyObject or Batch Operations Copy |
UpdateObjectEncryption switches SSE-S3/SSE-KMS objects to SSE-KMS (new key or Bucket Key) in place, keeping ETag and Last-Modified; unencrypted, SSE-C and DSSE-KMS objects still need a copy |
AWS docs |
| §4 · Object Lock on existing buckets | Only at bucket creation | Can be enabled on existing versioned buckets since 2023-11-20; can't be disabled afterwards | AWS docs |
| §5 · Replication of SSE-C objects | SSE-C objects aren't replicated | Replicated like unencrypted and SSE-S3 objects since 2022-10-24 | AWS docs |
| §5 · Replication Time Control SLA | 99.99 % of objects within 15 minutes (SLA) | SLA covers 99.9 % within 15 minutes per Region pair per month (credits below 99.9 %); 99.99 % is the design figure on the overview page | AWS SLA |
| §6 · Storage class list | Seven classes | Eight: S3 Express One Zone (2023-11-28, directory buckets, single AZ) | AWS docs |
| §6 · Glacier Flexible/Deep Archive minimum billable size | 40 KB | No minimum billable size; 40 KB metadata overhead per archived object (8 KB at Standard + 32 KB at the archive rate) | AWS docs |
| §6 · Intelligent-Tiering archive thresholds | "700+ days" | Archive Access 90–730 days, Deep Archive Access 180–730 days | AWS docs |
| §6 · Small objects in lifecycle transitions | Objects < 128 KB skip only IA transitions and still go to Glacier Flexible Retrieval / Deep Archive | Objects < 128 KB aren't transitioned to any class by default since September 2024 (older configurations keep the old behaviour until edited); override with size filters or varies_by_storage_class |
AWS docs |
| §6 · Wait before transitions to Standard-IA / One Zone-IA | 30 days in S3 Standard first | No minimum age since 2026-07-16 (Days 0 or more; with 0, objects are eligible at the first midnight UTC after creation); the 30-day minimum storage charge still applies |
What's New · AWS docs |
| §8 · S3 Select | Filter rows server-side with SQL | No longer available to new customers since 2024-07-25 | AWS docs |
| §10 · S3 Object Lambda | Available to any account | Existing customers and selected APN partners only since 2025-11-07; no new capabilities | AWS docs |