65 лет полету человека в космос! Хостинг и домены со скидкой
до 22.04.2026 Подробнее
Выберите продукт

Django/Python на VDS: Gunicorn+Nginx, статика и media, миграции, systemd и healthcheck

Подробный гайд по продакшн-развёртыванию Django на VDS: структура каталогов, Python venv, Gunicorn и Unix‑сокет, Nginx со статикой и media, миграции, unit‑файлы systemd, перезапуски, healthcheck и базовая SSL‑схема.
Django/Python на VDS: Gunicorn+Nginx, статика и media, миграции, systemd и healthcheck

Зачем именно связка Django + Gunicorn + Nginx

Django принято запускать за WSGI-приложением. Gunicorn — надёжный и простой WSGI-сервер, а Nginx — быстрый и экономичный reverse proxy с раздачей статики и гибкой маршрутизацией. Такая схема хорошо масштабируется, прозрачно логируется и легко автоматизируется через systemd. Мы пройдём весь путь: подготовка окружения Python, структура каталогов, запуск Gunicorn как службы, проксирование через Nginx, статика и media, миграции, healthcheck и, наконец, базовая SSL-настройка. Если своего сервера ещё нет — возьмите небольшой VDS и следуйте инструкциям.

Предварительные условия

Ниже предполагается, что у вас есть сервер с Linux (например, семейство Ubuntu LTS), доступ по SSH с правами sudo, установленный Python 3.x, пакетный менеджер pip и возможность открыть порты 80/443 в брандмауэре. Если база данных внешняя — убедитесь, что она доступна и правильно защищена. Для простоты примеров можно начать с SQLite, а затем переключиться на PostgreSQL или MySQL. Выбираете панель для управления сервером — посмотрите обзор в статье панелей для VDS.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Структура проекта и системный пользователь

Рекомендуем создать отдельного системного пользователя без shell-доступа для приложения. Каталоги будем держать в одном корне, чтобы упростить пути в конфиге Nginx и юните systemd.

# создание пользователя и каталогов
sudo adduser --system --group --home /srv/myproj myproj
sudo mkdir -p /srv/myproj/app
sudo mkdir -p /srv/myproj/run
sudo mkdir -p /srv/myproj/logs
sudo mkdir -p /srv/myproj/static
sudo mkdir -p /srv/myproj/media
sudo chown -R myproj:myproj /srv/myproj

Каталоги /srv/myproj/static и /srv/myproj/media пригодятся для раздачи статики и пользовательских файлов напрямую из Nginx. Каталог /srv/myproj/run используем для Unix-сокета Gunicorn.

Фрагмент конфига Nginx: upstream, /static и /media.

Python: виртуальное окружение и Django

Виртуальное окружение важнее, чем кажется: версии библиотек, сборки Си-зависимостей и обновления не будут мешать системному Python. Создаём и активируем venv, устанавливаем Django и Gunicorn.

sudo -u myproj python3 -m venv /srv/myproj/venv
sudo -u myproj /srv/myproj/venv/bin/pip install --upgrade pip wheel
sudo -u myproj /srv/myproj/venv/bin/pip install django gunicorn

Инициализируем проект. Пример названия — config (внутренний пакет с настройками), а приложение пусть будет core. Вы можете использовать уже существующий репозиторий с кодом, тогда просто разместите его в /srv/myproj/app.

sudo -u myproj /srv/myproj/venv/bin/django-admin startproject config /srv/myproj/app

Проверим локальный запуск через manage.py:

cd /srv/myproj/app
sudo -u myproj /srv/myproj/venv/bin/python manage.py runserver 127.0.0.1:8000

Если стартует — отлично. Остановите сервер и приведите настройки проекта к продакшн-виду.

Базовые настройки Django для продакшна

В файле /srv/myproj/app/config/settings.py задаём минимум: DEBUG = False, список хостов, пути к статике и media. Подставьте свой домен и отредактируйте пути под нашу структуру.

DEBUG = False
ALLOWED_HOSTS = ["example.com", "www.example.com"]

STATIC_URL = "/static/"
STATIC_ROOT = "/srv/myproj/static"

