What Is AWS EC2 and Why Deploy Django There?
Amazon Elastic Compute Cloud (EC2) is a core AWS service that provides resizable virtual servers in the cloud. You launch an EC2 instance, choose an operating system (like Ubuntu or Amazon Linux), and gain full root access to configure it exactly as your application requires. For Django developers, deploying on EC2 means you retain complete control over the server environment, can integrate tightly with other AWS services (RDS, S3, CloudWatch), and can scale vertically or horizontally as traffic grows. Unlike Platform-as-a-Service offerings, EC2 does not abstract away the server layer — you own the stack from the operating system up to the application, which is ideal when you need custom middleware, specific Python versions, or fine‑grained performance tuning.
What This Tutorial Covers
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →You will learn how to launch an Ubuntu EC2 instance, install Python and a virtual environment, clone a Django project, configure Gunicorn as the application server, set up Nginx as a reverse proxy, manage the process with systemd, secure the server with a firewall and optional SSL, and adopt production‑ready best practices. Every step includes concrete commands and configuration files that you can copy and adapt.
Prerequisites
- An AWS account with billing enabled (free tier eligible if you stay within limits)
- A domain name (optional, for HTTPS setup)
- A Django project pushed to a Git repository (public or private)
- Familiarity with the Linux command line and SSH
Step 1: Launch an Ubuntu EC2 Instance
Log into the AWS Management Console, navigate to EC2, and click Launch Instance. Choose the following:
- AMI: Ubuntu 22.04 LTS (64-bit x86) – free tier eligible
- Instance type: t2.micro (free tier) or t3.micro
- Key pair: Create or select an existing key pair to enable SSH access
- Network settings: Allow SSH (port 22) from your IP, and later we’ll add HTTP/HTTPS
- Storage: 8 GiB gp3 volume is sufficient to start
Launch the instance. Once it shows “running”, note its public IPv4 address and public IPv4 DNS.
Step 2: Connect to the Instance via SSH
Open a terminal and use the key pair you downloaded. Replace your-key.pem and the IP address.
chmod 400 your-key.pem
ssh -i your-key.pem ubuntu@<instance-public-ip>
The default username for Ubuntu AMIs is ubuntu. After login, you should see a welcome banner.
Step 3: Update the System and Install Python Dependencies
First, refresh the package index and install Python, pip, and other build tools.
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3-pip python3-dev python3-venv libpq-dev curl git
If your Django project uses PostgreSQL (recommended), install the PostgreSQL client library as shown (libpq-dev). For MySQL, use libmysqlclient-dev.
Step 4: Create a Virtual Environment and Clone the Project
We’ll place the application in /home/ubuntu/app. Create the directory and set up a Python virtual environment.
mkdir -p /home/ubuntu/app
cd /home/ubuntu/app
python3 -m venv venv
source venv/bin/activate
Now clone your Django project from GitHub (or copy the source).
git clone https://github.com/your-username/your-django-project.git .
Install the project dependencies inside the virtual environment.
pip install -r requirements.txt
If you haven’t pinned versions, run pip freeze > requirements.txt after a successful local test.
Step 5: Configure Django Settings for Production
Create a separate production settings file or use environment variables. A minimal safe production configuration:
# settings/production.py or settings.py with condition
DEBUG = False
ALLOWED_HOSTS = ['your-domain.com', '<instance-ip>', 'localhost']
# Security
SECURE_SSL_REDIRECT = True # once HTTPS is set
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
Never run with DEBUG = True in production. Store the SECRET_KEY and database credentials in environment variables (see best practices).
Step 6: Install and Test Gunicorn
Gunicorn is a production‑grade WSGI HTTP server for Python. Install it inside the virtual environment.
pip install gunicorn
Test that Gunicorn can serve your Django app locally. Run it from the project root (where manage.py resides):
gunicorn --bind 0.0.0.0:8000 your_project_name.wsgi:application
Replace your_project_name with the actual Django project name. Open a browser to http://<instance-ip>:8000 (you may need to temporarily open port 8000 in the security group). If you see your site, stop Gunicorn (Ctrl+C).
Step 7: Configure Gunicorn as a Systemd Service
We want Gunicorn to start on boot and restart if it crashes. Create a systemd unit file.
sudo nano /etc/systemd/system/gunicorn.service
Add the following content, adjusting paths and the project name:
[Unit]
Description=Gunicorn daemon for Django project
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/app
EnvironmentFile=/home/ubuntu/app/.env
ExecStart=/home/ubuntu/app/venv/bin/gunicorn \
--workers 3 \
--bind unix:/home/ubuntu/app/gunicorn.sock \
your_project_name.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Key details:
--bind unix:...sockuses a Unix socket for faster communication with Nginx (no TCP overhead).EnvironmentFilepoints to a file where you set environment variables likeSECRET_KEY,DB_PASSWORD, etc.User=ubunturuns the service as the ubuntu user;Group=www-dataensures the socket is accessible by Nginx.
Create the .env file (secure it with chmod 600 .env):
SECRET_KEY=your-secure-random-key
DB_NAME=your_db_name
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_HOST=your_db_host (e.g., RDS endpoint)
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable gunicorn
sudo systemctl start gunicorn
sudo systemctl status gunicorn
The socket file gunicorn.sock should appear in the app directory.
Step 8: Install and Configure Nginx
Nginx will act as a reverse proxy, handling client requests and forwarding them to Gunicorn via the Unix socket. It also serves static and media files efficiently.
sudo apt install -y nginx
Create a site configuration:
sudo nano /etc/nginx/sites-available/your_project
Paste the configuration below, substituting your domain or IP.
server {
listen 80;
server_name your-domain.com <instance-ip>;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
alias /home/ubuntu/app/staticfiles/;
}
location /media/ {
alias /home/ubuntu/app/media/;
}
location / {
proxy_pass http://unix:/home/ubuntu/app/gunicorn.sock;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Enable the site and test the configuration:
sudo ln -s /etc/nginx/sites-available/your_project /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default # optional, remove default catch-all
sudo nginx -t
sudo systemctl restart nginx
Now visit your instance’s public IP or domain (HTTP). Django should respond via Nginx.
Step 9: Update AWS Security Group (Firewall)
Go back to the EC2 console → Security Groups. Edit the inbound rules for your instance’s security group and add:
- HTTP (port 80) – Source 0.0.0.0/0
- HTTPS (port 443) – Source 0.0.0.0/0 (if using SSL)
- Remove the temporary port 8000 rule if you added one
Only allow SSH (port 22) from your specific IP for security.
Step 10: Obtain an SSL Certificate (Let’s Encrypt)
For production, HTTPS is mandatory. Install Certbot and obtain a free certificate.
sudo apt install -y certbot python3-certbot-nginx
Ensure your domain’s DNS A record points to the instance’s public IP. Then run:
sudo certbot --nginx -d your-domain.com
Follow the prompts. Certbot will modify the Nginx config to enable HTTPS and set up automatic renewal. Test renewal with:
sudo certbot renew --dry-run
Now your site is accessible securely via https://your-domain.com.
Step 11: Collect Static Files and Configure Storage
Django serves static files from a single directory when DEBUG=False. Collect them into the directory defined in STATIC_ROOT.
python manage.py collectstatic --noinput
For media (user uploads), consider using AWS S3 with django-storages to avoid storing files on the instance file system, which isn’t persistent across instance replacements. However, for a single-instance setup, you can keep them locally with regular backups.
Step 12: Set Up a Managed Database (RDS – Optional but Recommended)
Instead of running a database on the same instance, use Amazon RDS for PostgreSQL (or MySQL). It provides automated backups, snapshots, and scaling. Create an RDS instance in the same VPC, note the endpoint, and update your .env file with the credentials. Ensure the EC2 security group allows inbound traffic from the RDS security group (and vice versa) on the database port (e.g., 5432).
In your Django settings, use:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST'),
'PORT': '5432',
}
}
Best Practices for Django on AWS EC2
- Use a virtual environment – Always isolate dependencies to avoid conflicts.
- Keep secrets out of code – Use environment variables (via
.envfile, AWS Secrets Manager, or Parameter Store). Never hardcodeSECRET_KEYor database passwords. - Separate settings – Maintain a
base.pyand aproduction.pysettings file, importing from base and overriding appropriately. - Enable firewall (UFW) on the instance – Even with security groups, add an extra layer:
sudo ufw allow 22/tcp,sudo ufw allow 'Nginx Full',sudo ufw enable. - Set up monitoring and logging – Use AWS CloudWatch agent or a tool like Sentry for error tracking. Gunicorn logs can be directed to a file or journald.
- Automated backups – RDS does this automatically; for the EC2 instance, create AMI snapshots regularly or use a backup script for the app directory.
- Use a process manager (systemd) – Always run Gunicorn as a service, never in a screen/tmux session.
- Keep the OS updated – Enable unattended-upgrades or schedule regular patching.
- Restrict database access – Only allow connections from the EC2 instance’s security group (or a specific IP for admin).
- Offload static/media to S3 with CloudFront – For higher scale, move static files to S3 and serve via CloudFront CDN. This reduces load on the instance and improves speed.
- Use Elastic IP or a load balancer – Associate a static Elastic IP with the instance to prevent address changes on reboot. For multiple instances, place an Application Load Balancer (ALB) in front.
- Test disaster recovery – Simulate an instance failure and restore from an AMI or rebuild using an automated script (Infrastructure as Code).
Conclusion
Deploying a Django application on AWS EC2 gives you complete control, deep AWS integration, and a path to scale as your traffic grows. By following the steps above, you’ve launched a secure, production‑ready instance with Gunicorn, Nginx, systemd supervision, and optional SSL. Combine these practices with managed databases, offloaded static files, and robust monitoring, and your Django app will be ready to serve users reliably. As a next step, consider automating the entire setup with Terraform or AWS CloudFormation, and explore containerized deployments via Amazon ECS or EKS if you move toward microservices. Happy deploying!