Why This Stack?
This architecture separates concerns for optimal performance and security:
Django
Handles your application logic, URL routing, templates, and ORM.
Gunicorn
Manages multiple worker processes for concurrent request handling.
Nginx
Serves static files efficiently and acts as a reverse proxy.
Prerequisites & VPS Selection
📋 Requirements: Ubuntu 22.04/24.04 LTS, root or sudo access, a domain name pointed to your VPS IP, and your Django project ready for deployment.
Initial Server Setup
Connect to your RamNode VPS and update the system:
ssh root@your_server_ip
apt update && apt upgrade -yCreate a dedicated user for running your Django application (security best practice):
adduser django
usermod -aG sudo django
su - djangoInstall Required Packages
Install Python, pip, Nginx, and other essential packages:
sudo apt install python3 python3-pip python3-venv nginx curl git -yVerify the installations:
python3 --version
nginx -vSet Up Your Django Project
Create a directory structure and set up a Python virtual environment:
mkdir -p ~/apps/myproject
cd ~/apps/myproject
python3 -m venv venv
source venv/bin/activateClone your existing project or create a new one:
# Clone existing project
git clone https://github.com/yourusername/your-django-project.git .
# Or create a new project
pip install django
django-admin startproject myproject .Install project dependencies:
pip install -r requirements.txt
pip install gunicornConfigure Django for Production
Update your Django settings for production:
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# Security settings
DEBUG = False
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com', 'your_server_ip']
# Static files configuration
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
# Media files configuration
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Security headers
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True🔐 Security Note: Never hardcode your SECRET_KEY in production. Use environment variables. Generate a new key with:
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"Collect static files and run migrations:
python manage.py collectstatic --noinput
python manage.py migrateConfigure Gunicorn
Test that Gunicorn can serve your application:
cd ~/apps/myproject
source venv/bin/activate
gunicorn --bind 0.0.0.0:8000 myproject.wsgi:applicationIf this works, press Ctrl+C to stop. Now create a Gunicorn configuration file:
import multiprocessing
# Binding
bind = "unix:/run/gunicorn/myproject.sock"
# Worker processes
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
# Logging
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info"
# Process naming
proc_name = "myproject"
# Security
limit_request_line = 4094
limit_request_fields = 100
limit_request_field_size = 8190Create the necessary directories:
sudo mkdir -p /run/gunicorn /var/log/gunicorn
sudo chown django:django /run/gunicorn /var/log/gunicornCreate Systemd Services
Create a systemd socket file for socket activation:
sudo nano /etc/systemd/system/gunicorn.socket[Unit]
Description=Gunicorn socket for myproject
[Socket]
ListenStream=/run/gunicorn/myproject.sock
SocketUser=www-data
SocketGroup=www-data
SocketMode=0660
[Install]
WantedBy=sockets.targetCreate the Gunicorn service file:
sudo nano /etc/systemd/system/gunicorn.service[Unit]
Description=Gunicorn daemon for myproject
Requires=gunicorn.socket
After=network.target
[Service]
Type=notify
User=django
Group=django
RuntimeDirectory=gunicorn
WorkingDirectory=/home/django/apps/myproject
Environment="DJANGO_SECRET_KEY=your-secret-key-here"
Environment="DJANGO_SETTINGS_MODULE=myproject.settings"
ExecStart=/home/django/apps/myproject/venv/bin/gunicorn \
--config /home/django/apps/myproject/gunicorn.conf.py \
myproject.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetEnable and start the services:
sudo systemctl daemon-reload
sudo systemctl enable gunicorn.socket gunicorn.service
sudo systemctl start gunicorn.socket
sudo systemctl start gunicorn.serviceVerify the services are running:
sudo systemctl status gunicorn.socket
sudo systemctl status gunicorn.service
curl --unix-socket /run/gunicorn/myproject.sock localhostConfigure Nginx
Create an Nginx server block for your Django application:
sudo nano /etc/nginx/sites-available/myprojectupstream django_app {
server unix:/run/gunicorn/myproject.sock fail_timeout=0;
}
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com;
# Redirect HTTP to HTTPS (uncomment after SSL setup)
# return 301 https://$server_name$request_uri;
client_max_body_size 10M;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Static files
location /static/ {
alias /home/django/apps/myproject/staticfiles/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Media files
location /media/ {
alias /home/django/apps/myproject/media/;
expires 7d;
add_header Cache-Control "public";
}
# Favicon
location /favicon.ico {
alias /home/django/apps/myproject/staticfiles/favicon.ico;
access_log off;
log_not_found off;
}
# Proxy to Gunicorn
location / {
proxy_set_header Host $http_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;
proxy_redirect off;
proxy_pass http://django_app;
}
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}Enable the site and test the configuration:
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginxSecure with SSL (Let's Encrypt)
Install Certbot and obtain a free SSL certificate:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.comVerify auto-renewal is working:
sudo certbot renew --dry-run💡 Tip: After SSL is configured, update your Django settings to set SECURE_SSL_REDIRECT = True and uncomment the HTTP to HTTPS redirect in your Nginx configuration.
Configure the Firewall
Set up UFW (Uncomplicated Firewall) to secure your server:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw statusDatabase Configuration (PostgreSQL)
For production, PostgreSQL is recommended over SQLite. Install and configure:
sudo apt install postgresql postgresql-contrib libpq-dev -y
sudo systemctl start postgresql
sudo systemctl enable postgresqlCreate a database and user for your Django application:
sudo -u postgres psql
CREATE DATABASE myproject;
CREATE USER djangouser WITH PASSWORD 'secure_password_here';
ALTER ROLE djangouser SET client_encoding TO 'utf8';
ALTER ROLE djangouser SET default_transaction_isolation TO 'read committed';
ALTER ROLE djangouser SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE myproject TO djangouser;
\qInstall the PostgreSQL adapter and update Django settings:
source ~/apps/myproject/venv/bin/activate
pip install psycopg2-binaryDATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'myproject',
'USER': 'djangouser',
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': 'localhost',
'PORT': '5432',
}
}Monitoring and Logging
Set up log rotation to prevent logs from consuming disk space:
sudo nano /etc/logrotate.d/gunicorn/var/log/gunicorn/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 django django
sharedscripts
postrotate
systemctl reload gunicorn > /dev/null 2>&1 || true
endscript
}Useful commands for monitoring your deployment:
Gunicorn Logs
sudo tail -f /var/log/gunicorn/error.log
sudo tail -f /var/log/gunicorn/access.logNginx Logs
sudo tail -f /var/log/nginx/error.log
sudo tail -f /var/log/nginx/access.log# Check service status
sudo systemctl status gunicorn nginx
# View Django application logs
journalctl -u gunicorn -fMaintenance and Updates
Deploying updates to your Django application:
cd ~/apps/myproject
source venv/bin/activate
git pull origin main
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput
sudo systemctl restart gunicornFor graceful reloads without dropping connections (zero-downtime):
sudo systemctl reload gunicornTroubleshooting Common Issues
| Issue | Solution |
|---|---|
| 502 Bad Gateway | Check Gunicorn is running: sudo systemctl status gunicorn. Verify socket permissions. |
| Static files 404 | Run collectstatic, verify STATIC_ROOT path matches Nginx alias. |
| Permission denied | Check ownership: chown -R django:django ~/apps/myproject |
| Socket not found | Start socket: sudo systemctl start gunicorn.socket |
| Database errors | Verify PostgreSQL is running, check credentials and migrations. |
Performance Tuning
Optimize Gunicorn workers based on your RamNode VPS specifications. The formula workers = (2 × CPU cores) + 1 is a good starting point.
For memory-constrained environments, reduce workers and use --max-requests to prevent memory leaks:
# For a 2-core VPS with 4GB RAM
workers = 5
max_requests = 1000
max_requests_jitter = 50Enable Nginx gzip compression for better performance:
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json
application/javascript application/xml+rss
application/atom+xml image/svg+xml;Deployment Complete!
You now have a production-ready Django deployment on your RamNode VPS. This setup provides a solid foundation that can handle significant traffic while remaining maintainable and secure.
As your application grows, consider adding Redis for caching and Celery for background task processing. For additional resources, visit the official documentation for Django, Gunicorn, and Nginx.
Need More Compute Power?
RamNode offers easy VPS scaling to meet your growing needs.
Security Checklist
- ✅ DEBUG = False - Never run with DEBUG enabled in production
- ✅ Use environment variables - Never hardcode secrets in settings.py
- ✅ Enable HTTPS - Use Let's Encrypt for free SSL certificates
- ✅ Configure firewall - Only allow necessary ports (22, 80, 443)
- ✅ Keep software updated - Regular
apt update && apt upgrade - ✅ Use separate database user - Don't use postgres superuser for Django
- ✅ Enable security headers - X-Frame-Options, X-Content-Type-Options, etc.
