DEV Community

Cover image for How to set up and deploy a Django application on a Linux server
Ajith Kumar P M
Ajith Kumar P M

Posted on • Edited on

How to set up and deploy a Django application on a Linux server

Deploying a web application can seem daunting, but with a structured approach, it becomes a clear, manageable process. This guide will walk you through setting up a Linux server, configuring security, and deploying your Django application using either Apache with mod_wsgi or Nginx with Gunicorn.

Note: While this guide was originally conceptualized on July 29th, 2021, the principles remain highly relevant for modern Django deployments.


### Step 1: Server Initialization and Security Best Practices

Before deploying any application, a secure and well-configured server is fundamental.

**1.1. Operating System Setup:**
Begin by installing a Linux distribution (e.g., Ubuntu, Debian, CentOS). Once installed, ensure all your system packages are up-to-date:

Enter fullscreen mode Exit fullscreen mode

On Debian/Ubuntu-based distributions

sudo apt update && sudo apt upgrade -y


**1.2. Set Your Server Hostname:**
A descriptive hostname makes server management easier.

Enter fullscreen mode Exit fullscreen mode

hostnamectl set-hostname

Verify the hostname

hostname


**1.3. Map Hostname to IP Address:**
Edit your `/etc/hosts` file to map your server's IP address to its hostname. This aids in local resolution.

Enter fullscreen mode Exit fullscreen mode

Add this line to /etc/hosts


**1.4. Secure User Management (Avoid Root Login!):**
Running commands as the `root` user is a significant security risk. Always perform administrative tasks with a regular user account that has `sudo` privileges.

Enter fullscreen mode Exit fullscreen mode

Create a new user

adduser

Add the new user to the 'sudo' group

adduser sudo

Now, log out of root and log in as your new user

exit


**1.5. Harden SSH Access (Passwordless Login with SSH Keys):**
Password authentication for SSH is less secure. Opt for SSH key-based authentication.

**On your Local Machine:**
Generate a new SSH key pair:

Enter fullscreen mode Exit fullscreen mode

ssh-keygen -b 4096

Follow the prompts, optionally setting a strong passphrase for added security.

**Transferring Your Public Key to the Server:**
* **Recommended (if `ssh-copy-id` is available):**
    ```


    ssh-copy-id <your-username>@<your-server-IP-address>


    ```
* **Manual Method:**
    1.  Create the `.ssh` directory on your server (if it doesn't exist) and set appropriate permissions:
        ```


        mkdir -p ~/.ssh
        chmod 700 ~/.ssh


       ```
    2.  Transfer your local public key (`id_rsa.pub` by default) to the server:
        ```


        scp ~/.ssh/id_rsa.pub <your-username>@<your-server-IP-address>:~/.ssh/authorized_keys


        ```
    3.  Ensure the `authorized_keys` file has correct permissions on the server:
        ```


        chmod 600 ~/.ssh/authorized_keys


        ```

**1.6. Configure SSH Daemon (`sshd_config`):**
Edit `/etc/ssh/sshd_config` to disable root login and password authentication, enhancing security.

Enter fullscreen mode Exit fullscreen mode

sudo nano /etc/ssh/sshd_config

Add or modify the following lines:

Enter fullscreen mode Exit fullscreen mode

PermitRootLogin no
PasswordAuthentication no

After making changes, restart the SSH service:

Enter fullscreen mode Exit fullscreen mode

sudo systemctl restart sshd

**Crucial:** Before closing your current SSH session, open a *new* terminal and try logging in with your new user and SSH key to ensure it works. If it fails, revert the changes in `sshd_config` from your original session.

Enter fullscreen mode Exit fullscreen mode

Step 2: Setting Up Your Firewall with UFW

A firewall is your server's first line of defense. UFW (Uncomplicated Firewall) simplifies firewall management on Ubuntu/Debian.

sudo apt install ufw -y

# Deny all incoming traffic by default, allow all outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH to maintain access (CRITICAL: Do this BEFORE enabling UFW)
sudo ufw allow ssh

# Allow traffic on port 8000 (for initial Django development server testing)
sudo ufw allow 8000/tcp

# Enable the firewall
sudo ufw enable
Enter fullscreen mode Exit fullscreen mode

Once you've confirmed your application works (in Step 7), you can tighten the rules:

# Delete the temporary allow for port 8000 and allow standard HTTP (port 80)
sudo ufw delete allow 8000
sudo ufw allow http/tcp # For port 80
sudo ufw allow https/tcp # For port 443 (essential for production)

# Check firewall status
sudo ufw status verbose
Enter fullscreen mode Exit fullscreen mode

### Step 3: Preparing Your Django Project for Production

Your Django project needs minor adjustments for a production environment.

**3.1. Generate `requirements.txt`:**
In your local project's root directory, create a `requirements.txt` file listing all your project's Python dependencies.

Enter fullscreen mode Exit fullscreen mode

pip freeze > requirements.txt

Example content:

Django==3.2.5

psycopg2-binary==2.9.1 # Use psycopg2-binary for easier installation, switch to psycopg2 in production if needed

pytz==2021.1

sqlparse==0.4.1


**3.2. Transfer Your Project to the Server:**
Choose your preferred method for transferring your project.

* **SCP (Secure Copy Protocol):** Best for initial transfers of local projects.
    ```


    scp -r <local-project-path> <your-username>@<your-server-IP-address>:~/


    ```
* **Git:** Ideal for projects under version control. Clone your repository directly on the server.
    ```


    git clone <your-remote-repository-url> ~/your-project-name


    ```
    *(Assuming you've configured Git on your server, including SSH keys for private repos)*

Enter fullscreen mode Exit fullscreen mode

Step 4: Setting Up a Python Virtual Environment

Isolate your project's Python dependencies from system-wide packages.

sudo apt install python3-pip -y
sudo apt install python3-venv -y

# Navigate into your project directory on the server
cd ~/your-project-name

# Create a virtual environment (e.g., named 'venv')
python3 -m venv venv

# Activate the virtual environment
source venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

You'll see (venv) prepended to your shell prompt, indicating the virtual environment is active.


### Step 5: Install Project Dependencies

With the virtual environment active, install the packages listed in your `requirements.txt`.

Enter fullscreen mode Exit fullscreen mode

pip install -r requirements.txt


Enter fullscreen mode Exit fullscreen mode

Step 6: Configure Django settings.py for Production

Adjust your Django settings for a production environment.

# settings.py

DEBUG = False
ALLOWED_HOSTS = ['<your-server-IP-address>', '<your-domain-name-if-any>'] # e.g., ['192.168.1.100', 'example.com']

# Static files configuration for production
STATIC_ROOT = os.path.join(BASE_DIR, 'static_root') # A dedicated folder for collected static files
# MEDIA_ROOT and MEDIA_URL if you handle user-uploaded files
# MEDIA_ROOT = os.path.join(BASE_DIR, 'media_root')
# MEDIA_URL = '/media/'
Enter fullscreen mode Exit fullscreen mode

Run collectstatic to gather all static files into the STATIC_ROOT directory:

python manage.py collectstatic
Enter fullscreen mode Exit fullscreen mode

### Step 7: Configure PostgreSQL Database

A robust database is critical for production.

Enter fullscreen mode Exit fullscreen mode

sudo apt install postgresql postgresql-contrib -y

Switch to the postgres user to manage the database

sudo -u postgres psql

Inside psql:

CREATE DATABASE your_database_name;
CREATE USER your_db_username WITH PASSWORD 'your_strong_password';
ALTER ROLE your_db_username SET client_encoding TO 'utf8';
ALTER ROLE your_db_username SET default_transaction_isolation TO 'read committed';
ALTER ROLE your_db_username SET timezone TO 'Asia/Kolkata'; # Adjust timezone as needed
GRANT ALL PRIVILEGES ON DATABASE your_database_name TO your_db_username;

Exit psql

\q

**Update your Django `settings.py` with the new database credentials:**

Enter fullscreen mode Exit fullscreen mode

settings.py

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'your_database_name',
'USER': 'your_db_username',
'PASSWORD': 'your_strong_password',
'HOST': 'localhost', # Or the IP address if your DB is on a different server
'PORT': '', # Default PostgreSQL port is 5432
}
}

Run migrations to set up your database tables:

Enter fullscreen mode Exit fullscreen mode

python manage.py migrate


Enter fullscreen mode Exit fullscreen mode

Step 8: Test Your Application with the Development Server

