Django on AWS Production Checklist

A production checklist for Django on AWS: account security, VPC and security groups, IAM roles instead of keys, RDS and ElastiCache settings, S3 and CloudFront, secrets, TLS, logging, backups, and cost controls.

Problem statement

AWS gives a Django deployment every building block it needs, and just as many ways to misconfigure them: a database with a public endpoint, long-lived access keys in environment variables, an S3 bucket that lists its contents to the world, security groups open to 0.0.0.0/0, and a surprise bill from NAT gateway traffic.

This checklist reviews a Django deployment on AWS, whether it runs on EC2, ECS Fargate, Elastic Beanstalk, or App Runner-style platforms, with RDS for PostgreSQL, ElastiCache for Redis, and S3 for media. It focuses on the settings that most often cause security incidents, outages, and cost surprises.

Django on AWS, private by defaultALBpublic, TLSApp tierprivate subnetsRDS, ElastiCacheno public accessS3block public accessOnly the load balancer is public; everything else accepts traffic from the app tier only.
  1. Secure the account: no root usage, MFA everywhere, SSO or IAM Identity Center for people, CloudTrail enabled.
  2. Put the app, database, and cache in private subnets; only the load balancer is public.
  3. Use IAM roles (instance profiles, ECS task roles) instead of access keys in environment variables.
  4. RDS: no public access, encryption, automated backups with PITR, Multi-AZ where downtime matters, deletion protection.
  5. S3: Block Public Access on, private media via presigned URLs, public assets via CloudFront.
  6. Secrets in Secrets Manager or Parameter Store; TLS certificates from ACM on the load balancer.
  7. Logs and metrics in CloudWatch with retention set; budgets and cost alerts configured.

Checklist

1. Account and identity

  • The root user is not used for daily work, has MFA, and has no access keys.
  • People sign in through IAM Identity Center (SSO) or IAM users with MFA; permissions follow least privilege.
  • CloudTrail records management events for all regions, stored in a protected bucket.
  • Production runs in its own account (AWS Organizations) or is at least clearly separated from staging and experiments.

2. Network

  • The VPC has public subnets (load balancer, NAT) and private subnets (app, database, cache) across at least two Availability Zones.
  • Security groups reference each other instead of IP ranges: the database accepts connections only from the app's security group; the app only from the load balancer's.
  • No security group allows 0.0.0.0/0 on database, Redis, or SSH ports.
  • SSH is replaced by Systems Manager Session Manager (no open port 22), or restricted to a bastion or VPN.
  • VPC endpoints for S3 (gateway endpoint) and other AWS services used heavily reduce NAT gateway charges and keep traffic private.
three subnet tiersPublic subnetsload balancer, NAT gatewayPrivate app subnetsEC2, ECS tasks: no public IPsPrivate data subnetsRDS, ElastiCache: app SG onlyOnly the load balancer has a route from the internetdjango-deployment.com
Spread each tier across at least two availability zones.