MEDIA_URL = "/media/"
MEDIA_ROOT = "/srv/myproj/media"

# Дополнительно для SSL в дальнейшем
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True

Если используете PostgreSQL или MySQL, настройте DATABASES с безопасным хранением паролей через переменные окружения. Для старта на SQLite достаточно дефолта, чтобы отладить инфраструктуру.

Миграции и сбор статики

Прежде чем показывать приложение наружу, прогоним миграции и соберём статические файлы. Команды выполняем из каталога приложения.

cd /srv/myproj/app
sudo -u myproj /srv/myproj/venv/bin/python manage.py migrate --noinput
sudo -u myproj /srv/myproj/venv/bin/python manage.py collectstatic --noinput

При необходимости создайте суперпользователя:

sudo -u myproj /srv/myproj/venv/bin/python manage.py createsuperuser

Если у вас сторонние приложения Django, которые генерируют статические файлы, убедитесь, что они оказываются в /srv/myproj/static. В противном случае Nginx не найдёт их без обращения к Gunicorn, теряя производительность.

Gunicorn: тестовый запуск и настройка под systemd

Проверим, что Gunicorn подхватывает WSGI-приложение проекта. В Django точка входа обычно config.wsgi:application.

cd /srv/myproj/app
sudo -u myproj /srv/myproj/venv/bin/gunicorn --bind 127.0.0.1:8001 config.wsgi:application

Если всё ок, выключаем и переходим к штатному Unix-сокету, чтобы Nginx подключался локально без TCP-порта. Настроим systemd-юнит для демонизации и автозапуска.

sudo nano /etc/systemd/system/gunicorn-myproj.service
[Unit]
Description=Gunicorn for Django myproj
After=network.target

[Service]
User=myproj
Group=myproj
WorkingDirectory=/srv/myproj/app
Environment="DJANGO_SETTINGS_MODULE=config.settings"
Environment="PYTHONUNBUFFERED=1"
ExecStart=/srv/myproj/venv/bin/gunicorn --workers 3 --bind unix:/srv/myproj/run/gunicorn.sock --access-logfile - --error-logfile - config.wsgi:application
Restart=on-failure
RestartSec=5
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

Обратите внимание: сокет в /srv/myproj/run/gunicorn.sock лежит в каталоге, доступном пользователю Nginx. По умолчанию Nginx стартует от пользователя www-data, ему нужна хотя бы возможность читать этот сокет. Чаще всего достаточно выставить владельца и группу проекта, а в Nginx оставить группу www-data — Unix-сокет будет читаться благодаря правам.

sudo systemctl daemon-reload
sudo systemctl enable --now gunicorn-myproj.service
sudo systemctl status gunicorn-myproj.service

Если статус активен и сокет существует, можно подключать Nginx.

Nginx: reverse proxy и раздача статики/media

Создадим отдельный upstream на Unix-сокет, раздачу /static/ и /media/, а также базовые заголовки и лимиты. Пример конфига сервера на 80 порту:

sudo nano /etc/nginx/sites-available/myproj.conf
upstream myproj_backend {
    server unix:/srv/myproj/run/gunicorn.sock;
}