Before setting up a production-grade web server, perform a quick test.

python manage.py runserver 0.0.0.0:8000
Enter fullscreen mode Exit fullscreen mode

Now, open your web browser and navigate to http://<your-server-IP-address>:8000. You should see your Django application running. Once verified, stop the development server (Ctrl+C).


### Step 9: Configuring a Production Web Server

Django's development server is not suitable for production. We'll use either Apache with `mod_wsgi` or Nginx with Gunicorn.

#### Option A: Apache with `mod_wsgi`

Apache is a widely used web server, and `mod_wsgi` is a powerful module for serving Python applications.

**9.1. Install Apache and `mod_wsgi`:**

Enter fullscreen mode Exit fullscreen mode

sudo apt install apache2 -y
sudo apt install libapache2-mod-wsgi-py3 -y


**9.2. Configure Apache Virtual Host:**
Navigate to Apache's available sites directory:

Enter fullscreen mode Exit fullscreen mode

cd /etc/apache2/sites-available/

Duplicate the default configuration to start your own:

Enter fullscreen mode Exit fullscreen mode

sudo cp 000-default.conf your-project.conf
sudo nano your-project.conf

Edit `your-project.conf` with the following content, making sure to replace placeholders (`YOURUSER`, `YOURPROJECT`, `VIRTUALENV`, `your-server-IP-address` or `your-domain-name`):

Enter fullscreen mode Exit fullscreen mode


ServerAdmin webmaster@localhost
ServerName # Or your domain name, e.g., example.com

DocumentRoot /var/www/html

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

# Map static and media files directly via Apache
Alias /static /home/YOURUSER/YOURPROJECT/static_root/ # Points to STATIC_ROOT
<Directory /home/YOURUSER/YOURPROJECT/static_root/>
    Require all granted
</Directory>

Alias /media /home/YOURUSER/YOURPROJECT/media_root/ # Points to MEDIA_ROOT if used
<Directory /home/YOURUSER/YOURPROJECT/media_root/>
    Require all granted
</Directory>

# WSGI configuration for your Django project
<Directory /home/YOURUSER/YOURPROJECT/your_project_name_folder/> # Folder containing wsgi.py
    <Files wsgi.py>
        Require all granted
    </Files>
</Directory>

WSGIScriptAlias / /home/YOURUSER/YOURPROJECT/your_project_name_folder/wsgi.py
WSGIDaemonProcess django_app python-path=/home/YOURUSER/YOURPROJECT python-home=/home/YOURUSER/YOURPROJECT/venv # Path to your virtual environment
WSGIProcessGroup django_app
Enter fullscreen mode Exit fullscreen mode

**9.3. Enable Your Configuration and Reload Apache:**

Enter fullscreen mode Exit fullscreen mode

sudo a2ensite your-project.conf # Enable your new virtual host
sudo a2dissite 000-default.conf # Disable the default Apache page
sudo systemctl reload apache2 # Reload Apache to apply changes


**9.4. Grant Apache Permissions:**
Apache's user (`www-data`) needs read access to your project files and write access to the media directory (if used) and SQLite database (if used).

Enter fullscreen mode Exit fullscreen mode

Grant ownership of project directory to www-data group for read access

sudo chown -R :www-data /home/YOURUSER/YOURPROJECT/
sudo chmod -R 755 /home/YOURUSER/YOURPROJECT/

For SQLite database (if used)

This is usually not recommended for production, use PostgreSQL instead.

sudo chown :www-data /path/to/your/sqlite/db.sqlite3
sudo chmod 664 /path/to/your/sqlite/db.sqlite3

For media files (user uploads)

sudo chown -R :www-data /home/YOURUSER/YOURPROJECT/media_root/
sudo chmod -R 775 /home/YOURUSER/YOURPROJECT/media_root/


#### Option B: Nginx with Gunicorn

Nginx is a high-performance web server, often used as a reverse proxy for Python applications served by Gunicorn.

**9.1. Install Nginx and Gunicorn:**

Enter fullscreen mode Exit fullscreen mode

sudo apt install nginx -y
pip install gunicorn # Install inside your virtual environment


**9.2. Configure Nginx Server Block:**
Edit the default Nginx configuration or create a new one.

Enter fullscreen mode Exit fullscreen mode

