HTTPS в Django

Настройка HTTPS обеспечивает безопасность вашего Django приложения, защищая передачу данных между клиентом и сервером. Это критически важно для защиты пользовательских данных и соответствия современным стандартам безопасности.

Зачем нужен HTTPS

HTTPS обеспечивает:

  • Шифрование данных между клиентом и сервером
  • Аутентификацию сервера (защита от атак "человек посередине")
  • Целостность данных (защита от подмены)
  • Соответствие требованиям GDPR и других регуляторов
  • Лучшие позиции в поисковых системах
  • Доверие пользователей

Получение SSL сертификата

1. Let's Encrypt (бесплатно)

 1# Установка Certbot
 2sudo apt update
 3sudo apt install certbot python3-certbot-nginx
 4
 5# Получение сертификата для домена
 6sudo certbot --nginx -d example.com -d www.example.com
 7
 8# Автоматическое обновление
 9sudo crontab -e
10# Добавляем строку для автоматического обновления
110 12 * * * /usr/bin/certbot renew --quiet

2. Коммерческие сертификаты

1# Установка сертификата от провайдера
2sudo cp your_certificate.crt /etc/ssl/certs/
3sudo cp your_private_key.key /etc/ssl/private/
4
5# Установка прав доступа
6sudo chmod 644 /etc/ssl/certs/your_certificate.crt
7sudo chmod 600 /etc/ssl/private/your_private_key.key

3. Самоподписанный сертификат (для разработки)

1# Генерация приватного ключа
2openssl genrsa -out private.key 2048
3
4# Генерация сертификата
5openssl req -new -x509 -key private.key -out certificate.crt -days 365 \
6    -subj "/C=RU/ST=Moscow/L=Moscow/O=Company/CN=localhost"
7
8# Проверка сертификата
9openssl x509 -in certificate.crt -text -noout

Настройка Django для HTTPS

1. Основные настройки безопасности

 1# settings.py
 2
 3# HTTPS настройки
 4SECURE_SSL_REDIRECT = True  # Редирект HTTP на HTTPS
 5SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
 6
 7# HSTS (HTTP Strict Transport Security)
 8SECURE_HSTS_SECONDS = 31536000  # 1 год
 9SECURE_HSTS_INCLUDE_SUBDOMAINS = True
10SECURE_HSTS_PRELOAD = True
11
12# Дополнительные настройки безопасности
13SECURE_CONTENT_TYPE_NOSNIFF = True
14SECURE_BROWSER_XSS_FILTER = True
15SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin'
16
17# Настройки сессий и куки
18SESSION_COOKIE_SECURE = True
19CSRF_COOKIE_SECURE = True
20CSRF_COOKIE_HTTPONLY = True
21SESSION_COOKIE_HTTPONLY = True
22SESSION_COOKIE_SAMESITE = 'Lax'
23
24# CSP (Content Security Policy)
25CSP_DEFAULT_SRC = ("'self'",)
26CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
27CSP_SCRIPT_SRC = ("'self'",)
28CSP_IMG_SRC = ("'self'", "data:", "https:")
29
30# Настройки для продакшена
31DEBUG = False
32ALLOWED_HOSTS = ['example.com', 'www.example.com']

2. Условные настройки для разных окружений

 1# settings/base.py
 2import os
 3
 4# Базовые настройки
 5SECURE_SSL_REDIRECT = False  # По умолчанию отключено
 6SECURE_HSTS_SECONDS = 0
 7
 8# settings/production.py
 9from .base import *
10
11# Продакшен настройки
12SECURE_SSL_REDIRECT = True
13SECURE_HSTS_SECONDS = 31536000
14SECURE_HSTS_INCLUDE_SUBDOMAINS = True
15SECURE_HSTS_PRELOAD = True
16
17# Настройки для прокси (если используешь Nginx/Apache)
18SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
19
20# settings/staging.py
21from .base import *
22
23# Staging настройки (промежуточные)
24SECURE_SSL_REDIRECT = True
25SECURE_HSTS_SECONDS = 3600  # 1 час для тестирования
26SECURE_HSTS_INCLUDE_SUBDOMAINS = False
27SECURE_HSTS_PRELOAD = False

