Deploying Django to Production on AWS with NGINX, uWSGI, and PostgreSQL

Deploying a Django web application to handle production traffic requires a robust, scalable architecture. In this guide, we‘ll walk through a proven stack consisting of:

  • NGINX – a high-performance web server that handles incoming HTTP requests
  • uWSGI – an application server that runs the Django app and communicates with NGINX
  • PostgreSQL – a powerful open-source relational database
  • AWS EC2 – a cloud compute service that provisions the underlying Linux servers

By the end of this guide, you‘ll have a secure, optimized Django deployment running on AWS. Let‘s dive in!

Why this stack?

While there are many possible ways to deploy Django, the NGINX + uWSGI + PostgreSQL stack is battle-tested and officially recommended by the Django project. Here‘s why:

  • NGINX is known for its high performance, stability, and low resource consumption. It powers over 40% of websites, including Django deployments at scale.

  • uWSGI is a versatile application server that‘s highly optimized for running Python applications. According to the Python Developers Survey 2020, uWSGI is the most popular choice for deploying Django, used by 28% of respondents.

  • PostgreSQL is a feature-rich, open-source database that pairs well with Django. It‘s known for its reliability, performance, and standards compliance. StackOverflow‘s 2020 Developer Survey ranks PostgreSQL as the 2nd most popular database.

  • AWS EC2 provides secure, resizable compute capacity in the cloud. It‘s a flexible choice for hosting that can scale to handle high traffic Django deployments when configured properly.

Now let‘s get into the technical details of configuring each piece.

Prerequisites

Before starting, you should have:

  • An AWS account with access to EC2
  • A registered domain name to serve your app on
  • Familiarity with Python, Django, and Linux sysadmin basics
  • A Django application ready to deploy (ensure DEBUG is off and ALLOWED_HOSTS is set)

Provisioning the server

Start by launching a new EC2 instance:

  1. Open the EC2 dashboard and click "Launch Instance"
  2. Select the Ubuntu Server 20.04 LTS AMI
  3. Choose an instance type with at least 1 GB RAM (t2.micro is sufficient to start)
  4. Configure instance details, increase the root block device size if needed
  5. Add tags to identify the instance (e.g. Name=django-prod-app)
  6. Configure a security group to allow inbound traffic on:
    • port 22 (SSH)
    • port 80 (HTTP)
    • port 443 (HTTPS)
  7. Review and launch, selecting an SSH key pair to access the instance

Once launched, make note of the instance‘s public IPv4 address and SSH into it:

ssh -i your-keypair.pem ubuntu@your-instance-ip

Setting up the environment

First, update the system packages:

sudo apt update
sudo apt upgrade -y

Create a dedicated user to run the app:

sudo useradd --system --gid webapps --shell /bin/bash --home /webapps/your-app-name your-app-user 

Install Python 3.8 and create a virtual environment:

sudo apt install python3.8-venv
python3.8 -m venv /webapps/your-app-name/venv

Installing and configuring PostgreSQL

Install the PostgreSQL packages:

sudo apt install postgresql postgresql-contrib libpq-dev

Switch to the postgres user and create the database:

sudo -u postgres psql
CREATE DATABASE your-app-db;
CREATE USER your-app-db-user WITH PASSWORD ‘your-db-password‘;
GRANT ALL PRIVILEGES ON DATABASE your-app-db TO your-app-db-user;
\q

Update your Django settings.py:

DATABASES = {
    ‘default‘: {
        ‘ENGINE‘: ‘django.db.backends.postgresql‘,
        ‘NAME‘: ‘your-app-db‘,
        ‘USER‘: ‘your-app-db-user‘,
        ‘PASSWORD‘: ‘your-db-password‘, 
        ‘HOST‘: ‘localhost‘,
        ‘PORT‘: ‘‘,
    }
}

Deploying the Django code

Clone your application repository:

sudo -u your-app-user git clone your-repo-url /webapps/your-app-name/your-app-repo

Install dependencies with pip:

sudo -u your-app-user /webapps/your-app-name/venv/bin/pip install -r /webapps/your-app-name/your-app-repo/requirements.txt

Run database migrations:

sudo -u your-app-user /webapps/your-app-name/venv/bin/python /webapps/your-app-name/your-app-repo/manage.py migrate

Collect static files:

sudo -u your-app-user /webapps/your-app-name/venv/bin/python /webapps/your-app-name/your-app-repo/manage.py collectstatic

Your Django code is now deployed on the server. Next we‘ll set up uWSGI and NGINX to serve it.

Configuring uWSGI

Install uWSGI into the virtual environment:

sudo -u your-app-user /webapps/your-app-name/venv/bin/pip install uwsgi 

Create a uWSGI configuration file at /webapps/your-app-name/uwsgi.ini:

[uwsgi]
project = your_app_name
base = /webapps/%(project)

chdir = %(base)/your-app-repo 
home = %(base)/venv
module = %(project).wsgi:application

master = true
processes = 5

socket = %(base)/run/uwsgi.sock
chown-socket = %(project):webapps
chmod-socket = 660
vacuum = true

die-on-term = true
logto = %(base)/logs/uwsgi.log

This config instructs uWSGI to:

  • Run the Django WSGI application located at your_app_name/wsgi.py
  • Use the Python virtual environment at /webapps/your_app_name/venv
  • Create a Unix socket at /webapps/your_app_name/run/uwsgi.sock to communicate with NGINX
  • Log to /webapps/your_app_name/logs/uwsgi.log

Create the required directories:

sudo -u your-app-user mkdir -p /webapps/your_app_name/run /webapps/your_app_name/logs

Now you can start uWSGI:

sudo -u your-app-user /webapps/your_app_name/venv/bin/uwsgi --ini /webapps/your_app_name/uwsgi.ini

