When running Django applications via mod_wsgi on a 4GB RAM Ubuntu server, memory becomes the primary limiting factor. Each WSGI process typically consumes between 50-100MB of RAM (depending on your application's memory footprint). With two production-grade Django sites sharing resources, we need careful allocation.
For memory-bound systems, use this formula:
Max Processes = (Total RAM - System Reserve) / Average Process Memory Footprint
Recommended Processes = Max Processes * 0.8 (safety buffer)
For our 4GB system:
(4000MB - 1000MB system) / 80MB per process ≈ 37 processes max
37 * 0.8 ≈ 29 processes (shared between both sites)
Assuming equal traffic (similar to ServerFault/SuperUser):
# Site 1 configuration
WSGIDaemonProcess serverfault.com \
user=www-data \
group=www-data \
processes=12 \
threads=5 \
maximum-requests=1000
# Site 2 configuration
WSGIDaemonProcess superuser.com \
user=www-data \
group=www-data \
processes=12 \
threads=5 \
maximum-requests=1000
Consider these additional parameters for production environments:
WSGIDaemonProcess example.com \
processes=10 \
threads=15 \
display-name=%{GROUP} \
inactivity-timeout=300 \
queue-timeout=45 \
socket-timeout=60
Implement monitoring to validate your configuration:
# Install mod_status
apt install apache2-utils
# Sample mod_wsgi status configuration
<Location /server-status>
SetHandler server-status
Require host localhost
</Location>
Key metrics to watch:
- Memory usage per process (pmap -x <pid>)
- Request queue times
- CPU wait states
For critical production environments:
<VirtualHost *:80>
WSGIDaemonProcess primary \
processes=8 \
threads=25 \
umask=0002 \
python-path=/opt/apps/myapp:/opt/apps/myapp/venv/lib/python3.10/site-packages
WSGIProcessGroup primary
WSGIApplicationGroup %{GLOBAL}
WSGIRestrictProcess primary
WSGIRestrictStdin On
WSGIRestrictStdout On
ErrorLog /var/log/apache2/django_error.log
CustomLog /var/log/apache2/django_access.log combined
</VirtualHost>
When running Django applications through mod_wsgi, the WSGIDaemonProcess
directive is crucial for performance tuning. The processes
parameter determines how many Python interpreter instances will handle requests for your application.
A typical Django process on mod_wsgi consumes between 50MB-150MB RAM depending on:
- Application complexity
- Libraries loaded
- Middleware configuration
- Database connection pooling
# Example configuration for a production Django site
WSGIDaemonProcess example.com \
user=www-data \
group=www-data \
processes=4 \
threads=15 \
python-path=/path/to/venv/lib/python3.8/site-packages
For a 4GB server running two Django sites with ServerFault/SuperUser traffic levels:
- Reserve 1GB for OS and other services
- Allocate remaining 3GB for both sites
- Assuming 100MB per process (average for well-optimized Django)
- Each site gets 1.5GB (1500MB/100MB = 15 processes total)
For two equally important sites:
# Site 1 configuration
WSGIDaemonProcess serverfault.com \
user=www-data \
group=www-data \
processes=6 \
threads=5 \
python-home=/path/to/venv
# Site 2 configuration
WSGIDaemonProcess superuser.com \
user=www-data \
group=www-data \
processes=6 \
threads=5 \
python-home=/path/to/venv
Consider implementing these optimizations:
Thread-based scaling:
# Fewer processes with more threads can reduce memory overhead
WSGIDaemonProcess high_traffic.com \
user=www-data \
group=www-data \
processes=3 \
threads=25
Request timeout settings:
WSGIScriptAlias / /path/to/wsgi.py \
process-group=serverfault.com \
application-group=%{GLOBAL} \
request-timeout=600
Use these commands to monitor memory usage:
# Check WSGI process memory
ps -eo pid,user,args,%mem,rss --sort=-rss | grep wsgi
# Apache server-status with mod_status
http://localhost/server-status?auto