Deploy a Django App on Ubuntu Server with Apache
Deploying a Django application on a production server can seem daunting at first, but by following a systematic approach you can have a robust, secure, and maintainable setup. In this article, we’ll walk through every step to deploy a Django app on an Ubuntu server using Apache with the mod_wsgi module. We’ll cover prerequisites, server setup, application configuration, security hardening, and best practices for ongoing maintenance. By the end, you will have a fully functional, production-ready Django deployment.
Prerequisites
Before we begin, ensure you have the following:
- Ubuntu Server (18.04, 20.04, 22.04 LTS or later) with root or sudo access.
- Domain name pointing to your server’s public IP.
- Basic knowledge of Django, Linux command line, and Apache.
- Git repository containing your Django project (optional but recommended).
- An administrator user with sudo privileges.
Preparing the Ubuntu Server
Updating System Packages
First, update the package lists and upgrade existing packages to the latest versions:
sudo apt update sudo apt upgrade -y
Keeping the system up to date ensures you have the latest security fixes and features.
Creating a Dedicated System User
For security and manageability, create a system user that will own and run the Django application:
sudo adduser --disabled-login --gecos 'Django App' django
This command creates a user django without login capabilities and sets descriptive GECOS fields.
Installing Apache and mod_wsgi
Installing Apache
Install Apache HTTP Server from Ubuntu’s repositories:
sudo apt install -y apache2
Enable Apache to start on boot:
sudo systemctl enable apache2
Installing mod_wsgi
mod_wsgi is the Apache module that interfaces with Python applications via the WSGI protocol:
sudo apt install -y libapache2-mod-wsgi-py3
This will install mod_wsgi compiled for Python 3.
Enabling Necessary Apache Modules
Enable the mod_wsgi, headers, and rewrite modules, which are commonly needed:
sudo a2enmod wsgi sudo a2enmod headers sudo a2enmod rewrite
After enabling modules, reload Apache:
sudo systemctl reload apache2
Setting Up the Python Virtual Environment
Installing Python and pip
Ensure Python 3 and pip are installed:
sudo apt install -y python3 python3-pip python3-venv
Creating and Activating a Virtualenv
Switch to the dedicated user and create a virtual environment inside your project directory:
sudo -i -u django cd ~ git clone https://github.com/yourusername/your-django-project.git cd your-django-project python3 -m venv venv source venv/bin/activate
Your shell prompt should now show the (venv) prefix.
Installing Django and Application Dependencies
Within the virtualenv, install your project’s dependencies:
pip install --upgrade pip pip install -r requirements.txt
If you don’t yet have a requirements.txt, generate one locally with:
pip freeze > requirements.txt
Configuring the Django Application
Adjusting Settings for Production
Edit your settings.py to ensure production readiness:
- DEBUG: set to False.
- ALLOWED_HOSTS: add your domain name and server IP.
- SECRET_KEY: load from an environment variable or external file; do not hardcode.
- Static/Media: configure STATIC_ROOT, MEDIA_ROOT, and corresponding URLs.
- Database: switch from SQLite (development) to PostgreSQL or MySQL for production.
Example snippet:
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
DEBUG = False
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'fallback-secret')
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com', 'your.server.ip']
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_ROOT = BASE_DIR / 'mediafiles'
STATIC_URL = '/static/'
MEDIA_URL = '/media/'Collecting Static Files
Collect all static assets into the directory defined by STATIC_ROOT:
python manage.py collectstatic --noinput
This command gathers CSS, JavaScript, images, and other static files for efficient serving.
Database Migrations
Apply any pending migrations to set up your production database schema:
python manage.py migrate
You should also create a superuser for admin access:
python manage.py createsuperuser
Apache Virtual Host Configuration
Creating the Virtual Host File
On Ubuntu, Apache site configurations reside in /etc/apache2/sites-available/. Create a new file, e.g. yourproject.conf:
sudo nano /etc/apache2/sites-available/yourproject.conf
Populate it as follows:
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
# Serve static and media directly
Alias /static/ /home/django/your-django-project/staticfiles/
<Directory /home/django/your-django-project/staticfiles>
Require all granted
</Directory>
Alias /media/ /home/django/your-django-project/mediafiles/
<Directory /home/django/your-django-project/mediafiles>
Require all granted
</Directory>
# WSGI configuration
WSGIDaemonProcess yourproject python-home=/home/django/your-django-project/venv python-path=/home/django/your-django-project
WSGIProcessGroup yourproject
WSGIScriptAlias / /home/django/your-django-project/yourproject/wsgi.py
<Directory /home/django/your-django-project/yourproject>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
# Logging
ErrorLog ${APACHE_LOG_DIR}/yourproject-error.log
CustomLog ${APACHE_LOG_DIR}/yourproject-access.log combined
</VirtualHost>Adjust paths to match your project’s directory structure and WSGI module location.
Enabling the Site and Restarting Apache
Enable your new site configuration and disable the default site to avoid conflicts:
sudo a2ensite yourproject.conf sudo a2dissite 000-default.conf
Reload Apache to apply changes:
sudo systemctl reload apache2
Test that your application is accessible via http://yourdomain.com/. If you see Django’s default “It worked!” page or your application homepage, deployment is successful on HTTP.
Serving Static and Media Files
In our VirtualHost above, we configured Apache to serve static and media files from designated directories. This is efficient, as Apache handles these requests faster than going through Django. Ensure the file permissions allow the Apache user (www-data) to read these directories:
sudo chown -R django:www-data /home/django/your-django-project/staticfiles sudo chown -R django:www-data /home/django/your-django-project/mediafiles sudo chmod -R 750 /home/django/your-django-project/staticfiles sudo chmod -R 750 /home/django/your-django-project/mediafiles
Enabling HTTPS with Let’s Encrypt
Securing your application with TLS is crucial. We’ll use Certbot to obtain a free SSL certificate.
- Install Certbot and the Apache plugin:
sudo apt install -y certbot python3-certbot-apache
- Obtain and install the certificate:
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
- Follow interactive prompts to agree to terms and configure redirection from HTTP to HTTPS.
- Verify Renewal:
- Certbot installs a cron job for renewal. Test it:
sudo certbot renew --dry-run
Securing the Server
Firewall Configuration
Use UFW (Uncomplicated Firewall) to allow only necessary traffic:
sudo apt install -y ufw sudo ufw allow OpenSSH sudo ufw allow 'Apache Full' sudo ufw enable
This opens ports 22 (SSH), 80 (HTTP), and 443 (HTTPS).
Fail2Ban Setup
Protect against brute‑force attacks by installing Fail2Ban:
sudo apt install -y fail2ban
Create a local jail configuration:
sudo nano /etc/fail2ban/jail.local
Insert:
[sshd] enabled = true [apache-auth] enabled = true
Restart Fail2Ban:
sudo systemctl restart fail2ban
Automatic Security Updates
Enable unattended security updates:
sudo apt install -y unattended-upgrades sudo dpkg-reconfigure --priority=low unattended-upgrades
This ensures critical security patches install automatically.
Logging and Monitoring
- Apache Logs: Check /var/log/apache2/yourproject-error.log and -access.log.
- Django Logs: Configure Django logging in settings.py to capture application-level errors.
- System Monitoring: Use tools like htop, top, or more advanced solutions (Prometheus + Grafana) for resource monitoring.
Automating Deployment
Manual deployments are error prone. Consider using:
- Fabric or Invoke: Python-based remote execution.
- Ansible: Automated provisioning and deployment playbooks.
- CI/CD Pipelines: GitHub Actions, GitLab CI, or Jenkins to build/test/deploy on push.
Example Fabric snippet (fabfile.py):
from fabric import task
@task
def deploy(c):
c.run('cd ~/your-django-project && git pull')
c.run('cd ~/your-django-project && source venv/bin/activate && pip install -r requirements.txt')
c.run('cd ~/your-django-project && source venv/bin/activate && python manage.py migrate --noinput')
c.run('cd ~/your-django-project && source venv/bin/activate && python manage.py collectstatic --noinput')
c.sudo('systemctl restart apache2')Troubleshooting Common Issues
- 500 Internal Server Error: Check Apache error log and Django logs; often a Python exception or misconfigured settings.
- Static files not loading: Verify Alias paths, directory permissions, and that you ran collectstatic.
- Permission denied: Ensure the Apache user (www-data) can read your project files and the WSGI socket.
- Certificate issues: Use sudo certbot certificates to inspect and certbot renew --dry-run to test renewals.
Conclusion
Deploying Django with Apache on Ubuntu involves several steps—from server preparation and package installation to configuring Apache virtual hosts and securing your application with HTTPS. While it may seem complex, each step builds on the previous to yield a reliable and maintainable production environment. Once set up, consider automating your deployments and continuously monitoring your server for performance and security.
By following this guide, you can confidently deploy any Django project onto an Ubuntu server using Apache and serve your application to the world in a secure, efficient manner. Happy deploying!