3. Настройки для Docker

 1# settings/docker.py
 2import os
 3
 4# Настройки для Docker контейнеров
 5SECURE_SSL_REDIRECT = os.environ.get('SECURE_SSL_REDIRECT', 'False').lower() == 'true'
 6SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
 7
 8# Настройки для reverse proxy
 9USE_X_FORWARDED_HOST = True
10USE_X_FORWARDED_PORT = True
11
12# Настройки для Cloudflare
13SECURE_PROXY_SSL_HEADER = ('HTTP_CF_VISITOR', '{"scheme":"https"}')

Настройка веб-сервера

1. Nginx конфигурация

 1# /etc/nginx/sites-available/example.com
 2server {
 3    listen 80;
 4    server_name example.com www.example.com;
 5
 6    # Редирект HTTP на HTTPS
 7    return 301 https://$server_name$request_uri;
 8}
 9
10server {
11    listen 443 ssl http2;
12    server_name example.com www.example.com;
13
14    # SSL сертификаты
15    ssl_certificate /etc/ssl/certs/example.com.crt;
16    ssl_certificate_key /etc/ssl/private/example.com.key;
17
18    # SSL настройки
19    ssl_protocols TLSv1.2 TLSv1.3;
20    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
21    ssl_prefer_server_ciphers off;
22    ssl_session_cache shared:SSL:10m;
23    ssl_session_timeout 10m;
24
25    # HSTS заголовки
26    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
27
28    # Дополнительные заголовки безопасности
29    add_header X-Frame-Options DENY;
30    add_header X-Content-Type-Options nosniff;
31    add_header X-XSS-Protection "1; mode=block";
32    add_header Referrer-Policy "strict-origin-when-cross-origin";
33
34    # Проксирование к Django
35    location / {
36        proxy_pass http://127.0.0.1:8000;
37        proxy_set_header Host $host;
38        proxy_set_header X-Real-IP $remote_addr;
39        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
40        proxy_set_header X-Forwarded-Proto $scheme;
41    }
42
43    # Статические файлы
44    location /static/ {
45        alias /path/to/your/staticfiles/;
46        expires 1y;
47        add_header Cache-Control "public, immutable";
48    }
49
50    location /media/ {
51        alias /path/to/your/media/;
52        expires 1y;
53        add_header Cache-Control "public";
54    }
55}

2. Apache конфигурация

 1# /etc/apache2/sites-available/example.com.conf
 2<VirtualHost *:80>
 3    ServerName example.com
 4    ServerAlias www.example.com
 5
 6    # Редирект на HTTPS
 7    Redirect permanent / https://example.com/
 8</VirtualHost>
 9
10<VirtualHost *:443>
11    ServerName example.com
12    ServerAlias www.example.com
13
14    # SSL настройки
15    SSLEngine on
16    SSLCertificateFile /etc/ssl/certs/example.com.crt
17    SSLCertificateKeyFile /etc/ssl/private/example.com.key
18
19    # Дополнительные SSL настройки
20    SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
21    SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384
22
23    # HSTS заголовки
24    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
25
26    # Дополнительные заголовки безопасности
27    Header always set X-Frame-Options DENY
28    Header always set X-Content-Type-Options nosniff
29    Header always set X-XSS-Protection "1; mode=block"
30    Header always set Referrer-Policy "strict-origin-when-cross-origin"
31
32    # Проксирование к Django
33    ProxyPreserveHost On
34    ProxyPass / http://127.0.0.1:8000/
35    ProxyPassReverse / http://127.0.0.1:8000/
36
37    # Статические файлы
38    Alias /static/ /path/to/your/staticfiles/
39    <Directory /path/to/your/staticfiles/>
40        Require all granted
41        ExpiresActive On
42        ExpiresDefault "access plus 1 year"
43    </Directory>
44</VirtualHost>

3. Gunicorn с SSL

 1# gunicorn_config.py
 2import multiprocessing
 3
 4# Основные настройки
 5bind = "0.0.0.0:8000"
 6workers = multiprocessing.cpu_count() * 2 + 1
 7worker_class = "gevent"
 8worker_connections = 1000
 9
10# SSL настройки (если нужно запускать с SSL напрямую)
11# keyfile = "/path/to/private.key"
12# certfile = "/path/to/certificate.crt"
13
14# Настройки безопасности
15limit_request_line = 4094
16limit_request_fields = 100
17limit_request_field_size = 8190
18
19# Логирование
20accesslog = "/var/log/gunicorn/access.log"
21errorlog = "/var/log/gunicorn/error.log"
22loglevel = "info"
23
24# Настройки для прокси
25forwarded_allow_ips = "127.0.0.1,::1"
26proxy_allow_forwarded = True

Middleware для безопасности

1. Кастомный HTTPS middleware

 1# middleware.py
 2from django.http import HttpResponsePermanentRedirect
 3from django.conf import settings
 4
 5class HTTPSRedirectMiddleware:
 6    """Middleware для принудительного редиректа на HTTPS"""
 7
 8    def __init__(self, get_response):
 9        self.get_response = get_response
10
11    def __call__(self, request):
12        # Проверяем, нужно ли принудительно перенаправлять на HTTPS
13        if getattr(settings, 'SECURE_SSL_REDIRECT', False):
14            # Проверяем заголовки прокси
15            if getattr(settings, 'SECURE_PROXY_SSL_HEADER', None):
16                header_name, header_value = settings.SECURE_PROXY_SSL_HEADER
17                if request.META.get(header_name) != header_value:
18                    # Перенаправляем на HTTPS
19                    url = request.build_absolute_uri(request.get_full_path())
20                    if url.startswith('http://'):
21                        url = 'https://' + url[7:]
22                        return HttpResponsePermanentRedirect(url)
23
24        response = self.get_response(request)
25        return response
26
27class SecurityHeadersMiddleware:
28    """Middleware для добавления заголовков безопасности"""
29
30    def __init__(self, get_response):
31        self.get_response = get_response
32
33    def __call__(self, request):
34        response = self.get_response(request)
35
36        # Добавляем заголовки безопасности
37        response['X-Frame-Options'] = 'DENY'
38        response['X-Content-Type-Options'] = 'nosniff'
39        response['X-XSS-Protection'] = '1; mode=block'
40        response['Referrer-Policy'] = 'strict-origin-when-cross-origin'
41
42        # HSTS заголовок
43        if getattr(settings, 'SECURE_HSTS_SECONDS', 0) > 0:
44            hsts_value = f"max-age={settings.SECURE_HSTS_SECONDS}"
45            if getattr(settings, 'SECURE_HSTS_INCLUDE_SUBDOMAINS', False):
46                hsts_value += "; includeSubDomains"
47            if getattr(settings, 'SECURE_HSTS_PRELOAD', False):
48                hsts_value += "; preload"
49            response['Strict-Transport-Security'] = hsts_value
50
51        return response

2. Настройка middleware в settings

 1# settings.py
 2MIDDLEWARE = [
 3    'django.middleware.security.SecurityMiddleware',
 4    'django.contrib.sessions.middleware.SessionMiddleware',
 5    'django.middleware.common.CommonMiddleware',
 6    'django.middleware.csrf.CsrfViewMiddleware',
 7    'django.contrib.auth.middleware.AuthenticationMiddleware',
 8    'django.contrib.messages.middleware.MessageMiddleware',
 9    'django.middleware.clickjacking.XFrameOptionsMiddleware',
10
11    # Кастомные middleware
12    'your_app.middleware.HTTPSRedirectMiddleware',
13    'your_app.middleware.SecurityHeadersMiddleware',
14]

Практические примеры

1. Настройка для разных доменов

 1# settings.py
 2import socket
 3
 4# Определяем текущий хост
 5hostname = socket.gethostname()
 6
 7# Настройки для разных окружений
 8if hostname.startswith('prod-'):
 9    # Продакшен
10    SECURE_SSL_REDIRECT = True
11    SECURE_HSTS_SECONDS = 31536000
12    SECURE_HSTS_INCLUDE_SUBDOMAINS = True
13    SECURE_HSTS_PRELOAD = True
14    ALLOWED_HOSTS = ['example.com', 'www.example.com']
15
16elif hostname.startswith('staging-'):
17    # Staging
18    SECURE_SSL_REDIRECT = True
19    SECURE_HSTS_SECONDS = 3600
20    SECURE_HSTS_INCLUDE_SUBDOMAINS = False
21    SECURE_HSTS_PRELOAD = False
22    ALLOWED_HOSTS = ['staging.example.com']
23
24else:
25    # Разработка
26    SECURE_SSL_REDIRECT = False
27    SECURE_HSTS_SECONDS = 0
28    ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'dev.example.com']

