How to Back Up Postgres to AWS S3 With Encryption

A nightly Postgres backup should never touch local disk - a temp file forces you to size /tmp to your largest database and leaves a half-written object if the process dies. Stream it instead: pg_dump app | gzip | aws s3 cp - s3://db-backups/app-$(date +%s).sql.gz, and add --sse aws:kms so the dump is encrypted at rest the moment it lands. Here is the full pipeline.

DevOps Engineerawss3postgres

Stream the dump - no temp file

The naive backup writes a dump to disk, then uploads it:

pg_dump -U postgres app | gzip > /tmp/app.sql.gz
aws s3 cp /tmp/app.sql.gz s3://db-backups/app.sql.gz

That forces you to size /tmp to your largest database, fails outright when the disk fills, and leaves a half-written file if the process dies mid-run. The fix is a single streaming pipeline - aws s3 cp - reads the upload body from stdin, so the dump flows straight into S3 as pg_dump produces it:

#!/bin/bash
set -euo pipefail
TS=$(date +%s)
KEY="app-${TS}.sql.gz"
pg_dump -U postgres app | gzip | aws s3 cp - "s3://db-backups/${KEY}"
echo "s3://db-backups/${KEY}"

Three commands, one pass, zero intermediate files. Constant memory, no /tmp pressure, and nothing to clean up if the process is interrupted. The object key app-<unix-timestamp>.sql.gz (for example app-1779625707.sql.gz) keeps every nightly dump distinct and sorts chronologically.

Turn on AWS S3 encryption

A database dump is your most sensitive object - it holds every row. Encrypt it at rest. AWS gives you two server-side options, both applied at upload time by a single flag on aws s3 cp:

# SSE-S3: AWS-managed keys, no key setup, no extra cost
pg_dump -U postgres app | gzip | \
  aws s3 cp - "s3://db-backups/${KEY}" --sse AES256

# SSE-KMS: your own KMS key, per-key access control + an audit trail
pg_dump -U postgres app | gzip | \
  aws s3 cp - "s3://db-backups/${KEY}" \
  --sse aws:kms --sse-kms-key-id alias/db-backups

--sse AES256 is the zero-config default. Reach for --sse aws:kms when you want to control who can decrypt (via the KMS key policy) and log every decrypt call in CloudTrail - the right choice for backups that must satisfy an audit.

Enforce encryption at the bucket

Setting the flag per-upload works, but one script that forgets it uploads a plaintext dump. Enforce encryption on the bucket so S3 encrypts every object regardless of what the client sends. Set default encryption:

aws s3api put-bucket-encryption \
  --bucket db-backups \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "alias/db-backups"}
    }]
  }'

With default encryption on, any object that arrives without an SSE header is still encrypted with the bucket's chosen key. To go further and reject unencrypted uploads outright, add a bucket policy that denies s3:PutObject when s3:x-amz-server-side-encryption is absent - the request then fails with 403 AccessDenied instead of silently storing plaintext.

Verify the backup is real and encrypted

A backup you have not restored is a guess. Confirm the object exists, has real size, and reports its encryption:

aws s3api head-object --bucket db-backups --key "$KEY" \
  --query '{size:ContentLength, enc:ServerSideEncryption}'
# -> {"size": 1043, "enc": "aws:kms"}

Then prove it round-trips - pull it back, gunzip, and grep for a known table:

aws s3 cp "s3://db-backups/${KEY}" - | gunzip | grep -c 'CREATE TABLE'

A non-zero count means the dump is complete and readable. Wire the whole script into a nightly CronJob and alert on a non-zero exit, and you have a self-managed Postgres backup that lands encrypted every night.

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

How do I enable AWS S3 encryption on an upload?

Add --sse AES256 to aws s3 cp for AWS-managed SSE-S3, or --sse aws:kms --sse-kms-key-id <key> for SSE-KMS with your own key. S3 encrypts the object at rest at write time; no client-side work is needed.

What is the difference between SSE-S3 and SSE-KMS on S3?

SSE-S3 (--sse AES256) uses keys AWS manages entirely - zero setup, no extra cost. SSE-KMS (--sse aws:kms) uses a KMS key you control, so you can restrict decrypt access via the key policy and log every decrypt call in CloudTrail, at a small per-request cost.

How do I back up a Postgres database to S3 without a temp file?

Stream it in one pipeline: pg_dump -U postgres app | gzip | aws s3 cp - s3://db-backups/app-$(date +%s).sql.gz. The aws s3 cp - form reads the upload from stdin, so the dump flows straight to S3 with constant memory and no disk file to clean up.

How do I force every object in an S3 bucket to be encrypted?

Set default encryption with aws s3api put-bucket-encryption so S3 encrypts anything that arrives without an SSE header. To reject unencrypted uploads instead of silently fixing them, add a bucket policy that denies s3:PutObject when s3:x-amz-server-side-encryption is missing.

Keep learning

Enable S3 Bucket EncryptionDevOps projectWrite an AWS KMS Key PolicyDevOps projectLock Down a Public S3 BucketDevOps projectDevOps roadmapStep by step to hiredDevOps interview questionsSTAR answersAll DevOps projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →