Django projenizi geliştirme sunucusunda çalıştırmak kolaydır, ama production ortamına taşıma noktasında işler karmaşıklaşmaya başlar. python manage.py runserver komutu production için kesinlikle uygun değil; bu komut tek thread çalışır, güvenlik açıkları barındırır ve yük altında kolayca çöker. Apache ve mod_wsgi kombinasyonu ise yıllardır production ortamlarında kanıtlanmış, kararlı ve güçlü bir çözüm sunar. Bu yazıda sıfırdan başlayarak Django uygulamanızı Apache ile nasıl servis edeceğinizi, olası tuzakları ve gerçek dünya senaryolarını ele alacağız.
WSGI Nedir ve Neden Önemli?
WSGI (Web Server Gateway Interface), Python web uygulamaları ile web sunucuları arasındaki standart arayüzü tanımlayan bir spesifikasyondur. Apache gibi bir web sunucusu doğrudan Python kodunu nasıl çalıştıracağını bilmez. WSGI bu boşluğu doldurur ve Apache’nin Django uygulamanızla konuşabilmesini sağlar.
mod_wsgi ise Apache için geliştirilmiş bir modüldür ve Python WSGI uygulamalarını doğrudan Apache içinde çalıştırmanıza olanak tanır. İki farklı modda çalışabilir:
- Embedded Mode: WSGI uygulaması Apache worker process içinde çalışır. Basit kurulum ama esneklik azdır.
- Daemon Mode: WSGI uygulaması ayrı bir process olarak çalışır. Production için önerilen moddur, daha iyi izolasyon ve kaynak yönetimi sağlar.
Daemon mode’u tercih etmenizin nedenleri şunlardır: uygulamanızı Apache’yi yeniden başlatmadan reload edebilirsiniz, her uygulama için farklı kullanıcı hakları tanımlayabilirsiniz ve bellek kullanımını daha iyi kontrol edebilirsiniz.
Sistem Gereksinimleri ve Kurulum
Bu yazıda Ubuntu 22.04 LTS üzerinde çalışacağız. Önce sisteminizi güncelleyin ve gerekli paketleri kurun.
sudo apt update && sudo apt upgrade -y
sudo apt install apache2 apache2-dev python3 python3-pip python3-venv libapache2-mod-wsgi-py3
Apache kurulumu tamamlandıktan sonra mod_wsgi modülünü aktif hâle getirin:
sudo a2enmod wsgi
sudo systemctl restart apache2
Kurulumun başarılı olduğunu doğrulamak için Apache modül listesine bakabilirsiniz:
apache2ctl -M | grep wsgi
Çıktıda wsgi_module (shared) görüyorsanız modül başarıyla yüklenmiştir.
Django Projesi Hazırlama
Production sunucusunda uygulamayı düzgün bir dizin yapısında tutmak kritik önemdedir. Standart bir yapı oluşturalım:
sudo mkdir -p /var/www/myproject
sudo chown $USER:$USER /var/www/myproject
cd /var/www/myproject
# Virtual environment oluştur
python3 -m venv venv
source venv/bin/activate
# Django ve diğer bağımlılıkları kur
pip install django gunicorn
pip install -r requirements.txt # Mevcut requirements dosyanız varsa
# Yeni proje oluşturuyorsanız
django-admin startproject mysite .
Dizin yapınız şu şekilde görünmelidir:
/var/www/myproject/– Ana proje dizini/var/www/myproject/venv/– Python sanal ortamı/var/www/myproject/mysite/– Django proje paketi/var/www/myproject/mysite/settings.py– Ayar dosyası/var/www/myproject/mysite/wsgi.py– WSGI giriş noktası/var/www/myproject/manage.py– Django yönetim aracı
Django Settings Production Ayarları
settings.py dosyasını production için yapılandırmanız gerekiyor. Bu adımı atlayan pek çok sysadmin sonradan başını ağrıtmıştır.
# /var/www/myproject/mysite/settings.py
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# Güvenlik: SECRET_KEY ortam değişkeninden oku
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'fallback-only-for-dev')
# Production'da mutlaka False olmalı
DEBUG = False
# Sunucu IP veya domain adınızı girin
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com', '192.168.1.100']
# Static dosyalar için
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
# Media dosyalar için
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Veritabanı ayarları
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', 'mydb'),
'USER': os.environ.get('DB_USER', 'myuser'),
'PASSWORD': os.environ.get('DB_PASSWORD', ''),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
Static dosyaları toplamak için şu komutu çalıştırın:
cd /var/www/myproject
source venv/bin/activate
python manage.py collectstatic --noinput
python manage.py migrate
Apache VirtualHost Yapılandırması
Şimdi asıl kritik adıma geliyoruz. Apache VirtualHost dosyasını oluşturacağız. Bu yapılandırma dosyası Apache’ye Django uygulamanızı nasıl servis edeceğini söyler.
sudo nano /etc/apache2/sites-available/myproject.conf
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
ServerAdmin [email protected]
# mod_wsgi daemon mode yapılandırması
WSGIDaemonProcess myproject python-home=/var/www/myproject/venv
python-path=/var/www/myproject
processes=2
threads=15
display-name=%{GROUP}
WSGIProcessGroup myproject
WSGIScriptAlias / /var/www/myproject/mysite/wsgi.py
# WSGI dosyasına erişim izni
<Directory /var/www/myproject/mysite>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
# Static dosyalar için alias
Alias /static/ /var/www/myproject/staticfiles/
<Directory /var/www/myproject/staticfiles>
Require all granted
Options -Indexes
</Directory>
# Media dosyalar için alias
Alias /media/ /var/www/myproject/media/
<Directory /var/www/myproject/media>
Require all granted
Options -Indexes
</Directory>
# Log dosyaları
ErrorLog ${APACHE_LOG_DIR}/myproject_error.log
CustomLog ${APACHE_LOG_DIR}/myproject_access.log combined
# Güvenlik başlıkları
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set X-XSS-Protection "1; mode=block"
</VirtualHost>
WSGIDaemonProcess direktifindeki parametreleri açıklayalım:
- python-home: Virtual environment’ın yolu
- python-path: Django projenizin kök dizini
- processes: Kaç adet worker process çalışacak
- threads: Her process kaç thread kullanacak
- display-name:
psçıktısında görünecek isim
Siteyi aktif hâle getirip Apache’yi yeniden başlatın:
sudo a2ensite myproject.conf
sudo a2enmod headers
sudo systemctl reload apache2
Dosya İzinleri ve Güvenlik
Bu kısım çoğu kişinin gözden kaçırdığı ama production sorunlarının büyük bölümüne neden olan konudur. Apache, www-data kullanıcısı altında çalışır ve proje dosyalarınıza erişebilmesi gerekir.
# Proje dizini sahipliğini ayarla
sudo chown -R www-data:www-data /var/www/myproject
sudo chmod -R 755 /var/www/myproject
# Static ve media dizinlerine özel izin
sudo chmod -R 755 /var/www/myproject/staticfiles
sudo chmod -R 755 /var/www/myproject/media
# Log dosyaları için dizin oluştur
sudo mkdir -p /var/log/myproject
sudo chown www-data:www-data /var/log/myproject
Eğer SQLite kullanıyorsanız (production için önerilmez ama küçük projeler için kabul edilebilir) veritabanı dosyasının da yazılabilir olması gerekir:
sudo chown www-data:www-data /var/www/myproject/db.sqlite3
sudo chmod 664 /var/www/myproject/db.sqlite3
Önemli bir güvenlik notu: www-data kullanıcısının sadece ihtiyaç duyduğu dizinlere erişimi olmalı. Virtual environment dizinine yazma iznine ihtiyacı yoktur, sadece okuma yeterlidir. Bu prensibi ihmal etmek, bir güvenlik açığı durumunda saldırganın sisteminize daha kolay yerleşmesine zemin hazırlar.
Ortam Değişkenlerini Yönetme
Secret key, veritabanı şifreleri gibi hassas bilgileri kod içinde veya VirtualHost dosyasında tutmak doğru değil. Apache üzerinden ortam değişkeni tanımlamanın en temiz yolu SetEnv direktifini kullanmaktır.
sudo nano /etc/apache2/sites-available/myproject.conf
VirtualHost bloğu içine şunları ekleyin:
<VirtualHost *:80>
# ... diğer ayarlar ...
# Ortam değişkenleri
SetEnv DJANGO_SECRET_KEY "cok-gizli-ve-uzun-bir-anahtar-buraya"
SetEnv DB_NAME "mydb"
SetEnv DB_USER "myuser"
SetEnv DB_PASSWORD "guclu-sifre"
SetEnv DB_HOST "localhost"
SetEnv DJANGO_SETTINGS_MODULE "mysite.settings"
</VirtualHost>
Ancak bu yöntemin bir dezavantajı var: ortam değişkenleri Apache yapılandırma dosyasında açık metin olarak duruyor. Daha güvenli bir alternatif olarak /etc/environment veya systemd service dosyasını kullanabilirsiniz. Ya da bir .env dosyası oluşturup python-dotenv kütüphanesiyle okutabilirsiniz:
# /var/www/myproject/.env dosyası oluştur
sudo nano /var/www/myproject/.env
DJANGO_SECRET_KEY=cok-gizli-anahtar
DB_NAME=mydb
DB_USER=myuser
DB_PASSWORD=guclu-sifre
DJANGO_DEBUG=False
# .env dosyasının izinlerini kısıtla
sudo chown www-data:www-data /var/www/myproject/.env
sudo chmod 600 /var/www/myproject/.env
HTTPS ile SSL Yapılandırması
Production ortamında HTTP üzerinden çalışmak artık kabul edilemez. Let’s Encrypt ile ücretsiz SSL sertifikası alabilirsiniz:
sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
Certbot Apache yapılandırmanızı otomatik olarak düzenler ve şu satırları ekler. Manuel kontrol için:
sudo nano /etc/apache2/sites-available/myproject-le-ssl.conf
<VirtualHost *:443>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/yourdomain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.com/privkey.pem
# HSTS başlığı
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
WSGIDaemonProcess myproject_ssl python-home=/var/www/myproject/venv
python-path=/var/www/myproject
processes=2 threads=15
WSGIProcessGroup myproject_ssl
WSGIScriptAlias / /var/www/myproject/mysite/wsgi.py
# ... geri kalan ayarlar aynı ...
</VirtualHost>
HTTP’yi HTTPS’e yönlendirmek için HTTP VirtualHost bloğunu şu şekilde düzenleyin:
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
Redirect permanent / https://yourdomain.com/
</VirtualHost>
Sık Karşılaşılan Sorunlar ve Çözümleri
Yıllar içinde Apache-Django kurulumlarında karşılaşılan tipik sorunları ve çözümlerini paylaşalım.
500 Internal Server Error
İlk iş Apache hata loguna bakın:
sudo tail -f /var/log/apache2/myproject_error.log
# veya
sudo journalctl -u apache2 -f
En sık karşılaşılan 500 hatası sebebi yanlış python-path veya virtual environment yoludur. wsgi.py dosyanızın içeriğini kontrol edin:
# mysite/wsgi.py
import os
import sys
# Bu satır kritik
sys.path.insert(0, '/var/www/myproject')
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = get_wsgi_application()
Permission Denied Hataları
# www-data kullanıcısının dosyalara erişimini test et
sudo -u www-data python3 /var/www/myproject/manage.py check
Bu komut www-data olarak Django kontrol koşturur. Herhangi bir izin sorunu varsa burada görürsünüz.
Static Dosyalar Yüklenmiyor
Apache’nin static dosyalara erişimini doğrudan test edin:
curl -I http://yourdomain.com/static/admin/css/base.css
404 alıyorsanız STATIC_ROOT yolunun collectstatic sonrası doğru olduğundan emin olun. 403 alıyorsanız dizin izinlerine bakın.
Uygulama Değişikliklerinin Yansımaması
Daemon mode’da Python kodu değiştiğinde Apache’yi restart etmek zorunda değilsiniz. wsgi.py dosyasına touch atmak yeterlidir:
touch /var/www/myproject/mysite/wsgi.py
Bu komut dosyanın zaman damgasını günceller ve mod_wsgi daemon process’i yeniden başlatır. CI/CD pipeline’ınıza bu komutu ekleyebilirsiniz.
Performance Tuning
Orta ölçekli bir production kurulumu için makul başlangıç değerleri şunlardır. Sunucunuzun kaynaklarına göre bunları uyarlamanız gerekir:
WSGIDaemonProcess myproject
python-home=/var/www/myproject/venv
python-path=/var/www/myproject
processes=4
threads=10
maximum-requests=500
inactivity-timeout=300
request-timeout=60
connect-timeout=15
socket-timeout=60
queue-timeout=45
cpu-time-limit=60
memory-limit=536870912
display-name=myproject-wsgi
Parametreleri açıklayalım:
- processes: CPU çekirdeği sayısına eşit veya iki katı mantıklıdır
- threads: I/O ağırlıklı uygulamalar için artırın, CPU ağırlıklı için düşürün
- maximum-requests: Process kaç istek aldıktan sonra yeniden başlatılacak, memory leak’e karşı önlem
- inactivity-timeout: Process bu kadar saniye boşta kalırsa öldürülür
- memory-limit: Process bu kadar byte bellek kullanırsa öldürülür (512MB)
Apache’nin kendisi için de MPM worker veya event modülünü kullanmanızı öneririm. Prefork modülü mod_wsgi ile çalışır ama performans açısından idealden uzaktır:
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2
Loglama ve Monitoring
Production ortamında neyin döndüğünü takip edebilmek şarttır. Django tarafında loglama ayarlarını settings.py‘a ekleyin:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'WARNING',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/myproject/django.log',
'maxBytes': 1024 * 1024 * 50, # 50MB
'backupCount': 5,
'formatter': 'verbose',
},
},
'root': {
'handlers': ['file'],
'level': 'WARNING',
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'WARNING',
'propagate': False,
},
},
}
Apache erişim loglarını analiz etmek için goaccess aracını kullanabilirsiniz:
sudo apt install goaccess
goaccess /var/log/apache2/myproject_access.log -o /var/www/html/report.html --log-format=COMBINED
Gerçek Dünya Senaryosu: Çoklu Django Uygulaması
Tek sunucuda birden fazla Django uygulaması çalıştırmak yaygın bir ihtiyaçtır. Her uygulama için ayrı bir daemon process grubu tanımlamanız yeterlidir:
# /etc/apache2/sites-available/app1.conf
<VirtualHost *:443>
ServerName app1.yourdomain.com
WSGIDaemonProcess app1
python-home=/var/www/app1/venv
python-path=/var/www/app1
processes=2 threads=10
user=app1user group=app1group
WSGIProcessGroup app1
WSGIScriptAlias / /var/www/app1/config/wsgi.py
# ...
</VirtualHost>
# /etc/apache2/sites-available/app2.conf
<VirtualHost *:443>
ServerName app2.yourdomain.com
WSGIDaemonProcess app2
python-home=/var/www/app2/venv
python-path=/var/www/app2
processes=2 threads=10
user=app2user group=app2group
WSGIProcessGroup app2
WSGIScriptAlias / /var/www/app2/config/wsgi.py
# ...
</VirtualHost>
Her uygulama için ayrı sistem kullanıcısı (app1user, app2user) oluşturmak iyi bir pratiktir. Böylece bir uygulamada yaşanan güvenlik sorunu diğerini etkilemez.
Sonuç
Apache ve mod_wsgi kombinasyonu, Django uygulamalarını production ortamında servis etmek için olgun ve güvenilir bir çözüm sunuyor. Bu yazıda ele aldığımız adımları özetleyecek olursak: önce mod_wsgi’yi doğru şekilde kurmak ve daemon mode’u tercih etmek, Django’nun production ayarlarını eksiksiz yapmak, dosya izinlerine önem vermek ve loglama altyapısını kurmak başarılı bir kurulumun temelini oluşturuyor.
Nginx + Gunicorn kombinasyonu son yıllarda daha popüler hâle gelse de Apache + mod_wsgi hâlâ pek çok production ortamında başarıyla çalışıyor. Özellikle halihazırda Apache kullanan bir altyapınız varsa mod_wsgi eklemek en pratik yol. Gunicorn veya uWSGI gibi alternatifler de masada ama bunlar ayrı bir yazının konusu.
Kurulumu tamamladıktan sonra ilk yapmanız gereken şey yük testi çalıştırmak ve maximum-requests, processes, threads değerlerini kendi uygulamanızın profiline göre ayarlamaktır. Her Django uygulaması farklı davranır ve evrensel bir reçete yoktur. Apache erişim loglarını, Django uygulama loglarını ve sistem kaynak kullanımını düzenli takip etmek ise uzun vadede sorunsuz çalışmanın anahtarıdır.