2. Настройка для Cloudflare

 1# settings.py
 2
 3# Настройки для Cloudflare
 4if 'HTTP_CF_VISITOR' in request.META:
 5    # Определяем протокол из заголовка Cloudflare
 6    import json
 7    cf_visitor = json.loads(request.META['HTTP_CF_VISITOR'])
 8    is_https = cf_visitor.get('scheme') == 'https'
 9
10    if is_https:
11        SECURE_PROXY_SSL_HEADER = ('HTTP_CF_VISITOR', '{"scheme":"https"}')
12        SECURE_SSL_REDIRECT = True
13
14# Доверенные IP адреса Cloudflare
15SECURE_PROXY_SSL_HEADER = ('HTTP_CF_CONNECTING_IP', 'https')
16SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
17
18# Настройки для Cloudflare
19USE_X_FORWARDED_HOST = True
20USE_X_FORWARDED_PORT = True

3. Настройка для Heroku

 1# settings.py
 2
 3# Настройки для Heroku
 4if 'DYNO' in os.environ:
 5    # Heroku environment
 6    SECURE_SSL_REDIRECT = True
 7    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
 8
 9    # Настройки для Heroku
10    USE_X_FORWARDED_HOST = True
11    USE_X_FORWARDED_PORT = True
12
13    # HSTS настройки
14    SECURE_HSTS_SECONDS = 31536000
15    SECURE_HSTS_INCLUDE_SUBDOMAINS = True
16    SECURE_HSTS_PRELOAD = True

Тестирование HTTPS

1. Проверка SSL сертификата

 1# Проверка сертификата
 2openssl s_client -connect example.com:443 -servername example.com
 3
 4# Проверка цепочки сертификатов
 5openssl s_client -connect example.com:443 -servername example.com -showcerts
 6
 7# Проверка через curl
 8curl -I https://example.com
 9
10# Проверка заголовков безопасности
11curl -I -H "User-Agent: Mozilla/5.0" https://example.com

2. Онлайн инструменты для проверки

 1# Скрипт для проверки безопасности
 2import requests
 3import ssl
 4import socket
 5
 6def check_https_security(domain):
 7    """Проверка безопасности HTTPS"""
 8    results = {}
 9
10    try:
11        # Проверяем SSL сертификат
12        context = ssl.create_default_context()
13        with socket.create_connection((domain, 443)) as sock:
14            with context.wrap_socket(sock, server_hostname=domain) as ssock:
15                cert = ssock.getpeercert()
16                results['ssl_valid'] = True
17                results['ssl_expires'] = cert['notAfter']
18                results['ssl_issuer'] = cert['issuer']
19    except Exception as e:
20        results['ssl_valid'] = False
21        results['ssl_error'] = str(e)
22
23    try:
24        # Проверяем заголовки безопасности
25        response = requests.get(f'https://{domain}', timeout=10)
26        results['https_working'] = True
27
28        # Проверяем HSTS
29        hsts_header = response.headers.get('Strict-Transport-Security')
30        results['hsts_enabled'] = hsts_header is not None
31
32        # Проверяем другие заголовки
33        results['x_frame_options'] = response.headers.get('X-Frame-Options')
34        results['x_content_type_options'] = response.headers.get('X-Content-Type-Options')
35        results['x_xss_protection'] = response.headers.get('X-XSS-Protection')
36
37    except Exception as e:
38        results['https_working'] = False
39        results['https_error'] = str(e)
40
41    return results
42
43# Использование
44security_info = check_https_security('example.com')
45print(security_info)

Автоматизация и мониторинг

1. Автоматическое обновление сертификатов

 1#!/bin/bash
 2# /usr/local/bin/renew_ssl.sh
 3
 4# Обновление SSL сертификатов
 5certbot renew --quiet
 6
 7# Перезагрузка Nginx
 8if systemctl is-active --quiet nginx; then
 9    systemctl reload nginx
