Эта статья — результат многократного прохождения всех граблей при деплое Django-приложений. Здесь собрано всё, что нужно знать для развертывания проекта на чистом Ubuntu Server 22.04: от первого подключения по SSH до настроенного мониторинга с автозапуском при перезагрузке.
Что мы будем делать
Берём чистый сервер и поднимаем на нём полноценное production-окружение:
- Django-приложение через Gunicorn
- Nginx в роли reverse proxy
- SSL-сертификаты от Let's Encrypt
- Автозапуск всех сервисов через systemd
- Netdata для мониторинга состояния сервера
Все команды проверены на практике. Где возможны ошибки — расскажу о них заранее.
Содержание
- Подготовка сервера
- Подготовка проекта
- Настройка Gunicorn
- Настройка Nginx
- Настройка SSL через Let's Encrypt
- Финальная настройка безопасности
- Проверка работоспособности
- Дополнительные рекомендации
1. Подготовка сервера
1.1. Первое, что делаем после подключения
Подключаемся к серверу по SSH под root и обновляем систему. Это не формальность — часто в базовых образах Ubuntu отсутствуют критические обновления безопасности:
apt update
apt upgrade -y
1.2. Ставим необходимый софт
Устанавливаем Python, инструменты для работы с виртуальными окружениями, компиляторы и веб-сервер:
apt install -y python3 python3-pip python3-venv python3-dev build-essential libpq-dev nginx curl git
Пакет build-essential нужен для компиляции некоторых Python-пакетов с C-расширениями. libpq-dev понадобится, если будете использовать PostgreSQL (хотя в этом гайде мы остановимся на SQLite).
1.3. Настраиваем firewall
UFW (Uncomplicated Firewall) — простой и надёжный способ закрыть всё лишнее. Разрешаем только SSH, HTTP и HTTPS:
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw --force enable
ufw status
Флаг --force пропускает интерактивное подтверждение. После включения проверяем статус — должны быть открыты порты 22, 80 и 443.
Важно: Если подключены не через порт 22, сначала разрешите свой SSH-порт отдельно, иначе потеряете доступ к серверу.
1.4. Создаём непривилегированного пользователя
Запускать веб-приложения от root — плохая идея с точки зрения безопасности. Создаём отдельного пользователя:
adduser siteuser
usermod -aG sudo siteuser
При создании система запросит пароль. Остальные поля (имя, телефон и т.д.) можно пропустить, нажимая Enter.
2. Подготовка проекта
2.1. Создаём структуру директорий
Переключаемся на созданного пользователя и готовим место для проекта:
su - siteuser
sudo mkdir -p /var/www/project
sudo chown -R siteuser:siteuser /var/www/project
cd /var/www/project
Директория /var/www/ — стандартное место для веб-приложений в Linux. Можно использовать другую, но зачем изобретать велосипед.
2.2. Загружаем код проекта
Если проект в Git:
git clone <URL_ВАШЕГО_РЕПОЗИТОРИЯ> # ЗАМЕНИТЕ НА СВОЙ URL
Если загружаете вручную, используйте scp или rsync:
# С локальной машины:
rsync -avz --exclude 'venv' --exclude '*.pyc' /path/to/local/project/ siteuser@your-server-ip:/var/www/project/
2.3. Виртуальное окружение
Виртуальное окружение изолирует зависимости проекта от системных пакетов. Это спасает от конфликтов версий и делает проект переносимым:
python3 -m venv venv
source venv/bin/activate
После активации в начале строки появится (venv) — значит, всё работает.
2.4. Устанавливаем зависимости
pip install --upgrade pip
pip install -r requirements.txt
Если requirements.txt нет, но есть Pipfile или poetry.lock — адаптируйте под свой инструмент управления зависимостями.
2.5. Конфигурация через переменные окружения
Критически важный момент: никогда не храните секретные ключи в коде. Django генерирует SECRET_KEY при создании проекта, но для production нужен новый, уникальный ключ.
Генерируем новый ключ:
python3 -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
Создаём файл .env в корне проекта:
nano .env
Минимальное содержимое:
DJANGO_SECRET_KEY='ВСТАВЬТЕ_СЮДА_СГЕНЕРИРОВАННЫЙ_КЛЮЧ' # ЗАМЕНИТЕ!
DJANGO_DEBUG=False
# Если используете дополнительные сервисы, добавьте их данные:
# EMAIL_HOST=smtp.example.com
# EMAIL_PORT=465
# EMAIL_HOST_USER='noreply@example.com'
# EMAIL_HOST_PASSWORD='your_password'
Сохраняем (Ctrl+O, Enter, Ctrl+X).
2.6. Учим Django читать .env
Устанавливаем python-dotenv:
pip install python-dotenv
В начале файла settings.py (после импортов os и Path) добавляем:
from dotenv import load_dotenv
load_dotenv()
Теперь Django будет автоматически подхватывать переменные из .env через os.getenv() или os.environ.get().
2.7. Защищаем .env
Ограничиваем доступ к файлу — только владелец может его читать и изменять:
chmod 600 .env
2.8. Применяем миграции
Создаём структуру базы данных:
python manage.py makemigrations
python manage.py migrate
Если используете кастомную модель пользователя через AUTH_USER_MODEL, убедитесь, что миграции для этого приложения создаются первыми. Иначе Django может запутаться в зависимостях.
2.9. Создаём суперпользователя
Для доступа в админку:
python manage.py createsuperuser
Следуем инструкциям. Пароль должен быть сложным — это production.
2.10. Собираем статику
Django не умеет эффективно отдавать статические файлы. Nginx справляется с этим в сотни раз быстрее, поэтому собираем всю статику в одну директорию:
python manage.py collectstatic --noinput
Флаг --noinput пропускает подтверждение перезаписи файлов.
2.11. Готовим директории для медиа и логов
mkdir -p media logs
2.12. Права доступа
SQLite требует прав на запись не только для файла базы, но и для родительской директории (для временных файлов блокировок):
chmod 664 db.sqlite3
chmod 775 . media logs
3. Настройка Gunicorn
Gunicorn — это WSGI-сервер, который запускает Django-приложение. Nginx будет проксировать запросы на Gunicorn, а тот — выполнять Python-код.
3.1. Тестовый запуск
Проверяем, что всё работает:
gunicorn --bind 0.0.0.0:8000 project.wsgi:application
Если видим строки о запуске workers — отлично. Жмём Ctrl+C и идём дальше.
3.2. Создаём systemd-сервис
systemd — система инициализации в современных Linux-дистрибутивах. Создаём юнит для автоматического управления Gunicorn.
Выходим из виртуального окружения:
deactivate
Создаём файл сервиса:
sudo nano /etc/systemd/system/gunicorn.service
Вставляем конфигурацию:
[Unit]
Description=Gunicorn daemon for project Django application # ЗАМЕНИТЕ project
After=network.target
[Service]
User=siteuser # ЗАМЕНИТЕ НА ВАШЕГО ПОЛЬЗОВАТЕЛЯ
Group=www-data
WorkingDirectory=/var/www/project # ЗАМЕНИТЕ НА ПУТЬ К ВАШЕМУ ПРОЕКТУ
EnvironmentFile=/var/www/project/.env # ЗАМЕНИТЕ НА ПУТЬ К ВАШЕМУ .env
ExecStart=/var/www/project/venv/bin/gunicorn \ # ЗАМЕНИТЕ ПУТИ
--workers 3 \
--bind unix:/var/www/project/project.sock \ # ЗАМЕНИТЕ ПУТИ
project.wsgi:application # ЗАМЕНИТЕ project НА ИМЯ ВАШЕГО ПРОЕКТА
[Install]
WantedBy=multi-user.target
Как это работает:
After=network.target— запускаем после инициализации сетиEnvironmentFile— загружаем переменные из.envв окружение процесса--workers 3— количество рабочих процессов (обычно(2 × CPU_cores) + 1)--bind unix:...— используем UNIX-сокет вместо TCP-порта (быстрее и безопаснее)WantedBy=multi-user.target— автозапуск при загрузке системы
3.3. Запускаем сервис
sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
Команда enable добавляет сервис в автозагрузку.
Проверяем статус:
sudo systemctl status gunicorn
Должно быть active (running). Если failed — смотрим логи:
sudo journalctl -u gunicorn -e
3.4. Проверяем сокет
Убеждаемся, что файл сокета создан:
ls -l /var/www/project/project.sock # ЗАМЕНИТЕ ПУТЬ
4. Настройка Nginx
Nginx будет принимать все входящие запросы, отдавать статику напрямую (быстро), а динамические запросы проксировать на Gunicorn.
4.1. Создаём конфигурацию виртуального хоста
sudo nano /etc/nginx/sites-available/project # ЗАМЕНИТЕ project
Базовая конфигурация:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com; # ЗАМЕНИТЕ НА ВАШИ ДОМЕНЫ
client_max_body_size 20M; # Максимальный размер загружаемых файлов
# Отключаем логирование для favicon — его часто нет, и логи захламляются
location = /favicon.ico {
access_log off;
log_not_found off;
}
# Статика: отдаём напрямую через Nginx
location /static/ {
alias /var/www/project/staticfiles/; # ЗАМЕНИТЕ ПУТЬ
expires 30d; # Кэшируем на 30 дней
add_header Cache-Control "public, immutable";
}
# Медиафайлы: тоже напрямую
location /media/ {
alias /var/www/project/media/; # ЗАМЕНИТЕ ПУТЬ
expires 7d;
add_header Cache-Control "public";
}
# Всё остальное: проксируем на Gunicorn
location / {
proxy_pass http://unix:/var/www/project/project.sock; # ЗАМЕНИТЕ ПУТЬ
proxy_set_header Host $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;
}
}
Почему UNIX-сокет, а не TCP-порт: UNIX-сокеты работают в пределах одной машины и не требуют сетевого стека ОС. Это быстрее и безопаснее — к сокету нельзя подключиться извне даже при неправильной настройке файрвола.
4.2. Активируем конфигурацию
sudo ln -s /etc/nginx/sites-available/project /etc/nginx/sites-enabled/ # ЗАМЕНИТЕ project
4.3. Удаляем дефолтный виртуальный хост
sudo rm /etc/nginx/sites-enabled/default
4.4. Проверяем синтаксис
sudo nginx -t
Должно быть syntax is ok и test is successful. Если есть ошибки — внимательно проверяем пути и имена в конфиге.
4.5. Перезапускаем Nginx
sudo systemctl restart nginx
На этом этапе сайт должен быть доступен по HTTP. Проверяем через браузер или curl:
curl -I http://example.com # ЗАМЕНИТЕ НА ВАШ ДОМЕН
Если видим 200 OK или редирект (301/302) — отлично, идём дальше.
5. Настройка SSL через Let's Encrypt
HTTP без шифрования — это прошлый век. Настраиваем HTTPS через бесплатные сертификаты Let's Encrypt.
5.1. Устанавливаем Certbot
sudo apt install -y certbot python3-certbot-nginx
5.2. Получаем сертификат
sudo certbot --nginx -d example.com -d www.example.com # ЗАМЕНИТЕ НА ВАШИ ДОМЕНЫ
Certbot запросит email (для уведомлений об истечении срока) и попросит согласиться с условиями. Затем автоматически:
- Проверит, что домены действительно указывают на этот сервер
- Получит сертификат от Let's Encrypt
- Модифицирует конфиг Nginx, добавив блок для HTTPS
- Настроит редирект с HTTP на HTTPS
Классическая ошибка: если DNS-записи ещё не обновились или указывают на старый IP, Certbot выдаст ошибку вроде:
Detail: <YOUR_IP>: Invalid response
Решение: проверяем DNS через dig +short example.com. Когда команда вернёт правильный IP — повторяем попытку с Certbot.
5.3. Проверяем автообновление
Сертификаты Let's Encrypt действуют 90 дней. Certbot автоматически настраивает обновление через systemd-таймер. Тестируем:
sudo certbot renew --dry-run
Если команда отработала без ошибок — всё в порядке, обновление будет происходить автоматически.
5.4. Проверяем результат
Открываем сайт в браузере: https://example.com. Должен открыться с зелёным замочком.
6. Финальная настройка безопасности
6.1. Права для базы данных
SQLite — особенный. Ему нужны права на запись для файла БД и для родительской директории:
sudo chown siteuser:www-data /var/www/project/db.sqlite3 # ЗАМЕНИТЕ ПУТИ И ПОЛЬЗОВАТЕЛЯ
sudo chmod 664 /var/www/project/db.sqlite3
sudo chown siteuser:www-data /var/www/project
sudo chmod 775 /var/www/project
6.2. Права для media и logs
sudo chown -R siteuser:www-data /var/www/project/media # ЗАМЕНИТЕ
sudo chown -R siteuser:www-data /var/www/project/logs
sudo chmod -R 775 /var/www/project/media
sudo chmod -R 775 /var/www/project/logs
6.3. Проверяем settings.py
Критически важно: DEBUG = False в production. При DEBUG = True Django показывает полные трейсбеки с путями, настройками и секретными ключами. Это подарок для атакующих.
Убедитесь, что в .env установлено DJANGO_DEBUG=False, а в settings.py это значение корректно читается.
7. Проверка работоспособности
7.1. Базовая проверка
Открываем браузер и проверяем:
https://example.com— работаетhttp://example.com— редиректит на HTTPShttps://example.com/admin/— админка доступна- Статика и медиа загружаются
7.2. Статус сервисов
sudo systemctl status gunicorn
sudo systemctl status nginx
Оба должны быть active (running).
7.3. Мониторинг логов
Gunicorn:
sudo journalctl -u gunicorn -f
Nginx:
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
Django (если настроено логирование в файл):
tail -f /var/www/project/logs/django.log # ЗАМЕНИТЕ ПУТЬ
Флаг -f (follow) показывает логи в реальном времени.
7.4. Тест автозапуска
Перезагружаем сервер:
sudo reboot
После перезагрузки проверяем, что всё поднялось автоматически:
sudo systemctl status gunicorn nginx
8. Дополнительные рекомендации
8.1. Регулярные бэкапы
Настраиваем автоматическое резервное копирование через cron:
sudo crontab -e
Добавляем задачу для ежедневного бэкапа в 3 ночи:
0 3 * * * cp /var/www/project/db.sqlite3 /var/www/project/backups/db_$(date +\%Y\%m\%d).sqlite3
Предварительно создаём директорию:
mkdir -p /var/www/project/backups # ЗАМЕНИТЕ ПУТЬ
8.2. Настройка мониторинга с Netdata
Netdata — мощная система мониторинга, которая собирает и визуализирует метрики в реальном времени. Установка занимает несколько минут, а польза огромная.
Установка Netdata
bash <(curl -Ss https://get.netdata.cloud/kickstart.sh)
Скрипт автоматически определит систему и установит все необходимое. Подтверждаем установку, когда система попросит.
Проверяем статус:
sudo systemctl status netdata
Безопасный доступ через Nginx
Netdata по умолчанию слушает на localhost:19999. Открывать этот порт в интернет — плохая идея. Настраиваем доступ через поддомен с аутентификацией.
Устанавливаем утилиту для создания паролей:
sudo apt install apache2-utils
Создаём файл с учётными данными:
sudo htpasswd -c /etc/nginx/.htpasswd admin
Вводим надёжный пароль (минимум 12 символов, буквы + цифры + спецсимволы).
Устанавливаем правильные права:
sudo chown root:www-data /etc/nginx/.htpasswd
sudo chmod 640 /etc/nginx/.htpasswd
Почему именно 640: владелец (root) может читать и писать, группа (www-data, к которой принадлежит Nginx) может только читать, остальные — вообще ничего.
Создаём конфиг для Nginx:
sudo nano /etc/nginx/sites-available/netdata
Содержимое:
upstream netdata_backend {
server 127.0.0.1:19999;
keepalive 64;
}
server {
listen 80;
listen [::]:80;
server_name monitor.example.com; # ЗАМЕНИТЕ НА ВАШЕ ИМЯ ПОДДОМЕНА
auth_basic "Netdata Monitoring";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://netdata_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $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 $host;
}
}
Активируем конфигурацию:
sudo ln -s /etc/nginx/sites-available/netdata /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Не забываем настроить DNS-запись для поддомена monitor.example.com.
Получаем SSL для поддомена
sudo certbot --nginx -d monitor.example.com # ЗАМЕНИТЕ НА ВАШ ПОДДОМЕН
Теперь панель мониторинга доступна по адресу https://monitor.example.com с защитой паролем.
Что мониторить в Netdata:
- CPU: загрузка по ядрам, контекстные переключения
- Memory: использование RAM и swap (если swap активно используется — не хватает памяти)
- Disk I/O: операции чтения/записи (помогает выявить узкие места)
- Network: входящий и исходящий трафик
- Applications: ресурсы конкретных процессов (Gunicorn, Nginx, Python)
Типичное потребление памяти Netdata после установки — около 70-150 МБ.
Заключение
Теперь у вас полноценное production-окружение:
- Django работает через Gunicorn
- Nginx эффективно отдаёт статику и проксирует динамику
- HTTPS настроен с автообновлением сертификатов
- Всё запускается автоматически при перезагрузке
- Безопасность на должном уровне
- Мониторинг позволяет видеть проблемы до того, как они станут критическими
При возникновении проблем первым делом смотрите логи. Большинство ошибок подробно описаны в журналах systemd, Nginx и Django.
Регулярно обновляйте систему и зависимости проекта. Безопасность — это не разовое действие, а постоянный процесс.
Комментарии (0)
Чтобы оставить комментарий, войдите или зарегистрируйтесь.
Комментариев пока нет. Будьте первым!