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:
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.
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.
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.
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:
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.
sudo nano /etc/ssh/sshd_config
Add or modify the following lines:
PermitRootLogin no
PasswordAuthentication no
After making changes, restart the SSH service:
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.
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
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
### 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.
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)*
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
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`.
pip install -r requirements.txt
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/'
Run collectstatic
to gather all static files into the STATIC_ROOT
directory:
python manage.py collectstatic
### Step 7: Configure PostgreSQL Database
A robust database is critical for production.
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:**
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:
python manage.py migrate
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
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`:**
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:
cd /etc/apache2/sites-available/
Duplicate the default configuration to start your own:
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`):
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
**9.3. Enable Your Configuration and Reload Apache:**
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).
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:**
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.
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`):
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;
}
}
**9.3. Enable Your Nginx Configuration:**
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).
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).
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 ...
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)
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': '',
}
}
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
{
"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"
}
Set strict permissions for this file:
sudo chmod 600 /etc/config.json # Only root can read/write
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 ...
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.
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!
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
orsupervisord
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)
Man, thanks a lot! This guide has all I needed. It saved my day! I even sign up here to say thanks!)
You're welcome! I'm glad the guide helped you out.
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 ?
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)