3. Compute and IAM roles

  • EC2 instances use instance profiles; ECS tasks use task roles; no AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY in application environment variables.
  • Role policies grant only needed actions on specific resources (for example s3:GetObject and s3:PutObject on arn:aws:s3:::myapp-media/*, ses:SendRawEmail on your identity).
  • EC2 instances require IMDSv2 (session tokens for the metadata service).
  • Instances or tasks are replaceable: launched from an AMI, launch template, or task definition, not hand-configured.
no long-lived keys on serversAccess keys in .env✕valid until someone rotates them✕copied into backups and images✕often over-privilegedInstance or task role✓temporary credentials, auto-rotated✓boto3 finds them with no config✓policy scoped to one bucketdjango-storages uses the role automatically when no keys are setdjango-deployment.com
Leave AWS_ACCESS_KEY_ID unset in production so the role is used.

4. Load balancer and TLS

  • An Application Load Balancer terminates TLS with an ACM certificate; HTTP listener redirects to HTTPS.
  • A modern TLS security policy is selected on the HTTPS listener.
  • Django trusts the forwarded protocol: SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https").
  • ALLOWED_HOSTS includes your domains; health checks send a valid Host header or use a health endpoint that tolerates the ALB's.
  • ALB idle timeout, Gunicorn timeout, and database statement timeout are aligned.
  • AWS WAF managed rules or rate-based rules protect login and API endpoints if you face abusive traffic.

5. Database (RDS or Aurora)

  • PubliclyAccessible is false; the database lives in private subnets.
  • Storage encryption is enabled (it must be chosen at creation).
  • Automated backups with a retention period that matches your recovery needs; point-in-time restore has been tested.
  • Deletion protection is on, and final snapshots are required on deletion.
  • Multi-AZ for production workloads that cannot tolerate hours of downtime.
  • Connections use TLS (sslmode=verify-full with the RDS CA bundle); newer PostgreSQL versions on RDS enforce TLS by default.
  • Parameter group sets useful values (for example log_min_duration_statement, idle_in_transaction_session_timeout) and enables pg_stat_statements.
  • Minor version upgrades are applied in a maintenance window; the major version is still supported.
RDS before go-liveNot publicly accessibleprivate data subnetsMulti-AZautomatic failoverBackups 7+ days, PITRrestore testedDeletion protection onand a final snapshotEncryption and TLS enforcedrds.force_sslMost of these cannot be changed without downtime laterdjango-deployment.com
Encryption at rest can only be chosen when the instance is created.

6. Cache (ElastiCache)

  • Deployed in private subnets, reachable only from the app security group.
  • Encryption in transit and AUTH or RBAC enabled; Django and Celery use rediss:// URLs.
  • Eviction policy matches the role (cache versus broker), and a separate cluster or configuration is used for the Celery broker if tasks must not be lost.

7. S3 and CloudFront

  • Account-level and bucket-level S3 Block Public Access are enabled.
  • Media buckets are private; files are served through presigned URLs or CloudFront with origin access control.
  • Default encryption is on (SSE-S3 or SSE-KMS); versioning is on for buckets holding user data.
  • Lifecycle rules expire temporary uploads and abort incomplete multipart uploads.
  • Static files use hashed names with long Cache-Control headers, served by CloudFront or WhiteNoise.
static and media need different bucketsAccessCachingStatic bucket✓CloudFront only (OAC)long, hashed namesMedia bucket✓private, signed URLsshort, or none for privateBlock Public Access stays on for both bucketsdjango-deployment.com
Separate buckets let you apply different lifecycle and backup rules.

8. Secrets and configuration

  • Secrets live in Secrets Manager or SSM Parameter Store (SecureString), injected at start-up (ECS secrets, a start script, or the SDK), never baked into images or AMIs.
  • Rotation is configured for database credentials where practical, with SECRET_KEY_FALLBACKS planned for Django's key.
  • Configuration differences between environments are parameters, not code branches.

9. Logging, monitoring, and backups

  • Application and access logs go to CloudWatch Logs (or another central system) with retention set per log group.
  • CloudWatch alarms cover 5xx rates, target response time, unhealthy hosts, RDS CPU, free storage, connections, and replica lag.
  • AWS Backup or native snapshots cover RDS and any EBS volumes with state, with copies to another region or account for disaster recovery if required.
  • Restores are tested regularly.

10. Cost controls

  • AWS Budgets alerts on monthly spend and forecast.
  • Resources are tagged by environment and component for cost reports.
  • NAT gateway data processing, cross-AZ traffic, and CloudWatch Logs ingestion are reviewed monthly, since they grow unnoticed.
  • Idle resources (unattached EBS volumes, old snapshots, unused Elastic IPs and load balancers) are cleaned up.

Explanation

On AWS, security and reliability are mostly configuration: which subnets resources live in, which security groups can talk to each other, which role a process assumes, and whether backups, encryption, and deletion protection are on. A Django app that is correct on its own can still be exposed or fragile because of those settings. Reviewing them layer by layer (account, network, compute, data, secrets, operations, cost) catches the common gaps before they become incidents or invoices.

Edge cases / notes

Elastic Beanstalk and managed platforms

Platforms that create resources for you still leave settings to review: whether the database was created inside the environment (and would be deleted with it), the instance profile's permissions, and log retention.

Infrastructure as code

Checklists drift once changes are made in the console. Defining the stack with Terraform, CloudFormation, or CDK lets you review these settings in code and reproduce environments exactly.

When to automate this

Enable AWS Config rules or Security Hub standards that flag public databases, open security groups, unencrypted resources, and root key usage, and fail infrastructure pull requests with policy checks. Automated detection keeps the checklist true after launch.

Next steps

FAQ

Should my RDS database be publicly accessible for Django?

No. Keep it in private subnets with PubliclyAccessible false, and allow connections only from the application's security group.

How should Django get AWS credentials in production?

From an IAM role: an EC2 instance profile or an ECS task role. boto3 and django-storages pick up role credentials automatically.

Do I need a NAT gateway for Django on AWS?

Only if private resources need outbound internet access. Use VPC endpoints for AWS services to reduce NAT traffic and cost.

What is the most common AWS security mistake with Django?

Long-lived access keys in environment variables or code, followed by publicly accessible databases and S3 buckets.

Keep going