Skip to content

Django on Debian 12 EC2

This setup utilizes a Public Subnet, since the Django server should be accessible via internet.

The reason why Amazon Linux is not used is that the installation of mod_wsgi and certbot wasn't straight-forward on it.

Launch EC2 Instance for Django

ParameterValue
Regionap-south-2 (Hyderabad)
NameGive your preferred name
OSDebian (Debian 12 as of 5-Jun-2025)
Architecture64-bit (Arm)
EC2 Typem6g.medium
EC2 KeyPairPirateDev.pem
VPCSelect PirateDev VPC
SubnetSelect a public subnet since Django/Apache server needs to be accessed via Internet
Auto-assign Public IPEnable
Auto-assign IPv6 IPEnable
Security GroupSelect Django Security Group that allows SSH from Admin System IP and HTTP/HTTPS from Internet
EBS Size8GB gp3 (Encrypted - using KMS key) (Min size and can't be downgraded after setup)
AdvancedEnable 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:
shell
# 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:
shell
sudo apt update && sudo apt upgrade -y

Set timezone

Install all locales first to disable locale warnings:

shell
sudo apt install locales-all

Set the server to IST timezone using:

shell
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:
shell
sudo vi ~/.ssh/authorized_keys
  • Exit and SSH as the new user using password:
shell
exit
ssh admin@<EC2 IP>
  • Disable Root login and Password Authentication:
sh
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:
sh
sudo systemctl restart sshd

Install and configure Django

NOTE

Instead of using pip and venv, uv package manager is recommended.

  • Install uv:
shell
curl -LsSf https://astral.sh/uv/install.sh | sh

Install Apache Web Server & mod_wsgi

  • To install apache2 and mod_wsgi on Debian, use following command:
shell
sudo apt -y install apache2 libapache2-mod-wsgi-py3
  • Start the Apache web server using:
shell
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:
shell
sudo vi /lib/systemd/system/apache2.service
# Change PrivateTmp to false
  • Reload daemons and restart Apache server:
shell
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:
shell
ssh-keygen -t rsa -b 4096
  • Grab the generated public key and add it to the GitHub account:
shell
cat ~/.ssh/id_rsa.pub
  • Install git to manage the GitHub repos locally:
shell
sudo apt -y install git
  • Confirm git installation using:
shell
git --version
  • Configure git locally:
shell
git config --global user.name "Pirate Dev"
git config --global user.email "57877111+PirateDevCom@users.noreply.github.com"

(Optional) Bash Customization

Refer this doc

(Optional) Create a new project

Use this only if you plan on creating a project from scratch.

  • Create a new Python project using uv:
shell
uv init pirate-dev && cd pirate-dev
  • Install required Python packages:
shell
# 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:
shell
django-admin startproject pirate_dev .
  • Initialize git and Link the repo to a GitHub project.

Clone an existing project

  • If you have an existing project, clone the GitHub repo using:
shell
cd ~ && git clone git@github.com:PirateDevCom/pirate-dev.git && cd ~/pirate-dev
  • Activate environment and install packages:
shell
uv sync

Set up Django project

  • Create Django log file:
shell
sudo mkdir /logs && cd /logs && sudo touch piratedev.log && sudo chmod 777 /logs && sudo chmod 666 /logs/piratedev.log
  • Python-dotenv is used for loading environment variables. This requires a .env file:
shell
touch /home/admin/pirate-dev/.env

Setup Databases

Setup Apache and HTTPS

  • Before we proceed, make sure

    • The Django settings.py is updated to use Postgres and Redis.
    • TO update .env file and that it is loaded in wsgi.py file.
    • TO review logging, ALLOWED_HOSTS and STATIC_ROOT.
  • Also, collect the static files for Django admin console using:

shell
cd ~/pirate-dev && uv run manage.py collectstatic
  • Also, check for Django errors (if any) by running:
shell
uv run manage.py check
  • Edit following apache config file first:
shell
sudo vi /etc/apache2/sites-available/000-default.conf

WARNING

certbot certificate installation will fail due to WSGI lines in the apache conf file, so don't include them in this file.

apache
<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 set is used, to make GET calls from frontend to retrieve backend static files, enable headers module using:
shell
sudo a2enmod headers
  • Restart apache server to check for any errors:
shell
sudo systemctl restart apache2
  • Install snapd using:
shell
sudo apt -y install snapd
  • Remove certbot-auto and any Certbot OS packages using:
shell
sudo apt remove certbot
  • Install Certbot using:
shell
sudo snap install --classic certbot
  • Prepare the Certbot command using:
shell
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:
text
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)

shell
sudo certbot --apache -v
  • Details 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
  • Update SSL config file:

shell
sudo vi /etc/apache2/sites-enabled/000-default-le-ssl.conf

Most 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.

apache
<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:
shell
sudo systemctl restart apache2
  • Now test the https endpoint at https://piratedev.com/.
    • The www should be redirected to non-www as well.
    • And http traffic should be redirected to https as well.

🎉 Congrats! Your server setup is done.