To keep uWSGI running in the background, use a process manager like systemd or Supervisor. Here‘s a sample Supervisor config at /etc/supervisor/conf.d/your_app_name.conf:

[program:your_app_name]  
command=/webapps/your_app_name/venv/bin/uwsgi --ini /webapps/your_app_name/uwsgi.ini
directory=/webapps/your_app_name/your-app-repo
user=your-app-user
autostart=true
autorestart=true
stdout_logfile=/webapps/your_app_name/logs/uwsgi.log
redirect_stderr=true

Update Supervisor and start the process:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start your_app_name

Configuring NGINX

Install NGINX from the Ubuntu package manager:

sudo apt install nginx

Create a config file at /etc/nginx/sites-available/your_app_name:

upstream django {
    server unix:///webapps/your_app_name/run/uwsgi.sock;
}

server {
    listen 80;
    server_name your-domain.com;

    location = /favicon.ico { access_log off; log_not_found off; }

    location /static/ {
        alias /webapps/your_app_name/your-app-repo/staticfiles/;
    }

    location / {
        include uwsgi_params;
        uwsgi_pass django;
    }
}

This NGINX configuration:

  • Listens for requests on port 80
  • Proxies requests to the uWSGI Unix socket
  • Serves static files from /webapps/your_app_name/your-app-repo/staticfiles/
  • Passes all other requests to the Django app via uWSGI

Enable the config by linking it into the sites-enabled directory:

sudo ln -s /etc/nginx/sites-available/your_app_name /etc/nginx/sites-enabled/

Test the NGINX configuration for syntax errors:

sudo nginx -t

If the configuration is valid, restart NGINX:

sudo systemctl restart nginx

Congratulations, your Django application is now deployed and accessible on your domain!

Optimizing for production

With the core setup complete, let‘s discuss some enhancements for security, performance and scalability.

Security

  • Configure HTTPS by installing an SSL certificate from Let‘s Encrypt or your domain registrar. HTTPS encrypts traffic and is essential for protecting user data.

  • Harden your Django settings by ensuring DEBUG is off, setting ALLOWED_HOSTS, and using strong SECRET_KEY and database passwords.

  • Enable the UFW firewall and only allow inbound traffic on ports 22, 80 and 443:

    sudo ufw allow ssh
    sudo ufw allow http
    sudo ufw allow https  
    sudo ufw enable
  • Keep Ubuntu updated with regular apt update && apt upgrade

  • Consider using Fail2ban to block suspicious IP addresses and protect against brute force attacks

Performance

A few key optimizations can help your application scale and handle higher traffic:

  • Use uWSGI‘s multi-process model – the number of processes and threads can be adjusted based on available server CPU and memory. A good starting point is 2x the number of CPU cores. Monitor CPU and memory usage and adjust accordingly.

  • Tune NGINX – the main levers are the number of worker processes and connections per worker. See NGINX‘s official guide for details.

  • Cache expensive computations and queries – NGINX can cache entire responses, while Django‘s caching framework can store objects in memory, in the database, or on a separate service like Redis or Memcached.

  • Implement a content delivery network (CDN) like AWS CloudFront or Cloudflare to cache and serve static assets from edge locations closer to users. This reduces load on the origin server.

  • Enable HTTP/2 in NGINX for faster parallel requests and lower latency. HTTP/2 is supported in NGINX 1.9.5+.

  • Use database connection pooling with a tool like pgbouncer to reduce overhead of new connections. For high-traffic sites, consider read replicas to spread query load.

Scalability

As traffic grows, you may need to scale either vertically (bigger server instances) or horizontally (more servers).

Vertical scaling is simpler – just upgrade to a larger EC2 instance type. However, you‘ll eventually hit a limit.

For horizontal scaling, run multiple EC2 instances behind a load balancer like AWS ELB or HAProxy. This requires some additional configuration:

  • Ensure sessions are stored in the database or a backend like Redis, as they will no longer be tied to a single server
  • Use a shared storage backend like S3 or EFS for user-uploaded media
  • Configure the load balancer for health checks and session stickiness

You can also consider containerizing your application with Docker and running it on a cluster manager like Kubernetes or Amazon ECS for easier scaling and management.

Deployment workflow

A streamlined deployment process is essential for shipping new features and fixes quickly and safely. A typical Git-based workflow looks like:

  1. Developers work on features/bug fixes in feature branches or forks
  2. Changes are submitted as pull requests to the master branch
  3. Automated tests and code reviews are run on the PR
  4. Once approved, the PR is merged to master
  5. A CI/CD pipeline is triggered to automatically build, test and deploy the master branch
  6. If the pipeline passes, changes are deployed to production

Deploying from a Git repo on the server, as done in this guide, works fine for small sites. For larger teams and more frequent deployments, consider a dedicated CI/CD service like AWS CodeDeploy, Jenkins or CircleCI.

Conclusion

Properly deploying a Django application is a complex task with many moving parts – NGINX, uWSGI, PostgreSQL, EC2. The key is understanding how each piece fits together and tuning them based on your application‘s specific needs.

With the NGINX + uWSGI + PostgreSQL stack outlined here, you have a solid foundation to build on. Adding in security, performance and CI/CD best practices will ensure your application is production-ready.

Of course, this stack is just one of many possible approaches. You may want to explore other options like:

  • Containerization with Docker and Kubernetes
  • Serverless deployments with AWS Lambda or Google Cloud Functions
  • Platform-as-a-Service options like Heroku or Python Anywhere for a more managed experience

Ultimately, the "best" deployment comes down to your team‘s skills, budget and scalability requirements. Don‘t be afraid to experiment and evolve your infrastructure as your application grows!

Similar Posts