sudo nano /etc/nginx/sites-available/your-project

Add the following content (replace `your-server-IP-address` or `your-domain-name`, and `your-project-name`):

Enter fullscreen mode Exit fullscreen mode

server {
listen 80;
server_name ; # e.g., 192.168.1.100 example.com

access_log /var/log/nginx/your-project-access.log;
error_log /var/log/nginx/your-project-error.log;

# Serve static and media files directly via Nginx
location /static/ {
    alias /home/YOURUSER/YOURPROJECT/static_root/; # Path to STATIC_ROOT
}

location /media/ {
    alias /home/YOURUSER/YOURPROJECT/media_root/; # Path to MEDIA_ROOT
}

# Pass all other requests to Gunicorn
location / {
    proxy_pass [http://127.0.0.1:8000](http://127.0.0.1:8000); # Gunicorn will listen on this port
    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_set_header X-Forwarded-Proto $scheme;
}
Enter fullscreen mode Exit fullscreen mode

}

**9.3. Enable Your Nginx Configuration:**

Enter fullscreen mode Exit fullscreen mode

sudo ln -s /etc/nginx/sites-available/your-project /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default # Remove the default Nginx config
sudo nginx -t # Test Nginx configuration for syntax errors
sudo systemctl restart nginx # Restart Nginx


**9.4. Start Gunicorn:**
Navigate to the directory containing your Django project's `wsgi.py` file (this is typically the inner project directory, not the root).

Enter fullscreen mode Exit fullscreen mode

cd /home/YOURUSER/YOURPROJECT/your_project_name_folder/
gunicorn wsgi:application --bind 0.0.0.0:8000 --daemon --workers 3 # --workers should be 2 * CPU_CORES + 1

* `wsgi:application`: Tells Gunicorn to load the `application` object from `wsgi.py`.
* `--bind 0.0.0.0:8000`: Gunicorn listens on all interfaces on port 8000.
* `--daemon`: Runs Gunicorn in the background.
* `--workers 3`: Sets the number of worker processes (adjust based on your server's CPU cores).

**Persistence for Gunicorn:**
The `--daemon` flag keeps Gunicorn running, but for true production stability, consider using a process manager like `systemd` or `supervisord` to automatically start and restart Gunicorn if it crashes or after a server reboot. (This is a more advanced step not fully covered here, but highly recommended).

Enter fullscreen mode Exit fullscreen mode

Step 10: Handling Environment Variables Securely

Never hardcode sensitive information (like SECRET_KEY) directly in settings.py.

Option A: Passing Apache Environment Variables (via mod_wsgi)

This method allows Apache to set environment variables that mod_wsgi then passes to your Django application.

10.1. Add to Apache Virtual Host Config (your-project.conf):
Inside your <VirtualHost> block, add SetEnv directives:

# ... other configurations ...

SetEnv DB_NAME your_database_name
SetEnv DB_USER your_db_username
SetEnv DB_PASSWD your_strong_password
SetEnv DB_HOST localhost
SetEnv SECRET_KEY 'your_django_secret_key' # Generate a strong one!

# ... rest of the VirtualHost config ...
Enter fullscreen mode Exit fullscreen mode

Reload Apache after changes: sudo systemctl reload apache2

10.2. Modify Your wsgi.py:
To make these environment variables accessible in Django, update your wsgi.py file:

# your_project_name_folder/wsgi.py
import os
import sys

# Add your project and virtual environment paths
project_home = '/home/YOURUSER/YOURPROJECT' # Path to your project root
if project_home not in sys.path:
    sys.path.insert(0, project_home)

# Set Django settings module (replace 'your_project_name' with your actual project folder name)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_project_name.settings")

from django.core.wsgi import get_wsgi_application

# List of environment variables to pass from Apache to Django
env_variables_to_pass = [
    'DB_NAME',
    'DB_USER',
    'DB_PASSWD',
    'DB_HOST',
    'SECRET_KEY',
    # Add any other environment variables you set in Apache
]

_application = get_wsgi_application()

def application(environ, start_response):
    # Pass the WSGI environment variables through to os.environ
    for var in env_variables_to_pass:
        if var in environ:
            os.environ[var] = environ[var]
    return _application(environ, start_response)
Enter fullscreen mode Exit fullscreen mode

10.3. Access in settings.py:

# settings.py
import os

SECRET_KEY = os.environ.get('SECRET_KEY')

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASSWD'),
        'HOST': os.environ.get('DB_HOST'),
        'PORT': '',
    }
}
Enter fullscreen mode Exit fullscreen mode

