Home/Blog/AWS S3 Security Best Practices: Encryption, Access Control & Compliance
Cloud & DevOps

AWS S3 Security Best Practices: Encryption, Access Control & Compliance

Secure your AWS S3 buckets with this comprehensive guide covering encryption options, IAM and bucket policies, Block Public Access, VPC endpoints, and compliance configurations.

By Inventive HQ Team
AWS S3 Security Best Practices: Encryption, Access Control & Compliance

S3 data breaches consistently make headlines—not because S3 is insecure, but because it's misconfigured. A single overly-permissive bucket policy can expose millions of records. This guide covers the security controls that prevent breaches and meet compliance requirements.

S3 Security Model

S3 security operates on multiple layers that work together:

┌─────────────────────────────────────────────────────────────────────────┐
│                        S3 SECURITY LAYERS                                │
└─────────────────────────────────────────────────────────────────────────┘
                                      │
     ┌────────────────────────────────┼────────────────────────────────┐
     │                                │                                │
     ▼                                ▼                                ▼
┌──────────────┐             ┌──────────────┐             ┌──────────────┐
│   NETWORK    │             │   ACCESS     │             │    DATA      │
│   CONTROLS   │             │   CONTROLS   │             │  PROTECTION  │
├──────────────┤             ├──────────────┤             ├──────────────┤
│ • VPC Endpts │             │ • Block Publ │             │ • Encryption │
│ • S3 Access  │             │ • IAM Policy │             │ • Versioning │
│   Points     │             │ • Bucket Pol │             │ • Object Lock│
│ • Firewall   │             │ • ACLs       │             │ • MFA Delete │
└──────────────┘             └──────────────┘             └──────────────┘
                                      │
                                      ▼
                          ┌──────────────────┐
                          │    MONITORING    │
                          ├──────────────────┤
                          │ • Access Logging │
                          │ • CloudTrail     │
                          │ • S3 Analytics   │
                          │ • Access Analyzer│
                          └──────────────────┘

Block Public Access

The first line of defense is S3 Block Public Access, which prevents buckets from being made public—even if a bucket policy or ACL attempts to grant public access.

Account-Level Settings

Apply to all buckets in the account:

# Enable all Block Public Access settings at account level
aws s3control put-public-access-block \
  --account-id 123456789012 \
  --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

Bucket-Level Settings

Apply to individual buckets:

# Enable Block Public Access on a bucket
aws s3api put-public-access-block \
  --bucket my-bucket \
  --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

# Verify settings
aws s3api get-public-access-block --bucket my-bucket

What Each Setting Does

SettingEffect
BlockPublicAclsReject PUT requests that include public ACLs
IgnorePublicAclsIgnore all public ACLs on bucket and objects
BlockPublicPolicyReject bucket policies that grant public access
RestrictPublicBucketsRestrict access to bucket with public policies to authorized users only

Recommendation: Enable all four settings at the account level unless you have a specific need for public buckets (like static website hosting).

Encryption Options

All data in S3 should be encrypted at rest. S3 offers four encryption methods:

Server-Side Encryption with S3-Managed Keys (SSE-S3)

AWS manages the encryption keys entirely. Simplest option with no additional cost.

# Upload with SSE-S3
aws s3 cp file.txt s3://my-bucket/ --sse AES256

# Enable default encryption on bucket
aws s3api put-bucket-encryption \
  --bucket my-bucket \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "AES256"
      },
      "BucketKeyEnabled": true
    }]
  }'

Server-Side Encryption with KMS (SSE-KMS)

AWS KMS manages keys with additional features: audit trails, key policies, automatic rotation.

# Upload with SSE-KMS
aws s3 cp file.txt s3://my-bucket/ \
  --sse aws:kms \
  --sse-kms-key-id alias/my-s3-key

# Enable default KMS encryption on bucket
aws s3api put-bucket-encryption \
  --bucket my-bucket \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
      },
      "BucketKeyEnabled": true
    }]
  }'

Bucket Keys: Enable BucketKeyEnabled to reduce KMS API calls and costs by using a bucket-level key derived from your KMS key.

