Deploying Django applications on Linux servers provides maximum control, performance, and cost-effectiveness. This chapter covers comprehensive server setup, security hardening, service configuration, and deployment automation for Ubuntu, CentOS, and other Linux distributions.
#!/bin/bash
# scripts/server_setup.sh - Initial server setup script
set -e
# Update system packages
echo "🔄 Updating system packages..."
apt update && apt upgrade -y
# Install essential packages
echo "📦 Installing essential packages..."
apt install -y \
curl \
wget \
git \
vim \
htop \
unzip \
software-properties-common \
apt-transport-https \
ca-certificates \
gnupg \
lsb-release \
fail2ban \
ufw \
logrotate \
supervisor
# Install Python and development tools
echo "🐍 Installing Python and development tools..."
apt install -y \
python3 \
python3-pip \
python3-venv \
python3-dev \
build-essential \
libpq-dev \
libjpeg-dev \
libpng-dev \
libfreetype6-dev \
libxml2-dev \
libxslt1-dev \
libffi-dev \
libssl-dev
# Install database and cache servers
echo "🗄️ Installing PostgreSQL and Redis..."
apt install -y postgresql postgresql-contrib redis-server
# Install web server
echo "🌐 Installing Nginx..."
apt install -y nginx
# Create application user
echo "👤 Creating application user..."
useradd --system --shell /bin/bash --home /opt/django_app --create-home django
usermod -a -G www-data django
# Set up directory structure
echo "📁 Setting up directory structure..."
mkdir -p /opt/django_app/{logs,static,media,backups}
mkdir -p /var/log/{django,gunicorn,nginx}
chown -R django:django /opt/django_app
chown -R django:django /var/log/django
chown -R django:django /var/log/gunicorn
echo "✅ Server setup completed!"
#!/bin/bash
# scripts/security_hardening.sh - Security hardening script
set -e
echo "🔒 Starting security hardening..."
# Configure firewall
echo "🔥 Configuring UFW firewall..."
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
# Configure fail2ban
echo "🛡️ Configuring fail2ban..."
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
backend = systemd
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 6
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
[django-auth]
enabled = true
filter = django-auth
logpath = /var/log/django/django.log
maxretry = 5
EOF
# Create Django fail2ban filter
cat > /etc/fail2ban/filter.d/django-auth.conf << 'EOF'
[Definition]
failregex = ^.*\[.*\] WARNING.*Invalid login attempt.*from <HOST>
ignoreregex =
EOF
# Restart fail2ban
systemctl restart fail2ban
systemctl enable fail2ban
# Configure SSH security
echo "🔑 Hardening SSH configuration..."
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
cat > /etc/ssh/sshd_config << 'EOF'
Port 22
Protocol 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
UsePrivilegeSeparation yes
KeyRegenerationInterval 3600
ServerKeyBits 1024
SyslogFacility AUTH
LogLevel INFO
LoginGraceTime 120
PermitRootLogin no
StrictModes yes
RSAAuthentication yes
PubkeyAuthentication yes
IgnoreRhosts yes
RhostsRSAAuthentication no
HostbasedAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
PasswordAuthentication no
X11Forwarding no
X11DisplayOffset 10
PrintMotd no
PrintLastLog yes
TCPKeepAlive yes
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
UsePAM yes
AllowUsers django
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
EOF
systemctl restart sshd
# Set up automatic security updates
echo "🔄 Configuring automatic security updates..."
apt install -y unattended-upgrades
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Package-Blacklist {
};
Unattended-Upgrade::DevRelease "false";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
EOF
echo "✅ Security hardening completed!"
#!/bin/bash
# scripts/setup_postgresql.sh - PostgreSQL setup script
set -e
echo "🗄️ Setting up PostgreSQL..."
# Configure PostgreSQL
sudo -u postgres psql << 'EOF'
CREATE USER django_user WITH PASSWORD 'secure_password_here';
CREATE DATABASE django_app OWNER django_user;
GRANT ALL PRIVILEGES ON DATABASE django_app TO django_user;
ALTER USER django_user CREATEDB;
\q
EOF
# Configure PostgreSQL for production
cat > /etc/postgresql/*/main/postgresql.conf << 'EOF'
# Connection settings
listen_addresses = 'localhost'
port = 5432
max_connections = 100
# Memory settings
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 4MB
maintenance_work_mem = 64MB
# Checkpoint settings
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
# Logging
log_destination = 'stderr'
logging_collector = on
log_directory = 'pg_log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_age = 1d
log_rotation_size = 100MB
log_min_duration_statement = 1000
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
# Performance
random_page_cost = 1.1
effective_io_concurrency = 200
EOF
# Configure pg_hba.conf for security
cat > /etc/postgresql/*/main/pg_hba.conf << 'EOF'
# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
local all all peer
host all all 127.0.0.1/32 md5
host all all ::1/128 md5
host django_app django_user 127.0.0.1/32 md5
EOF
# Restart PostgreSQL
systemctl restart postgresql
systemctl enable postgresql
echo "✅ PostgreSQL setup completed!"
#!/bin/bash
# scripts/setup_redis.sh - Redis setup script
set -e
echo "🔴 Setting up Redis..."
# Configure Redis
cat > /etc/redis/redis.conf << 'EOF'
# Network
bind 127.0.0.1
port 6379
timeout 300
tcp-keepalive 60
# General
daemonize yes
supervised systemd
pidfile /var/run/redis/redis-server.pid
loglevel notice
logfile /var/log/redis/redis-server.log
databases 16
# Snapshotting
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis
# Security
requirepass your_redis_password_here
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command DEBUG ""
rename-command CONFIG "CONFIG_b835c3f8a5d9e7f2"
# Memory management
maxmemory 256mb
maxmemory-policy allkeys-lru
maxmemory-samples 5
# Append only file
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# Slow log
slowlog-log-slower-than 10000
slowlog-max-len 128
EOF
# Set proper permissions
chown redis:redis /etc/redis/redis.conf
chmod 640 /etc/redis/redis.conf
# Restart Redis
systemctl restart redis-server
systemctl enable redis-server
echo "✅ Redis setup completed!"
#!/bin/bash
# scripts/deploy.sh - Main deployment script
set -e
# Configuration
APP_NAME="django_app"
APP_USER="django"
APP_DIR="/opt/$APP_NAME"
REPO_URL="https://github.com/yourusername/your-django-app.git"
BRANCH="main"
PYTHON_VERSION="3.9"
echo "🚀 Starting deployment of $APP_NAME..."
# Switch to application user
sudo -u $APP_USER bash << EOF
set -e
# Navigate to application directory
cd $APP_DIR
# Backup current version if exists
if [ -d "src" ]; then
echo "📦 Creating backup of current version..."
cp -r src src_backup_\$(date +%Y%m%d_%H%M%S)
fi
# Clone or update repository
if [ -d "src" ]; then
echo "🔄 Updating repository..."
cd src
git fetch origin
git reset --hard origin/$BRANCH
else
echo "📥 Cloning repository..."
git clone -b $BRANCH $REPO_URL src
cd src
fi
# Create virtual environment if it doesn't exist
if [ ! -d "../venv" ]; then
echo "🐍 Creating virtual environment..."
python3 -m venv ../venv
fi
# Activate virtual environment
source ../venv/bin/activate
# Upgrade pip
pip install --upgrade pip
# Install dependencies
echo "📦 Installing dependencies..."
pip install -r requirements.txt
# Run Django management commands
echo "🔧 Running Django management commands..."
python manage.py collectstatic --noinput
python manage.py migrate --noinput
# Create superuser if it doesn't exist
python manage.py shell << 'PYTHON'
from django.contrib.auth import get_user_model
User = get_user_model()
if not User.objects.filter(username='admin').exists():
User.objects.create_superuser('admin', 'admin@example.com', 'secure_password_here')
print("Superuser created")
else:
print("Superuser already exists")
PYTHON
# Run deployment checks
python manage.py check --deploy
echo "✅ Application deployment completed!"
EOF
# Restart services
echo "🔄 Restarting services..."
systemctl restart $APP_NAME
systemctl restart nginx
# Run post-deployment checks
echo "🔍 Running post-deployment checks..."
sleep 5
# Check if application is responding
if curl -f -s http://localhost:8000/health/ > /dev/null; then
echo "✅ Application is responding correctly!"
else
echo "❌ Application health check failed!"
exit 1
fi
echo "🎉 Deployment completed successfully!"
# /etc/systemd/system/django_app.service
[Unit]
Description=Django App Gunicorn daemon
Requires=django_app.socket
After=network.target postgresql.service redis-server.service
[Service]
Type=notify
User=django
Group=django
RuntimeDirectory=gunicorn
WorkingDirectory=/opt/django_app/src
ExecStart=/opt/django_app/venv/bin/gunicorn \
--config /opt/django_app/gunicorn.conf.py \
--pid /run/gunicorn/django_app.pid \
myproject.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5
# Environment
Environment=DJANGO_SETTINGS_MODULE=myproject.settings.production
EnvironmentFile=/opt/django_app/.env
# Security
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/opt/django_app /var/log/gunicorn /run/gunicorn /tmp
# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/django_app.socket
[Unit]
Description=Django App Gunicorn socket
[Socket]
ListenStream=/run/gunicorn/django_app.sock
SocketUser=www-data
SocketMode=600
[Install]
WantedBy=sockets.target
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
# Basic Settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
# MIME
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# Gzip Settings
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Rate Limiting
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=general:10m rate=200r/m;
# Connection Limiting
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
# Buffer Sizes
client_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
# Timeouts
client_body_timeout 12;
client_header_timeout 12;
send_timeout 10;
# Include site configurations
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
# /etc/nginx/sites-available/django_app
upstream django_app {
server unix:/run/gunicorn/django_app.sock fail_timeout=0;
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
# HTTPS Server
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# Modern SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
# Security Headers
add_header X-Frame-Options DENY 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;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';" always;
# Root and index
root /opt/django_app/src;
# Rate limiting
limit_req zone=general burst=20 nodelay;
limit_conn conn_limit_per_ip 10;
# Static files
location /static/ {
alias /opt/django_app/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
# Compression
gzip_static on;
# Security
location ~* \.(py|pyc|pyo|pyd|pdb|sql|sqlite|db)$ {
deny all;
}
}
# Media files
location /media/ {
alias /opt/django_app/media/;
expires 30d;
add_header Cache-Control "public";
# Security
location ~* \.(py|pyc|pyo|pyd|pdb|sql|sqlite|db|php|pl|sh)$ {
deny all;
}
}
# Admin rate limiting
location /admin/login/ {
limit_req zone=login burst=5 nodelay;
proxy_pass http://django_app;
include /etc/nginx/proxy_params;
}
# API rate limiting
location /api/ {
limit_req zone=api burst=50 nodelay;
proxy_pass http://django_app;
include /etc/nginx/proxy_params;
}
# Health check (no rate limiting)
location /health/ {
proxy_pass http://django_app;
include /etc/nginx/proxy_params;
access_log off;
}
# Main application
location / {
proxy_pass http://django_app;
include /etc/nginx/proxy_params;
}
# Error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /404.html {
root /opt/django_app/templates/errors;
internal;
}
location = /50x.html {
root /opt/django_app/templates/errors;
internal;
}
}
# /etc/nginx/proxy_params
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_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
proxy_redirect off;
#!/bin/bash
# scripts/setup_ssl.sh - SSL certificate setup
set -e
echo "🔒 Setting up SSL certificates..."
# Install Certbot
apt install -y certbot python3-certbot-nginx
# Obtain SSL certificate
certbot --nginx -d yourdomain.com -d www.yourdomain.com \
--non-interactive \
--agree-tos \
--email admin@yourdomain.com \
--redirect
# Set up automatic renewal
cat > /etc/cron.d/certbot << 'EOF'
0 12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew --nginx
EOF
# Test renewal
certbot renew --dry-run
echo "✅ SSL certificates configured!"
# /etc/logrotate.d/django_app
/var/log/django/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 django django
postrotate
systemctl reload django_app
endscript
}
/var/log/gunicorn/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 django django
postrotate
systemctl reload django_app
endscript
}
/var/log/nginx/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 www-data adm
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi
endscript
postrotate
systemctl reload nginx
endscript
}
#!/bin/bash
# scripts/monitor_system.sh - System monitoring script
# Configuration
ALERT_EMAIL="admin@yourdomain.com"
CPU_THRESHOLD=80
MEMORY_THRESHOLD=80
DISK_THRESHOLD=85
# Check CPU usage
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | awk -F'%' '{print $1}')
if (( $(echo "$CPU_USAGE > $CPU_THRESHOLD" | bc -l) )); then
echo "High CPU usage: $CPU_USAGE%" | mail -s "Server Alert: High CPU" $ALERT_EMAIL
fi
# Check memory usage
MEMORY_USAGE=$(free | grep Mem | awk '{printf("%.1f", $3/$2 * 100.0)}')
if (( $(echo "$MEMORY_USAGE > $MEMORY_THRESHOLD" | bc -l) )); then
echo "High memory usage: $MEMORY_USAGE%" | mail -s "Server Alert: High Memory" $ALERT_EMAIL
fi
# Check disk usage
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $DISK_USAGE -gt $DISK_THRESHOLD ]; then
echo "High disk usage: $DISK_USAGE%" | mail -s "Server Alert: High Disk Usage" $ALERT_EMAIL
fi
# Check if Django app is running
if ! systemctl is-active --quiet django_app; then
echo "Django application is not running!" | mail -s "Server Alert: Django App Down" $ALERT_EMAIL
fi
# Check if Nginx is running
if ! systemctl is-active --quiet nginx; then
echo "Nginx is not running!" | mail -s "Server Alert: Nginx Down" $ALERT_EMAIL
fi
# Check database connectivity
if ! sudo -u django psql -d django_app -c "SELECT 1;" > /dev/null 2>&1; then
echo "Database connection failed!" | mail -s "Server Alert: Database Connection Failed" $ALERT_EMAIL
fi
This comprehensive Linux server deployment guide provides everything needed to deploy Django applications securely and efficiently on Linux servers.
Using WSGI and ASGI Servers
Production Django applications require robust application servers to handle HTTP requests efficiently. This chapter covers configuring and deploying Django applications with WSGI servers (Gunicorn, uWSGI) for traditional synchronous applications and ASGI servers (Uvicorn, Daphne, Hypercorn) for asynchronous applications with WebSocket support.
Using Docker
Docker containerization provides consistent, portable, and scalable deployment environments for Django applications. This chapter covers Docker fundamentals, multi-stage builds, container orchestration, and production-ready Docker configurations for Django applications.