Django on Debian 12 EC2
This setup utilizes a Public Subnet, since the Django server should be accessible via internet.
The reason why
Amazon Linuxis not used is that the installation ofmod_wsgiandcertbotwasn't straight-forward on it.
Launch EC2 Instance for Django
| Parameter | Value |
|---|---|
| Region | ap-south-2 (Hyderabad) |
| Name | Give your preferred name |
| OS | Debian (Debian 12 as of 5-Jun-2025) |
| Architecture | 64-bit (Arm) |
| EC2 Type | m6g.medium |
| EC2 KeyPair | PirateDev.pem |
| VPC | Select PirateDev VPC |
| Subnet | Select a public subnet since Django/Apache server needs to be accessed via Internet |
| Auto-assign Public IP | Enable |
| Auto-assign IPv6 IP | Enable |
| Security Group | Select Django Security Group that allows SSH from Admin System IP and HTTP/HTTPS from Internet |
| EBS Size | 8GB gp3 (Encrypted - using KMS key) (Min size and can't be downgraded after setup) |
| Advanced | Enable Termination Protection |
Upon instance launch, attach an existing Elastic IP address
Connecting to Django Instance
If the Django Server's security group is configured as above, you should be able to SSH into the server from Admin System.
Steps to Connect
- SSH into the Django server:
# Permissions on pem file has to be 400, else it won't work
chmod 400 "PirateDev.pem"
# Replace <DJANGO EC2 Elastic IP address> below
# Notice the user for Debian-based instances is `admin` and not `ec2-user`
ssh -i "PirateDev.pem" admin@<DJANGO EC2 Elastic IP address>- Upgrade the packages on the server:
sudo apt update && sudo apt upgrade -ySet timezone
Install all locales first to disable locale warnings:
sudo apt install locales-allSet the server to IST timezone using:
sudo timedatectl set-timezone 'Asia/Kolkata'Confirm date by running date command in the terminal.
Disable Root Login
WARNING
A new limited user account is not created and admin is being used, since Apache's mod_wsgi runs into permission errors otherwise.
- Add SSH Public key (of Admin System that needs SSH into this server) to authorized keys of the new user:
sudo vi ~/.ssh/authorized_keys- Exit and SSH as the new user using password:
exit
ssh admin@<EC2 IP>- Disable Root login and Password Authentication:
sudo vi /etc/ssh/sshd_config
# Set `PasswordAuthentication` to `no`
# Set `PermitRootLogin` to `no`
# Set `AddressFamily` to `inet` (to disable IPv6 connections)- Restart SSH service:
sudo systemctl restart sshdInstall and configure Django
NOTE
Instead of using pip and venv, uv package manager is recommended.
- Install
uv:
curl -LsSf https://astral.sh/uv/install.sh | shInstall Apache Web Server & mod_wsgi
- To install
apache2andmod_wsgion Debian, use following command:
sudo apt -y install apache2 libapache2-mod-wsgi-py3- Start the Apache web server using:
sudo systemctl restart apache2- You should now have a working website at
http://<DJANGO EC2 Elastic IP address>.
Apache service changes for Django Logging
- By default, the apache server logs the messages from Django inside a
systemd-private-directory. To avoid this behavior, change this:
sudo vi /lib/systemd/system/apache2.service
# Change PrivateTmp to false- Reload daemons and restart Apache server:
sudo systemctl daemon-reload && sudo systemctl restart apache2(Optional) SSH Key Pair and Access to GitHub repos
This step is needed if you plan on re-using an existing Django GitHUb repo.
- Generate an SSH Key pair:
ssh-keygen -t rsa -b 4096- Grab the generated public key and add it to the GitHub account:
cat ~/.ssh/id_rsa.pub- Install
gitto manage the GitHub repos locally:
sudo apt -y install git- Confirm
gitinstallation using:
git --version- Configure git locally:
git config --global user.name "Pirate Dev"
git config --global user.email "57877111+PirateDevCom@users.noreply.github.com"(Optional) Bash Customization
(Optional) Create a new project
Use this only if you plan on creating a project from scratch.
- Create a new Python project using
uv:
uv init pirate-dev && cd pirate-dev- Install required Python packages:
# Django common packages
uv add django django-filter django-cors-headers django-redis django-storages
uv add psycopg2-binary
uv add pillow qrcode[pil]
uv add Python-dotenv
uv add boto3
uv add requests
uv add pyotp
uv add drf-spectacular
# Needed to serve S3 files via CloudFront (recommended)
uv add cryptography
# Django Rest Framework
uv add djangorestframework djangorestframework-simplejwt
# User agents to record devices info
uv add pyyaml ua-parser user-agents
uv add django-user-agents- Initiate a new Django project:
django-admin startproject pirate_dev .- Initialize
gitand Link the repo to a GitHub project.
Clone an existing project
- If you have an existing project, clone the GitHub repo using:
cd ~ && git clone git@github.com:PirateDevCom/pirate-dev.git && cd ~/pirate-dev- Activate environment and install packages:
uv syncSet up Django project
- Create Django log file:
sudo mkdir /logs && cd /logs && sudo touch piratedev.log && sudo chmod 777 /logs && sudo chmod 666 /logs/piratedev.logPython-dotenvis used for loading environment variables. This requires a.envfile:
touch /home/admin/pirate-dev/.envSetup Databases
- Install Postgres using this doc
- Install Redis using this doc
- For Django Cache
Setup Apache and HTTPS
Before we proceed, make sure
- The Django
settings.pyis updated to use Postgres and Redis. - TO update
.envfile and that it is loaded inwsgi.pyfile. - TO review logging, ALLOWED_HOSTS and STATIC_ROOT.
- The Django
Also, collect the static files for Django admin console using:
cd ~/pirate-dev && uv run manage.py collectstatic- Also, check for Django errors (if any) by running:
uv run manage.py check- Edit following apache config file first:
sudo vi /etc/apache2/sites-available/000-default.confWARNING
certbot certificate installation will fail due to WSGI lines in the apache conf file, so don't include them in this file.
<VirtualHost *:80>
DocumentRoot "/home/admin/pirate-dev/"
ServerName piratedev.com
ServerAlias www.piratedev.com
ServerAdmin info@piratedev.com
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# Allow access to static files
<Directory /home/admin/pirate-dev/static>
Require all granted
</Directory>
Alias /static /home/admin/pirate-dev/static
# Granting access to wsgi.py
<Directory "/home/admin/pirate-dev">
Require all granted
</Directory>
<Directory "/home/admin/pirate-dev/pirate_dev">
<Files wsgi.py>
Require all granted
</Files>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# To run WSGI daemon process (commented to avoid certbot errors)
# WSGIDaemonProcess piratedev.com python-home=/home/admin/pirate-dev/.venv python-path=/home/admin/pirate-dev
# WSGIProcessGroup piratedev.com
# WSGIScriptAlias / /home/admin/pirate-dev/pirate_dev/wsgi.py
# WSGIPassAuthorization On
</VirtualHost>- Since
Header setis used, to make GET calls from frontend to retrieve backend static files, enable headers module using:
sudo a2enmod headers- Restart apache server to check for any errors:
sudo systemctl restart apache2- Install
snapdusing:
sudo apt -y install snapd- Remove certbot-auto and any Certbot OS packages using:
sudo apt remove certbot- Install Certbot using:
sudo snap install --classic certbot- Prepare the Certbot command using:
sudo ln -s /snap/bin/certbot /usr/bin/certbot- At this point, you'll need to point the domain to the current IP address. You can do this using Route53 service and edit following A records:
piratedev.com
www.piratedev.com- Get and install a certificate using:
Make sure, at this point, the old EC2 instance is in stopped state, else certificate generation may fail. (Actual cause was due to CloudFront setup for serving static files)
sudo certbot --apache -vDetails to be filled as follows:
- Email:
info@piratedev.com - Terms of Service:
Y - Share Email Address:
N - Domain Names:
piratedev.com,www.piratedev.com - Unable to find ServerName:
Select default/original conf file
- Email:
Update SSL config file:
sudo vi /etc/apache2/sites-enabled/000-default-le-ssl.confMost of the content will be similar to what certbot generates, except for WSGI config, Rewrite config, header sets, valid host grants and root directory access.
<IfModule mod_ssl.c>
<VirtualHost *:443>
DocumentRoot "/home/admin/pirate-dev/"
ServerName piratedev.com
ServerAlias www.piratedev.com
ServerAdmin info@piratedev.com
SSLEngine on
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/piratedev.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/piratedev.com/privkey.pem
# BEGIN: Enable www to non-www redirection
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
RewriteCond %{HTTP_HOST} !^localhost
RewriteCond %{HTTP_HOST} !^[0-9]+.[0-9]+.[0-9]+.[0-9]+(:[0-9]+)?$
RewriteCond %{REQUEST_URI} !^/\.well-known
RewriteRule ^(.*)$ https://%1$1 [R=permanent,L]
# END: Enable www to non-www redirection
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SetEnvIfNoCase Host piratedev\.com VALID_HOST
# Allow access to static files
<Directory /home/admin/pirate-dev/static>
Require env VALID_HOST
</Directory>
Alias /static /home/admin/pirate-dev/static
# Granting access to wsgi.py
<Directory "/home/admin/pirate-dev">
Require env VALID_HOST
</Directory>
<Directory "/home/admin/pirate-dev/pirate_dev">
<Files wsgi.py>
Require env VALID_HOST
</Files>
Options Indexes FollowSymLinks
AllowOverride All
Require env VALID_HOST
</Directory>
LogLevel info
# To run WSGI daemon process
WSGIDaemonProcess piratedev.com python-home=/home/admin/pirate-dev/venv python-path=/home/admin/pirate-dev
WSGIProcessGroup piratedev.com
WSGIScriptAlias / /home/admin/pirate-dev/pirate_dev/wsgi.py
WSGIPassAuthorization On
# To avoid invalid host addresses reach Django (such as EC2 IP address)
<Directory "/">
#Require expr %{HTTP_HOST} == "piratedev.com"
SetEnvIfNoCase Host piratedev\.com VALID_HOST
Require env VALID_HOST
Options
</Directory>
</VirtualHost>
</IfModule>- Restart apache server using:
sudo systemctl restart apache2- Now test the https endpoint at
https://piratedev.com/.- The
wwwshould be redirected tonon-wwwas well. - And
httptraffic should be redirected tohttpsas well.
- The
🎉 Congrats! Your server setup is done.