10fi
11
12# Проверка статуса
13if [ $? -eq 0 ]; then
14    echo "SSL certificates renewed successfully"
15else
16    echo "SSL renewal failed"
17    # Отправка уведомления
18    curl -X POST "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK" \
19        -H "Content-type: application/json" \
20        -d '{"text":"SSL renewal failed for example.com"}'
21fi

2. Мониторинг срока действия сертификатов

 1# management/commands/check_ssl_expiry.py
 2from django.core.management.base import BaseCommand
 3from django.core.mail import send_mail
 4from django.conf import settings
 5import ssl
 6import socket
 7from datetime import datetime, timedelta
 8
 9class Command(BaseCommand):
10    help = 'Check SSL certificate expiry dates'
11
12    def add_arguments(self, parser):
13        parser.add_argument(
14            '--warning-days',
15            type=int,
16            default=30,
17            help='Days before expiry to send warning'
18        )
19
20    def handle(self, *args, **options):
21        warning_days = options['warning_days']
22        domains = getattr(settings, 'SSL_DOMAINS', ['example.com'])
23
24        for domain in domains:
25            try:
26                # Получаем информацию о сертификате
27                context = ssl.create_default_context()
28                with socket.create_connection((domain, 443)) as sock:
29                    with context.wrap_socket(sock, server_hostname=domain) as ssock:
30                        cert = ssock.getpeercert()
31
32                        # Проверяем срок действия
33                        expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
34                        days_until_expiry = (expiry_date - datetime.now()).days
35
36                        if days_until_expiry <= warning_days:
37                            # Отправляем предупреждение
38                            subject = f'SSL Certificate Expiry Warning: {domain}'
39                            message = f'''
40                            SSL certificate for {domain} will expire in {days_until_expiry} days.
41                            Expiry date: {expiry_date}
42                            Please renew the certificate.
43                            '''
44
45                            send_mail(
46                                subject=subject,
47                                message=message,
48                                from_email=settings.DEFAULT_FROM_EMAIL,
49                                recipient_list=settings.ADMINS,
50                                fail_silently=False,
51                            )
52
53                            self.stdout.write(
54                                self.style.WARNING(
55                                    f'Warning sent for {domain}: expires in {days_until_expiry} days'
56                                )
57                            )
58                        else:
59                            self.stdout.write(
60                                self.style.SUCCESS(
61                                    f'{domain}: expires in {days_until_expiry} days'
62                                )
63                            )
64
65            except Exception as e:
66                self.stdout.write(
67                    self.style.ERROR(f'Error checking {domain}: {e}')
68                )

FAQ

Q: Как получить SSL сертификат?
A: Используй Let's Encrypt для бесплатных сертификатов, купи у провайдера для коммерческих проектов, или создай самоподписанный для разработки.

Q: Как настроить автоматический редирект с HTTP на HTTPS?
A: Установи SECURE_SSL_REDIRECT = True в Django settings и настрой редирект в веб-сервере (Nginx/Apache) для лучшей производительности.

Q: Что такое HSTS и зачем он нужен?
A: HSTS (HTTP Strict Transport Security) принудительно использует HTTPS для всех запросов к сайту, защищая от downgrade атак и перехвата трафика.

Q: Как проверить правильность настройки HTTPS?
A: Используй онлайн инструменты (SSL Labs, Security Headers), проверяй заголовки через curl, и тестируй редирект с HTTP на HTTPS.

Q: Можно ли использовать HTTPS в разработке?
A: Да, создай самоподписанный сертификат или используй mkcert для локальной разработки с валидными сертификатами.

Q: Как настроить HTTPS для нескольких доменов?
A: Используй wildcard сертификаты или отдельные сертификаты для каждого домена, настрой соответствующие конфигурации в веб-сервере.

Q: Что делать если SSL сертификат истек?
A: Немедленно обнови сертификат, настрой автоматическое обновление через cron, и мониторь сроки действия сертификатов.

Q: Как оптимизировать производительность HTTPS?
A: Используй HTTP/2, настрой SSL session cache, включи OCSP stapling, и используй современные шифры для лучшей производительности.