Option B: Using a Configuration File (e.g., JSON)

This method is more portable and can be used with both Apache/Nginx.

10.1. Create a Secure Configuration File:
Create a file (e.g., /etc/config.json) outside your project directory to keep it secure.

sudo nano /etc/config.json
Enter fullscreen mode Exit fullscreen mode
{
  "SECRET_KEY": "your_highly_secure_django_secret_key_here",
  "DB_NAME": "your_database_name",
  "DB_USER": "your_db_username",
  "DB_PASSWD": "your_strong_password",
  "DB_HOST": "localhost"
}
Enter fullscreen mode Exit fullscreen mode

Set strict permissions for this file:

sudo chmod 600 /etc/config.json # Only root can read/write
Enter fullscreen mode Exit fullscreen mode

10.2. Update settings.py to Read the Config:

# settings.py
import os
import json

# Path to your configuration file
CONFIG_FILE_PATH = '/etc/config.json'

# Load configuration from the JSON file
try:
    with open(CONFIG_FILE_PATH) as config_file:
        config = json.load(config_file)
except FileNotFoundError:
    print(f"Warning: Configuration file not found at {CONFIG_FILE_PATH}. Using environment variables or defaults.")
    config = {} # Fallback to empty dict if file not found

# Access settings using .get() for safety, with os.environ as a fallback
SECRET_KEY = config.get("SECRET_KEY", os.environ.get('SECRET_KEY'))

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': config.get("DB_NAME", os.environ.get('DB_NAME')),
        'USER': config.get("DB_USER", os.environ.get('DB_USER')),
        'PASSWORD': config.get("DB_PASSWD", os.environ.get('DB_PASSWD')),
        'HOST': config.get("DB_HOST", os.environ.get('DB_HOST', 'localhost')),
        'PORT': '',
    }
}

# ... other settings ...
Enter fullscreen mode Exit fullscreen mode

This approach provides a flexible way to manage sensitive settings.


### Step 11: Final Restart and Verification

After all configurations, restart your web server and verify your application.

Enter fullscreen mode Exit fullscreen mode

For Apache:

sudo systemctl restart apache2

For Nginx:

sudo systemctl restart nginx

Now, open your web browser and navigate to your server's IP address or domain name (e.g., `http://<your-server-IP-address>` or `http://your-domain-name.com`). Your Django application should now be live!

Enter fullscreen mode Exit fullscreen mode

Additional Notes & Next Steps (Beyond this Guide)

  • HTTPS: For any production site, always enable HTTPS using Let's Encrypt or a similar SSL/TLS certificate provider. This encrypts traffic between your server and users.
  • Process Management: For Gunicorn, use systemd or supervisord to manage the Gunicorn process, ensuring it starts on boot and restarts automatically if it crashes.
  • Logging: Configure comprehensive logging for Django, Apache/Nginx, and Gunicorn to monitor your application's health.
  • Error Pages: Customize Django and web server error pages for a better user experience.
  • Security Headers: Implement strong security headers in Apache or Nginx (e.g., X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security).
  • Backup Strategy: Regularly back up your database and project files.
  • Monitoring: Set up monitoring tools to track server resources and application performance.

This comprehensive guide should provide a solid foundation for deploying your Django application. Happy deploying!

Top comments (4)

Collapse
 
agas0077 profile image
Andrey • Edited

Man, thanks a lot! This guide has all I needed. It saved my day! I even sign up here to say thanks!)

Collapse
 
ajth-in profile image
Ajith Kumar P M

You're welcome! I'm glad the guide helped you out.

Collapse
 
izenish profile image
Zenish

Is there a video to this? And does this work with a django app that take react as frontend. I’ve created APIS with django rest framework and built the front end with react.
Will this work ?

Collapse
 
ajth-in profile image
Ajith Kumar P M • Edited

you have to deploy them separately. Yes you can use this guide to deploy django apps with DRF.
youtube.com/watch?v=Sa_kQheCnds (refer this video)