VPS

Как развернуть Django на VPS с Gunicorn, systemd и Nginx

Разворачиваем Django на Ubuntu: настройки приложения, миграции, сбор статики, служба Gunicorn и прокси Nginx.

Приложение Python передаёт запросы через сервер приложений и прокси

Django-приложение на VPS можно запустить как службу systemd, принимать HTTP-запросы через Gunicorn и отдавать публичный трафик через Nginx. Эта схема подходит обычному WSGI-проекту на Ubuntu. Если приложение использует WebSocket или требует ASGI, выберите ASGI-сервер и настройте его отдельно.

Подготовить проект и окружение

В примере manage.py лежит в /srv/myapp/app, модуль проекта называется config, а домен — example.com. Подставьте свои значения. Gunicorn должен быть в зафиксированных зависимостях проекта. Перед выкладкой проверьте, что версия Python на VPS подходит этим зависимостям. Если вы ещё выбираете машину, начните с подбора VPS для первого проекта.

sudo apt update
sudo apt install python3-venv nginx
sudo adduser --system --group --home /srv/myapp djangoapp
sudo install -d -o djangoapp -g djangoapp -m 0755 /srv/myapp
sudo install -d -o djangoapp -g djangoapp -m 0755 /srv/myapp/app /srv/myapp/static
sudo install -d -o djangoapp -g djangoapp -m 0750 /var/lib/myapp

Перенесите в app исходники своего проекта и дайте djangoapp доступ к ним. Файлы должны включать manage.py, requirements.txt и пакет с wsgi.py. Для SQLite задайте в настройках абсолютный путь /var/lib/myapp/db.sqlite3, чтобы база и файлы её журнала оставались в каталоге данных при замене кода. Если база уже существует, сначала сделайте согласованную копию и перенесите данные отдельно. Для PostgreSQL пригодится инструкция по установке и подключению базы.

sudo -u djangoapp python3 -m venv /srv/myapp/venv
sudo -u djangoapp /srv/myapp/venv/bin/python -m pip install \
  -r /srv/myapp/app/requirements.txt

Задать параметры Django

В выделенных production-настройках укажите домен, параметры статики и источник секрета. DJANGO_SECRET_KEY ниже — переменная этого примера. Django прочитает её благодаря строке os.environ.

import os

DEBUG = False
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
ALLOWED_HOSTS = ['example.com']
STATIC_URL = '/static/'
STATIC_ROOT = '/srv/myapp/static'
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

Храните постоянный случайный ключ в /etc/myapp.env с владельцем root и правами 0600. Укажите строку DJANGO_SECRET_KEY=... и нужные вашему проекту параметры базы. Сгенерировать ключ можно командой python3 -c 'import secrets; print(secrets.token_urlsafe(64))'. Перенесите его прямо в закрытый файл и сохраните при следующих выкладках. Если настройки выбираются через DJANGO_SETTINGS_MODULE, задайте и эту переменную.

Заголовку X-Forwarded-Proto можно доверять, если Gunicorn доступен только локально, а Nginx всегда подставляет собственное значение. Иначе клиент сможет повлиять на то, считает ли Django запрос защищённым.

Применить миграции и собрать статику

Запускайте команды с теми же настройками и от того же пользователя, что и службу. systemd-run позволяет передать закрытый файл окружения без ослабления его прав. Для проекта с данными проверьте резервную копию до migrate.

run_manage() {
  sudo systemd-run --wait --pipe --collect \
    -p User=djangoapp -p Group=djangoapp \
    -p WorkingDirectory=/srv/myapp/app \
    -p EnvironmentFile=/etc/myapp.env \
    /srv/myapp/venv/bin/python manage.py "$@"
}

run_manage check --deploy
run_manage migrate --plan
run_manage migrate
run_manage collectstatic --noinput

check --deploy показывает рекомендации по настройкам. На промежуточном HTTP-этапе предупреждения об HTTPS ожидаемы. Вернитесь к ним после установки сертификата и разберите каждое по условиям своего проекта.

Запустить Gunicorn через systemd

Создайте /etc/systemd/system/myapp.service. Если пакет с вашим wsgi.py называется иначе, исправьте config.wsgi:application.

[Unit]
Description=Django application myapp
After=network.target

[Service]
User=djangoapp
Group=djangoapp
WorkingDirectory=/srv/myapp/app
EnvironmentFile=/etc/myapp.env
ExecStart=/srv/myapp/venv/bin/gunicorn --bind 127.0.0.1:8001 --workers 2 --access-logfile - config.wsgi:application
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
sudo systemctl status myapp.service --no-pager
curl -i -H 'Host: example.com' http://127.0.0.1:8001/

Проверьте маршрут, который действительно существует в вашем проекте. Если корневого URL нет, подставьте готовую страницу или /health/. 400 на локальной проверке часто связан с ALLOWED_HOSTS. При падении службы начните с sudo journalctl -u myapp.service -n 50 --no-pager и проверьте пути, переменные окружения и WSGI-модуль.

Подключить Nginx и HTTPS

На Ubuntu с пакетным Nginx создайте /etc/nginx/sites-available/myapp.

server {
    listen 80;
    server_name example.com;

    location /static/ {
        alias /srv/myapp/static/;
    }

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:8001;
    }
}
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp
sudo nginx -t && sudo systemctl reload nginx
curl -i -H 'Host: example.com' http://127.0.0.1/

Проверьте права Nginx на чтение статики и проход по родительским каталогам. Для проекта с админкой Django контрольный ресурс — /static/admin/css/base.css. Доступ к загрузкам пользователей проектируйте отдельно. Публичный alias для каталога с приватными вложениями обойдёт проверки Django.

Направьте домен на сервер и подключите сертификат. После запуска HTTPS настройте SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE и перенаправление HTTP по правилам вашего vhost. Проверьте через браузер публичную страницу, статику и отправку формы. Затем повторите run_manage check --deploy. При 502 используйте проверку маршрута до Gunicorn, а при задержках — разбор 504.

На стенде с Ubuntu 24.04 и Django 5.2 мы применили миграции и собрали статику. Nginx вернул 200 для страницы приложения и CSS админки. Запрос с неподходящим заголовком Host при прямом обращении к Gunicorn вернул 400.

Первоисточники

Нашли неточность?

Сообщите адрес статьи и фрагмент через контакты редакции.