Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

Deploy Django with Nginx and S3 Storage

profile picture
Author
Mohsen Nasiri
Published
2025-02-17
Time to read
7 minutes reading time

About the author- Software Engineer and open-source enthusiast.

Introduction

Django is a powerful Python web framework designed for building robust web applications. This comprehensive guide will walk you through deploying a Django project in a production environment using industry best practices.

It explains how to configure Nginx as a reverse proxy, integrate S3-compatible storage for media files, and secure the deployment with proper configurations.

Prerequisites

Before starting, ensure you have:

  • Basic Linux command knowledge
  • A Debian-based server with root/sudo access
  • An S3-compatible storage bucket (e.g. Hetzner Object Storage, or MinIO)
  • A domain name (optional but recommended)
  • System requirements:
    • Minimum 1GB RAM
    • 10GB storage space
    • Python 3.8 or higher

Step 1 - System Preparation

First, let's update your system and install the necessary dependencies:

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv nginx

Step 2 - Django Project Setup

Let's create a clean development environment:

# Create and navigate to project directory
mkdir /home/<yourname>/django_project
cd /home/<yourname>/django_project

# Set up Python virtual environment
python3 -m venv venv
source venv/bin/activate

# Install core dependencies
pip install django python-dotenv

# Create Django project
django-admin startproject myproject .

# Initialize database
python manage.py migrate
python manage.py createsuperuser

Step 3 - Configure Django for S3 Storage

  1. Install django-storages and boto3:

    These libraries allow Django to interact with S3-compatible storage:

    pip install django-storages boto3
  2. Modify settings.py:

    Open myproject/settings.py and add the following configurations:

    Keep the variables for the S3 information. Those are set in the next step.

    import os
    from dotenv import load_dotenv
    
    # Load environment variables from the .env file
    load_dotenv()
    
    # Add 'storages' to the INSTALLED_APPS list
    INSTALLED_APPS = [
        # Other installed apps
        'django.contrib.contenttypes',
        'django.contrib.auth',
        'django.contrib.admin',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'storages',
    ]
    
    # Basic security and configuration settings
    ALLOWED_HOSTS = ["*"]
    DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
    
    # Static and media file settings
    STATICFILES_DIRS = [BASE_DIR / "static"]
    STATIC_ROOT = BASE_DIR / "assets"
    STATIC_URL = "assets/"
    MEDIA_URL = "media/"
    
    # S3 configuration
    AWS_S3_ACCESS_KEY_ID = os.getenv("AWS_S3_ACCESS_KEY_ID")
    AWS_S3_SECRET_ACCESS_KEY = os.getenv("AWS_S3_SECRET_ACCESS_KEY")
    AWS_S3_ENDPOINT_URL = os.getenv("AWS_S3_ENDPOINT_URL")
    AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME")
    AWS_S3_FILE_OVERWRITE = False
    AWS_DEFAULT_ACL = "private"  # Restrict file access
    AWS_S3_SIGNATURE_VERSION = "s3"
    
    # Storage settings for Django
    STORAGES = {
        "default": {
            "BACKEND": "storages.backends.s3.S3Storage",  # Use S3 for default file storage
        },
        "staticfiles": {
            "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",  # Default storage for static files
        },
    }

    Apply the Changes

    # Create a directory for static files
    mkdir static
    # Collect static files
    python manage.py collectstatic --noinput
  3. Create a .env File for AWS Credentials:

    Create a .env file in the project root and add your AWS credentials:

    AWS_S3_ACCESS_KEY_ID=your_access_key
    AWS_S3_SECRET_ACCESS_KEY=your_secret_key
    AWS_S3_ENDPOINT_URL=https://your_endpoint_url
    AWS_STORAGE_BUCKET_NAME=your_bucket_name