Server-Side Encryption with Customer Keys (SSE-C)

You provide and manage encryption keys. S3 encrypts/decrypts but doesn't store your key.

# Generate a 256-bit key
KEY=$(openssl rand -base64 32)

# Upload with customer key
aws s3 cp file.txt s3://my-bucket/file.txt \
  --sse-c \
  --sse-c-key "$KEY"

# Download requires same key
aws s3 cp s3://my-bucket/file.txt ./file.txt \
  --sse-c \
  --sse-c-key "$KEY"

Warning: If you lose the key, you cannot decrypt the data. S3 doesn't store customer-provided keys.

Client-Side Encryption

Encrypt data before uploading. Provides end-to-end encryption where data is never decrypted on AWS.

# Encrypt locally with OpenSSL
openssl enc -aes-256-cbc -salt -in file.txt -out file.txt.enc -pass pass:$PASSWORD

# Upload encrypted file
aws s3 cp file.txt.enc s3://my-bucket/

# Download and decrypt
aws s3 cp s3://my-bucket/file.txt.enc ./
openssl enc -aes-256-cbc -d -in file.txt.enc -out file.txt -pass pass:$PASSWORD

Encryption Decision Matrix

RequirementRecommended Encryption
Default protection, no extra costSSE-S3
Audit trail of key accessSSE-KMS
Key rotation policiesSSE-KMS
Separate key permissionsSSE-KMS
You must control keysSSE-C
Regulatory requirement for client-sideClient-side
Cross-account sharing with encryptionSSE-KMS with key policy

Access Control Methods

S3 offers multiple access control mechanisms. Use them in combination:

IAM Policies

Attach to IAM users, groups, or roles. Define what actions the principal can perform on which resources.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowS3BucketAccess",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ]
    }
  ]
}

Bucket Policies

Attach directly to buckets. Best for cross-account access and resource-based conditions.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCrossAccountAccess",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::999888777666:root"
      },
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ]
    }
  ]
}

Common Bucket Policy Patterns

Deny unencrypted uploads:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyUnencryptedUploads",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::my-bucket/*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": ["AES256", "aws:kms"]
        }
      }
    }
  ]
}

Restrict access to specific VPC:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RestrictToVPC",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:SourceVpce": "vpce-1234567890abcdef0"
        }
      }
    }
  ]
}

Restrict to specific IP addresses:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RestrictByIP",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ],
      "Condition": {
        "NotIpAddress": {
          "aws:SourceIp": [
            "192.168.1.0/24",
            "10.0.0.0/8"
          ]
        }
      }
    }
  ]
}

Enforce HTTPS only:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EnforceHTTPS",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}

VPC Endpoints for Private Access

VPC endpoints allow EC2 instances to access S3 without traversing the public internet.

Gateway Endpoints (Free)

Route S3 traffic through AWS network:

# Create VPC endpoint
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-12345678 \
  --service-name com.amazonaws.us-east-1.s3 \
  --route-table-ids rtb-12345678

# The route table automatically gets a route to S3 via the endpoint

Create an ENI in your VPC for S3 access:

# Create interface endpoint
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-12345678 \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.us-east-1.s3 \
  --subnet-ids subnet-12345678 \
  --security-group-ids sg-12345678

Use gateway endpoints for most cases (free, simpler). Use interface endpoints when you need:

  • On-premises access via Direct Connect/VPN
  • Cross-region access
  • Specific IP addresses for firewall rules

Logging and Monitoring

S3 Server Access Logging

Logs all requests to your bucket:

# Create logging bucket
aws s3 mb s3://my-access-logs-bucket

# Grant S3 permission to write logs
aws s3api put-bucket-acl \
  --bucket my-access-logs-bucket \
  --grant-write URI=http://acs.amazonaws.com/groups/s3/LogDelivery \
  --grant-read-acp URI=http://acs.amazonaws.com/groups/s3/LogDelivery