server {
    listen 80;
    server_name example.com www.example.com;

    client_max_body_size 20m;

    access_log /var/log/nginx/myproj.access.log;
    error_log /var/log/nginx/myproj.error.log warn;

    location /static/ {
        alias /srv/myproj/static/;
        expires 30d;
        add_header Cache-Control "public";
    }

    location /media/ {
        alias /srv/myproj/media/;
        expires 1h;
        add_header Cache-Control "private";
    }

    location /healthz {
        proxy_pass http://myproj_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 5s;
    }

    location / {
        proxy_pass http://myproj_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }
}
sudo ln -s /etc/nginx/sites-available/myproj.conf /etc/nginx/sites-enabled/myproj.conf
sudo nginx -t
sudo systemctl reload nginx

Проверьте, что /static/ и /media/ отдаются без обращения к Gunicorn (по логам Nginx это видно: доступы к статике не должны идти в backend). Параметр client_max_body_size отвечает за лимит загрузок. Для форм с файлами используйте значение с запасом.

SSL: базовая схема

Когда всё работает по HTTP, переходите на HTTPS. На уровне Nginx вам понадобится сервер на 443 порту с ssl_certificate и ssl_certificate_key, а 80-й порт выдаёт 301-редирект. Дополнительно можно включить HSTS, но осторожно: это необратимо для клиентов на период TTL заголовка. Подробнее про редиректы и HSTS — в материале о 301 и HSTS. Для выпуска и продления используйте проверенные SSL-сертификаты.

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate /etc/ssl/certs/example.crt;
    ssl_certificate_key /etc/ssl/private/example.key;

    include /etc/nginx/snippets/ssl-params.conf;

    client_max_body_size 20m;

    location /static/ {
        alias /srv/myproj/static/;
        expires 30d;
        add_header Cache-Control "public";
    }

    location /media/ {
        alias /srv/myproj/media/;
        expires 1h;
        add_header Cache-Control "private";
    }

    location /healthz {
        proxy_pass http://myproj_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 5s;
    }

    location / {
        proxy_pass http://myproj_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }
}

Не забудьте, что в настройках Django включены параметры для корректной работы за reverse proxy при HTTPS через заголовок X-Forwarded-Proto.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Права доступа и безопасность

Каталоги /srv/myproj/static и /srv/myproj/media должны быть доступны пользователю Nginx для чтения. Для загрузки файлов приложением директории media нужны права на запись от имени пользователя Gunicorn (у нас это myproj). Самый простой вариант — оставить владельца myproj:myproj и режимы 0755 для каталогов и 0644 для файлов, этого достаточно для чтения Nginx. Для записываемых директорий иногда используют группу www-data и setgid-бит, но придерживайтесь минимально необходимых прав.

Следите, чтобы у каталога с сокетом не было слишком жёстких прав, иначе Nginx не сможет открыть сокет. Для журналов используйте journald либо перенаправляйте лог Gunicorn в stdout/stderr (в юните уже так) и читайте через journalctl -u gunicorn-myproj.service. Если хотите файловые логи, переведите --access-logfile и --error-logfile на путь в /srv/myproj/logs и обеспечьте права.

Миграции при деплое

Типичный деплой Django включает несколько шагов: сбор зависимостей, применение миграций, сбор статики, рестарт Gunicorn. Удобно иметь небольшой скрипт деплоя, который выполняет эти команды последовательно и фейлится на первом сбое. При использовании внешней БД не забывайте про резервные копии и откат миграций при необходимости.

cd /srv/myproj/app
sudo -u myproj /srv/myproj/venv/bin/pip install -r requirements.txt
sudo -u myproj /srv/myproj/venv/bin/python manage.py migrate --noinput
sudo -u myproj /srv/myproj/venv/bin/python manage.py collectstatic --noinput
sudo systemctl restart gunicorn-myproj.service

Можно оформить миграции как отдельный oneshot-юнит и вызывать его перед рестартом сервиса. Но проще и прозрачнее держать команды в сценарии деплоя, чтобы видеть пошаговый вывод и быстро диагностировать ошибки миграций.

Healthcheck: приложение и инфраструктура

Healthcheck нужен для мониторинга и автоматических перезапусков в оркестраторах и балансировщиках. На уровне приложения — это эндпоинт, который возвращает 200 OK быстро и без нагруженных запросов. На уровне Nginx можно сделать простой ответ 200, чтобы проверять только web-узел. Ниже пример Django-вью и Nginx-локации.

Django-вью для /healthz

# config/urls.py
from django.urls import path
from django.http import JsonResponse

def healthz(request):
    return JsonResponse({"status": "ok"})

urlpatterns = [
    path("healthz", healthz),
]

Если хотите глубже проверять доступность БД, добавьте простейший запрос к базе и увеличьте таймаут в локации /healthz Nginx до разумного значения. Важно: такой healthcheck не должен блокироваться долгими внешними вызовами.

Практика: делайте два уровня проверок — лёгкий /healthz без БД для частых проверок балансировщиком и отдельный /healthz/db для редких, но более глубоких проверок мониторингом.

systemd: автозапуск, рестарты и окружение

Наш юнит уже настроен на перезапуск при сбоях. Добавьте переменные окружения через файл, чтобы не хранить секреты в явном виде. Затем подключайте его через EnvironmentFile. Не забывайте, что права на файл с секретами должны быть ограничены.

sudo nano /etc/myproj.env
DJANGO_SETTINGS_MODULE=config.settings
SECRET_KEY=change-me
DATABASE_URL=postgres://user:pass@host:5432/dbname
sudo chmod 600 /etc/myproj.env
sudo chown root:root /etc/myproj.env
# правим юнит
sudo nano /etc/systemd/system/gunicorn-myproj.service
[Service]
EnvironmentFile=/etc/myproj.env
sudo systemctl daemon-reload
sudo systemctl restart gunicorn-myproj.service

Если вам нужно проводить мягкие релизы, можно использовать systemctl restart в сочетании со стратегиями Gunicorn (--max-requests и др.). Для релизов без простоя см. наш разбор приёмов в статье о миграции без даунтайма zero-downtime.

systemd-юнит Gunicorn и журнал journalctl.

Диагностика и типичные ошибки

  • Ошибка 502 Bad Gateway: чаще всего Nginx не может открыть Unix-сокет. Проверьте, что файл /srv/myproj/run/gunicorn.sock существует и права позволяют пользователю Nginx его читать.
  • Статика не грузится: забыли collectstatic или неверный alias в Nginx. Проверьте, нет ли лишнего слеша или опечатки в пути.
  • CSRF/Session проблемы после включения HTTPS: проверьте SECURE_PROXY_SSL_HEADER, CSRF_COOKIE_SECURE и SESSION_COOKIE_SECURE в Django.
  • Загрузки обрываются: увеличьте client_max_body_size в Nginx и проверьте таймауты proxy_read_timeout.
  • Миграции «висят»: убедитесь, что нет долгих блокировок в БД. Локализуйте команду и запускайте в отдельности от перезапуска сервера.

Производительность и масштабирование

Подберите количество воркеров Gunicorn под CPU: часто используют правило 2–4 воркера на ядро для синхронных воркеров, но всё зависит от нагрузки и блокирующих операций. Для I/O-нагруженных участков рассмотрите асинхронные воркеры, однако это требует аккуратности с библиотеками. На стороне Nginx держите кэш статических ответов и корректные заголовки Cache-Control — это разгружает Gunicorn и уменьшает TTFB.

Если проект растёт, вынесите статику в отдельный CDN, а media — в объектное хранилище, но в рамках этой статьи мы фокусируемся на локальном диске для простоты и предсказуемости.

Итоги

Мы прошли весь цикл: от подготовки Python-окружения и структуры каталогов до запуска Django через Gunicorn, проксирования в Nginx, раздачи статики и media, применения миграций, настройки healthcheck и базового SSL. Такая схема проста, воспроизводима и легко автоматизируется. Дальше вы можете дополнять её CI/CD, метриками и алертами, а также усложнять деплой, не меняя фундаментальные компоненты.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: как исправить apt_pkg ImportError и восстановить python3-apt OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить apt_pkg ImportError и восстановить python3-apt

Ошибка apt_pkg ImportError или ModuleNotFoundError apt_pkg в Debian/Ubuntu обычно появляется после смены системного Python, подмен ...
Debian/Ubuntu: Kernel panic not syncing: VFS: Unable to mount root fs on unknown-block OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: Kernel panic not syncing: VFS: Unable to mount root fs on unknown-block

Ошибка Kernel panic not syncing с сообщением Unable to mount root fs on unknown-block в Debian и Ubuntu обычно связана с initramfs ...
Debian/Ubuntu: sudo: unable to change expired password при входе по SSH — как исправить OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: sudo: unable to change expired password при входе по SSH — как исправить

Если в Debian или Ubuntu при входе по SSH или запуске sudo появляется unable to change expired password, проблема обычно связана н ...