Deploying a Django project to AWS EC2 can become confusing when issues like DisallowedHost, GitHub Actions not running, .env files not loading, or Gunicorn using the wrong environment start appearing.
In this guide, I’ll explain the complete professional production setup for deploying a Django project using:
- AWS EC2
- GitHub Actions (CI/CD)
- Gunicorn
- Nginx
- .env.production
- Secure .gitignore
- Proper ALLOWED_HOSTS
- Production-ready gunicorn.service
This is the exact setup used in real production projects.
Project Structure (Correct Setup)
Your project should look like this:
codify/
│
├── .github/
│ └── workflows/
│ └── deploy.yml
│
├── codify/
│ ├── account/
│ ├── contest/
│ ├── judge/
│ ├── manage.py
│ └── settings.py
│
├── frontend/
└── README.md
Important Note
GitHub Actions only works if your workflow file is here:
.github/workflows/deploy.yml
Wrong Location
codify/.github/workflows/
If your workflow is inside the wrong folder, GitHub Actions will never run.
Local vs Production Environment Files
A professional Django deployment uses separate environment files.
Local Development
.env.local
Used for:
- Local laptop development
- Testing
- Debugging
Example:
DEBUG=True
ALLOWED_HOSTS=127.0.0.1,localhost
DB_NAME=local_db
DB_USER=postgres
DB_PASSWORD=local_password
Production Server
.env.production
Used for:
- AWS EC2 deployment
- Live production server
Example:
ENVIRONMENT=production
DEBUG=False
ALLOWED_HOSTS=backend.jitcoder.in,api2.jitcoder.in,3.238.247.231,127.0.0.1,localhost
CORS_ALLOWED_ORIGINS=https://jitcoder.in,https://www.jitcoder.in
CSRF_TRUSTED_ORIGINS=https://jitcoder.in,https://www.jitcoder.in
FRONTEND_URL=https://jitcoder.in
DATABASE_URL=your_database_url
REDIS_URL=redis://127.0.0.1:6379/0
EMAIL_USER=your_email@gmail.com
EMAIL_PASS=your_password
Correct .gitignore Setup
Never push secrets to GitHub.
Use this:
# Environment variables
.env
.env.*
!.env.example
This means:
Ignored
.env
.env.local
.env.production
.env.test
Not Ignored
.env.example
This is the best professional practice.
Why .env.local Still Exists After .gitignore
Many developers get confused here.
Important Rule
.gitignore does NOT remove already tracked files.
It only prevents new files from being added.
If you pushed .env.local before adding .gitignore, Git will continue tracking it.
How to Check If Git Still Tracks It
Run:
git ls-files | findstr .env
Example output:
codify/.env.example
codify/.env.local
codify/.env.production
This means Git is still tracking those files.
Proper Fix for Already Tracked .env Files
Remove them from Git tracking without deleting local files:
git rm –cached codify/.env.local
git rm –cached codify/.env.production
git rm –cached frontend/my-app/.env.production
git commit -m “Remove env files from git tracking”
git push origin main
Now Git will stop tracking them.
The files remain safe on your machine and server.
Correct settings.py for Dynamic Environment Loading
Use this setup:
from pathlib import Path
import os
from dotenv import load_dotenv
BASE_DIR = Path(__file__).resolve().parent.parent
ENVIRONMENT = os.getenv(“ENVIRONMENT”, “local”)
env_file = (
BASE_DIR / “.env.production”
if ENVIRONMENT == “production”
else BASE_DIR / “.env.local”
)
if env_file.exists():
load_dotenv(env_file)
print(f”Loaded env file: {env_file}”)
Correct ALLOWED_HOSTS Setup
Use:
ALLOWED_HOSTS = os.getenv(
“ALLOWED_HOSTS”,
“127.0.0.1,localhost”
).split(“,”)
This avoids the common Django error:
DisallowedHost
Invalid HTTP_HOST header
Why DisallowedHost Happens
Example error:
Invalid HTTP_HOST header: ‘api2.jitcoder.in’
This happens because:
api2.jitcoder.in
is missing from:
ALLOWED_HOSTS
Fix:
ALLOWED_HOSTS=backend.jitcoder.in,api2.jitcoder.in,3.238.247.231,127.0.0.1,localhost
Gunicorn Using Wrong .env File
This is one of the most common production problems.
Even when .env.production exists, Gunicorn may still load:
.env.local
because ENVIRONMENT=production is missing from gunicorn.service.
Correct gunicorn.service
File:
/etc/systemd/system/gunicorn.service
Use:
[Unit]
Description=gunicorn daemon
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/jitcoder/codify
Environment=”ENVIRONMENT=production”
ExecStart=/home/ubuntu/jitcoder/venv/bin/gunicorn \
–workers 3 \
–bind unix:/home/ubuntu/jitcoder/gunicorn.sock \
codify.wsgi:application
[Install]
WantedBy=multi-user.target
Very Important After Editing
Always run:
sudo systemctl daemon-reload
sudo systemctl restart gunicorn
sudo systemctl restart nginx
Without daemon-reload, changes won’t apply.
How to Verify Which .env File Django Uses
Best command:
sudo journalctl -u gunicorn –no-pager | tail -50
Correct output:
Loaded env file: /home/ubuntu/jitcoder/codify/.env.production
Wrong output:
Loaded env file: /home/ubuntu/jitcoder/codify/.env.local
This is the most reliable method.
How .env.production Reaches AWS Server
Since .env.production is ignored by Git:
git pull
will NOT download it.
That is correct.
You create it manually once on EC2:
cd ~/jitcoder/codify
nano .env.production
Paste your production values.
Save it.
That file stays permanently on the server.
Future git pull updates only code, not secrets.
This is exactly how production should work.
GitHub Actions Verification
Go to:
GitHub Repository → Actions Tab
You should see:
- Success
- Failed
- Running
This confirms CI/CD status.
Verify Deployment from EC2
Run:
cd ~/jitcoder/codify
git log –oneline -5
If the latest pushed commit appears:
Your GitHub Actions deployment is working.
Final Professional Setup
GitHub
.env.example
only
Local PC
.env.local
private
AWS EC2
.env.production
private
Final Rule
Never Edit
settings.py
directly on the server
Always Edit
.env.production
for production changes
This is the professional and scalable way to deploy Django applications.
Conclusion
A proper Django production deployment is not just about making the website run.
It is about:
- security
- maintainability
- CI/CD automation
- clean architecture
- production safety
Once you follow this structure, deployments become reliable, scalable, and professional.
This setup is exactly how serious Django projects are deployed in production.