# Enable logging
aws s3api put-bucket-logging \
  --bucket my-bucket \
  --bucket-logging-status '{
    "LoggingEnabled": {
      "TargetBucket": "my-access-logs-bucket",
      "TargetPrefix": "my-bucket/"
    }
  }'

CloudTrail for API Auditing

Enable CloudTrail data events for S3:

aws cloudtrail put-event-selectors \
  --trail-name my-trail \
  --event-selectors '[{
    "ReadWriteType": "All",
    "DataResources": [{
      "Type": "AWS::S3::Object",
      "Values": ["arn:aws:s3:::my-bucket/"]
    }]
  }]'

S3 Access Analyzer

Identifies buckets with public or cross-account access:

# Create analyzer
aws accessanalyzer create-analyzer \
  --analyzer-name my-s3-analyzer \
  --type ACCOUNT

# List findings
aws accessanalyzer list-findings --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/my-s3-analyzer

Object Lock for Immutability

Object Lock prevents objects from being deleted or overwritten for a retention period—essential for compliance and ransomware protection.

Enable Object Lock

Must be enabled when creating the bucket:

# Create bucket with Object Lock
aws s3api create-bucket \
  --bucket compliance-bucket \
  --object-lock-enabled-for-bucket

# Set default retention
aws s3api put-object-lock-configuration \
  --bucket compliance-bucket \
  --object-lock-configuration '{
    "ObjectLockEnabled": "Enabled",
    "Rule": {
      "DefaultRetention": {
        "Mode": "COMPLIANCE",
        "Years": 7
      }
    }
  }'

Lock Modes

ModeCan Override?Use Case
GovernanceYes, with s3:BypassGovernanceRetention permissionTesting, flexible retention
ComplianceNo, not even root accountRegulatory (SEC 17a-4, HIPAA), ransomware protection

Indefinite lock independent of retention period:

# Apply legal hold
aws s3api put-object-legal-hold \
  --bucket my-bucket \
  --key evidence/document.pdf \
  --legal-hold Status=ON

# Remove legal hold
aws s3api put-object-legal-hold \
  --bucket my-bucket \
  --key evidence/document.pdf \
  --legal-hold Status=OFF

Security Checklist

Account-Level

  • Enable Block Public Access at account level
  • Enable S3 Access Analyzer
  • Enable CloudTrail for management events
  • Configure AWS Config rules for S3 compliance

Bucket-Level

  • Enable Block Public Access on each bucket
  • Enable default encryption (SSE-S3 or SSE-KMS)
  • Configure bucket policy with least privilege
  • Enable versioning for critical data
  • Enable access logging
  • Disable ACLs (use policies instead)

Sensitive Buckets

  • Enable Object Lock (compliance mode)
  • Enable MFA Delete
  • Restrict access via VPC endpoint
  • Enable CloudTrail data events
  • Use SSE-KMS for encryption key auditing

Monitoring

  • Review S3 Access Analyzer findings weekly
  • Alert on public bucket creation
  • Monitor for unusual access patterns
  • Audit bucket policies quarterly

Conclusion

S3 security is not a single setting—it's a combination of network controls, access policies, encryption, and monitoring working together. The most common breaches result from:

  1. Misconfigured bucket policies — Always test with least privilege
  2. Disabled Block Public Access — Keep it enabled unless absolutely necessary
  3. No encryption — Enable default encryption on all buckets
  4. No monitoring — You can't protect what you can't see

Start with the account-level settings (Block Public Access, Access Analyzer), then apply bucket-specific controls based on data sensitivity.

For complete S3 coverage, see our S3 Complete Guide. For protecting against data loss, see Versioning and Replication Guide. For building secure CLI commands, use our AWS S3 Command Generator.

Frequently Asked Questions

Find answers to common questions

Enable S3 Block Public Access at both the account and bucket level. This is now enabled by default for new buckets. At the account level, go to S3 console → Block Public Access settings for this account → Enable all four settings. This prevents any bucket policy or ACL from granting public access, even if misconfigured.

Let's turn this knowledge into action

Get a free 30-minute consultation with our experts. We'll help you apply these insights to your specific situation.