Step 4 - Set Up Gunicorn as a WSGI Server

  1. Install and Configure Gunicorn

    Gunicorn will serve our Django application:

    pip install gunicorn

    Test Gunicorn:

    gunicorn --bind 0.0.0.0:8000 myproject.wsgi

    You should be able to access http://<your_server_ip>:8000

    Press CTRL + C to stop it.

  2. Create a Systemd Service for Gunicorn:

    Create a service file:

    sudo nano /etc/systemd/system/gunicorn.service

    Add the following content:

    Replace <yourname>/django_project with the name of your actual user and project name.

    [Unit]
    Description=Gunicorn daemon for Django project
    After=network.target
    
    [Service]
    User=www-data
    Group=www-data
    WorkingDirectory=/home/<yourname>/django_project
    ExecStart=/home/<yourname>/django_project/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 myproject.wsgi:application
    
    [Install]
    WantedBy=multi-user.target

    Enable and start Gunicorn:

    sudo systemctl daemon-reload
    sudo systemctl enable gunicorn
    sudo systemctl start gunicorn

    Check to see if the Gunicorn Service is Active:

    sudo systemctl status gunicorn
    Click here if it failed

    If you get an error that says Changing to the requested working directory failed: Permission denied, you may need to adjust the permissions to your user:

    sudo chmod o+x /home/<yourname>
    sudo pkill -9 gunicorn
    sudo systemctl restart gunicorn 

    You should now also get some output when you run this command:

    curl -s http://127.0.0.1:8000 | head -n 7

Step 5 - Set Up Nginx as a Reverse Proxy

  1. Create an Nginx Configuration File

    Create a new Nginx configuration file:

    sudo nano /etc/nginx/sites-available/myproject

    Add the following configuration:

    Replace <yourname>/django_project with the name of your actual user and project name, and <your_server_ip_or_domain> with your own IP or domain.

    server {
        listen 80;
        listen [::]:80;
        server_name <your_server_ip_or_domain>;
    
        access_log /var/log/nginx/django_access.log;
        error_log /var/log/nginx/django_error.log;
    
        client_max_body_size 100M;  # Adjust based on your needs
    
        location / {
            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_pass http://127.0.0.1:8000;
        }
    
        # Serve static files directly through nginx
        location /assets/ {
            alias /home/<yourname>/django_project/assets/;
            expires 30d;
            add_header Cache-Control "public, no-transform";
        }
    }
  2. Enable the Configuration and Restart Nginx

    sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
    sudo nginx -t
    sudo systemctl restart nginx

    You should be able to access http://<your_server_ip_or_domain>.

Step 6 - Secure Your Setup with Firewall and SSL (Optional)

Allow HTTP and HTTPS traffic:

# Install and configure UFW
sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow "Nginx Full"
sudo ufw enable

If you have a domain, secure it with Let's Encrypt SSL:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Step 7 - File Upload Testing

Let's create a simple app to test our S3 storage integration:

python manage.py startapp upload

Add the following model (upload/models.py) to test file uploads:

from django.db import models

class TestUpload(models.Model):
    name = models.CharField(max_length=100, help_text="Enter a name for the upload")
    image = models.FileField(upload_to="uploads/", help_text="Select a file to upload")

    def __str__(self):
        return f"Upload: {self.name}"

Register the model in your admin interface (upload/admin.py):

from django.contrib import admin
from .models import TestUpload

@admin.register(TestUpload)
class TestUploadAdmin(admin.ModelAdmin):
    list_display = ['name', 'image']
    search_fields = ['name']

Now edit myproject/settings.py and add 'upload' in the section "INSTALLED_APPS:

INSTALLED_APPS = [
    # Other installed apps
    'django.contrib.contenttypes',
    'django.contrib.auth',
    'django.contrib.admin',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'storages',
    'upload',
]

Apply the database changes:

python manage.py makemigrations
python manage.py migrate

Step 8 - Deployment Verification

Testing Your Setup

  1. Visit your domain or server IP in a browser
  2. Access the admin panel at /admin
  3. Login — if you get an error, see troubleshooting below
  4. Try uploading a file through the TestUpload model
  5. Verify the file appears in your S3 bucket

Troubleshooting

  • If you encounter permission issues:

    1. Set proper ownership of the project directory:

      sudo chown -R www-data:www-data /home/<yourname>/django_project
      sudo chmod -R 755 /home/<yourname>/django_project
    2. For SQLite database, set file permissions:

      sudo chmod 666 /home/<yourname>/django_project/db.sqlite3
    3. After making permission changes, restart Gunicorn:

      sudo systemctl restart gunicorn
  • If static files aren't loading, run python manage.py collectstatic

  • If uploads fail, check your S3 credentials and permissions

  • For 502 errors, verify Gunicorn is running properly

Conclusion

Congratulations! You now have a production-ready Django deployment with:

  • A robust application server (Gunicorn)
  • High-performance reverse proxy (Nginx)
  • Scalable cloud storage solution (S3)
  • Automated process management (Systemd)
License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get €20/$20 free credit!

Valid until: 31 December 2025 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more