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.
- Secure the account: no root usage, MFA everywhere, SSO or IAM Identity Center for people, CloudTrail enabled.
- Put the app, database, and cache in private subnets; only the load balancer is public.
- Use IAM roles (instance profiles, ECS task roles) instead of access keys in environment variables.
- RDS: no public access, encryption, automated backups with PITR, Multi-AZ where downtime matters, deletion protection.
- S3: Block Public Access on, private media via presigned URLs, public assets via CloudFront.
- Secrets in Secrets Manager or Parameter Store; TLS certificates from ACM on the load balancer.
- 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/0on 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.
3. Compute and IAM roles
- EC2 instances use instance profiles; ECS tasks use task roles; no
AWS_ACCESS_KEY_IDorAWS_SECRET_ACCESS_KEYin application environment variables. - Role policies grant only needed actions on specific resources (for example
s3:GetObjectands3:PutObjectonarn:aws:s3:::myapp-media/*,ses:SendRawEmailon 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.
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_HOSTSincludes your domains; health checks send a validHostheader 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)
PubliclyAccessibleis 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-fullwith 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 enablespg_stat_statements. - Minor version upgrades are applied in a maintenance window; the major version is still supported.
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-Controlheaders, served by CloudFront or WhiteNoise.
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_FALLBACKSplanned 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
- Deploy on containers with Deploy Django on AWS ECS Fargate.
- Or on virtual machines with Deploy Django on AWS EC2 Step by Step.
- Configure private media with How to Store Django Media Files on Amazon S3.
- Keep the bill in check with Reduce Django Hosting Costs Without Losing Reliability.
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.