Production-развертывание web-приложения состоит из нескольких связанных этапов: подготовка конфигурации, сборка frontend, публикация backend, настройка DNS, reverse proxy и HTTPS, затем проверка пользовательских сценариев. Ошибка на любом уровне может проявиться одинаково: браузер покажет 404, 502 или пустую страницу, поэтому проверять систему нужно последовательно.
Рабочая схема выглядит так: DNS сопоставляет доменное имя с IP-адресом сервера, Nginx принимает HTTP или HTTPS-запрос, отдает статические файлы React-приложения и передает запросы с маршрутом /api внутреннему ASP.NET Core backend. После релиза проверяют DNS, TLS, маршрутизацию frontend, загрузку ресурсов, доступность API, авторизацию, логи и возможность отката.
Ниже приведен универсальный сценарий для Linux-сервера с systemd и Nginx. Команды рассчитаны на Debian или Ubuntu, а имена каталогов, порты, версии Node.js и .NET, выходную папку сборщика и адреса API нужно сверить с фактической структурой проекта.
Как развернуть web-приложение: краткий production-сценарий
Что должно работать после публикации
Готовый релиз дает предсказуемый результат для пользователя и администратора:
- домен разрешается в IP-адрес production-сервера;
- HTTP-запрос перенаправляется на HTTPS без циклов;
- сертификат выпущен для нужного домена, имеет доверенную цепочку и действующий срок;
- корневой маршрут открывает frontend-приложение;
- обновление страницы на вложенном маршруте SPA не приводит к ошибке Nginx;
- JavaScript, CSS, изображения, шрифты и favicon загружаются с ожидаемыми статусами;
- запросы к
/apiдоходят до backend, а backend отвечает без ошибок подключения к базе данных; - авторизация, cookies или токены работают через production-домен;
- логи Nginx и backend доступны, процесс приложения автоматически перезапускается после сбоя;
- предыдущая версия артефактов и конфигурации сохранена для rollback.
Порядок действий и точки контроля
- Подготовить приложение. Проверить версии runtime, production-переменные, секреты, миграции, health-check и резервную копию.
- Подготовить сервер. Создать пользователя и каталоги, установить Node.js, .NET runtime, Nginx и инструменты для диагностики.
- Собрать frontend. Получить production-артефакт и проверить, что в нем нет localhost, тестовых адресов и незапланированных source map.
- Опубликовать backend. Перенести результат сборки, запустить ASP.NET Core на внутреннем адресе и проверить endpoint напрямую с сервера.
- Разместить статические файлы. Настроить root Nginx на каталог конкретного релиза, а для SPA включить fallback на
index.html. - Настроить DNS. Проверить A и AAAA-записи, IP-адреса и отсутствие старых записей, направляющих трафик на другой сервер.
- Настроить reverse proxy. Передать запросы
/apiвнутреннему backend, а остальные маршруты оставить frontend. - Выпустить сертификат. После проверки HTTP-доступа подключить Let's Encrypt через Certbot и включить перенаправление на HTTPS.
- Проверить релиз. Выполнить smoke-тесты через браузер и curl, изучить логи, проверить авторизацию и основные бизнес-операции.
На каждой точке сначала фиксируйте ожидаемый результат. Если backend не отвечает локально, проверка Nginx и DNS не поможет найти первопричину. Если домен указывает на неправильный сервер, настройка сертификата будет работать с неверным virtual host.
Подготовка приложения и сервера до релиза
Проверка production-конфигурации и переменных окружения
Перед сборкой составьте список параметров, которые отличаются между development и production. Для frontend это обычно публичный адрес API, базовый путь приложения, имя окружения и флаги включения функций. Для backend список шире:
- режим окружения, например
Production; - строка подключения к базе данных;
- ключи подписи JWT, параметры OAuth и секреты cookies;
- разрешенные origins для CORS;
- лимиты размера тела запроса и загружаемых файлов;
- адреса SMTP, хранилища, очереди и других внешних сервисов;
- уровень логирования и параметры health-check;
- настройки прокси и доверенных forwarded-заголовков.
Публичные параметры frontend попадают в JavaScript и доступны каждому посетителю. Секреты backend нельзя помещать в React-код, файл .env, который уходит в каталог статических файлов, или открытый репозиторий. Храните их в защищенном файле окружения на сервере, в secret-хранилище CI/CD или в механизме управления секретами выбранной платформы.
Пример минимальной проверки перед сборкой:
node --version
dotnet --info
rg -n 'localhost|127[.]0[.]0[.]1|test|development' .env* src appsettings* 2>/dev/null
Проверка должна учитывать структуру проекта. Переменная VITE_API_URL используется сборщиком Vite, а проекты на Create React App могут использовать префикс REACT_APP_. Название параметра не универсально. После сборки ищите тестовые значения еще раз в итоговом каталоге.
Для ASP.NET Core задайте окружение через systemd или защищенный файл:
ASPNETCORE_ENVIRONMENT=Production
ASPNETCORE_URLS=http://127.0.0.1:5000
Фактические имена переменных зависят от приложения. Проверьте, что обязательный параметр не подменяется значением по умолчанию, а приложение завершается с понятной ошибкой при отсутствии критичного секрета.
Подготовка VPS и учетной записи для публикации
Для небольшого сервиса достаточно VPS или VDS с Linux. Объем CPU, RAM и диска выбирают по профилю нагрузки, размеру базы данных, числу пользователей и объему загружаемых файлов. На сервере заранее определите:
- DNS-имя приложения;
- публичные порты 80 и 443;
- временный административный доступ по SSH;
- внутренний порт backend, например 5000;
- каталог релизов и каталог общих данных;
- место для логов и резервных копий;
- пользователя, от которого работает backend;
- правила firewall и доступ к базе данных.
Для размещения production-сервера можно использовать облачную инфраструктуру с VPS, базами данных, хранилищем и Kubernetes, например Timeweb Cloud. Конкретный тариф выбирайте после оценки ресурсов, а не по одному числу vCPU.
Создайте отдельные каталоги для версий приложения. Один из практичных вариантов:
/srv/app/releases/<release>/frontend
/srv/app/releases/<release>/backend
/srv/app/current/frontend
/srv/app/current/backend
/srv/app/shared/backend.env
/srv/app/shared/uploads
/var/log/app
Символическая ссылка current позволяет переключать релиз без копирования файлов поверх работающей версии. У пользователя backend должен быть доступ к своему каталогу и загрузкам. Файл с секретами ограничьте правами владельца, например chmod 600 /srv/app/shared/backend.env. Статические файлы должны быть доступны Nginx на чтение, но не должны позволять веб-пользователю изменять исполняемые файлы.
Проверьте время, дисковое пространство и установленные компоненты:
timedatectl status
df -h
free -h
sudo systemctl status nginx
sudo nginx -v
Инструменты деплоя можно выбирать отдельно от способа запуска приложения. CI/CD, Docker, Kubernetes и shell-скрипты решают разные задачи по повторяемости, скорости выпуска и сопровождению. Критерии выбора разобраны в статье о системах развертывания приложений.
Health-check, миграции и резервная копия
Добавьте в backend легкий endpoint, например /healthz. Он должен быстро отвечать на запрос и показывать, что процесс принимает соединения. Отдельный readiness-check может проверять базу данных, очередь и другие обязательные зависимости. Эти проверки нельзя смешивать: живой процесс способен отвечать на /healthz, когда база данных недоступна.
Проверяйте endpoint локально на сервере:
curl -i http://127.0.0.1:5000/healthz
curl -i http://127.0.0.1:5000/api/version
Маршрут /api/version замените на безопасный диагностический или функциональный endpoint из проекта. Диагностический ответ не должен раскрывать секреты, строки подключения и внутренние адреса.
Миграции базы данных запускайте отдельным контролируемым шагом:
- сохраните резервную копию;
- проверьте список миграций и совместимость схемы с новой версией backend;
- примените изменения;
- проверьте readiness-check и безопасные API-методы;
- зафиксируйте результат в журнале релиза.
Для PostgreSQL резервную копию делают штатной утилитой pg_dump, для других СУБД используйте их инструменты. Резервная копия должна быть доступна для восстановления, а не только успешно создана. Перед боевым релизом проверьте восстановление на отдельной базе.
Публикация приложения на ASP.NET Core требует согласовать версию runtime, режим публикации и способ запуска. Практические варианты с systemd, IIS и Docker собраны в статье о развертывании .NET-приложений.
Сборка и публикация фронтенда
Production-сборка и проверка артефактов
На сервер передают результат production-сборки, а не исходную папку проекта. Используйте lock-файл и воспроизводимую установку зависимостей:
npm ci
npm run build
Команда npm run build может создать каталог dist, build или другое имя, заданное конфигурацией сборщика. Не угадывайте путь. Найдите его в настройках проекта или в выводе команды.
du -sh dist build 2>/dev/null
find dist build -maxdepth 2 -type f 2>/dev/null | sort | head -50
rg -n 'localhost|127[.]0[.]0[.]1|test-api|development' dist build 2>/dev/null
Проверьте результат по четырем признакам:
- сборка завершилась с кодом 0;
- каталог содержит
index.html, JavaScript, CSS и нужные медиафайлы; - адрес API соответствует production-среде;
- в итоговых файлах нет тестовых URL, лишних секретов и ошибок загрузки модулей.
Имя выходной папки и формат публичных переменных зависят от сборщика. Не переносите на сервер весь репозиторий, node_modules и локальные файлы окружения, если они не нужны для запуска.
Храните каждый frontend-артефакт отдельно: /srv/app/releases/20260906-1200/frontend. После проверки переключите ссылку current на новый каталог. Предыдущую папку не удаляйте до завершения smoke-теста и периода наблюдения.
Статические файлы и маршрутизация SPA
React SPA обычно возвращает один index.html, а переходы между страницами выполняет браузерный роутер. При прямом запросе к /settings Nginx сначала ищет физический файл. Если его нет, сервер должен вернуть index.html, чтобы роутер обработал путь.
Fallback не должен перехватывать API. Для этого задайте отдельный, более специфичный блок location /api/. Если он отсутствует или расположен после общего правила с неверной логикой, ошибка backend может маскироваться HTML-страницей frontend.
Проверьте следующие параметры:
- регистр имен каталогов и файлов, особенно при публикации после разработки на Windows;
- base URL или
basename, если приложение живет не в корне домена; - пути к favicon, изображениям, шрифтам и manifest-файлу;
- обработку неизвестного клиентского маршрута;
- политику source map, чтобы не публиковать исходный код без необходимости;
- совместимость URL ресурсов с HTTPS.
Пример базовой логики Nginx:
location /api/ {
proxy_pass http://127.0.0.1:5000;
}
location / {
try_files $uri $uri/ /index.html;
}
Если backend ожидает путь без префикса /api, настройку proxy_pass нужно изменить с учетом правил подстановки URI. Проверяйте фактический путь в логах backend.
Кэширование ресурсов без поломки нового релиза
Frontend может выглядеть устаревшим даже после успешной публикации. Причина часто связана с кэшем браузера, CDN или промежуточного proxy. Практичная схема использует разные правила:
index.htmlполучает короткое кэширование илиno-cache;- версированные JS и CSS-файлы с hash в имени можно кэшировать дольше;
- имена ассетов должны меняться при изменении их содержимого;
- кэш CDN очищают только после проверки, что новая версия действительно доступна на сервере.
Проверить заголовки можно так:
curl -I https://app.example/index.html
curl -I https://app.example/assets/app.abc123.js
Если index.html хранится без hash и получает длительный max-age, пользователь может продолжать загружать старую карту ассетов. После релиза смотрите вкладку Network в DevTools и сравнивайте версии index.html и JavaScript-файлов.
Публикация бэкенда и API
Запуск backend и проверка локального endpoint
Сначала докажите, что backend работает без Nginx и DNS. Для ASP.NET Core framework-dependent публикация обычно выглядит так:
dotnet restore
dotnet publish -c Release -o ./publish
Если сервер не содержит подходящего .NET runtime, используйте self-contained публикацию или установите совместимую версию runtime. Выбор должен соответствовать файлу проекта и принятому в команде способу доставки.
Для первичной проверки можно запустить приложение вручную:
ASPNETCORE_ENVIRONMENT=Production \
ASPNETCORE_URLS=http://127.0.0.1:5000 \
dotnet App.dll
В другой SSH-сессии выполните:
curl -i http://127.0.0.1:5000/healthz
ss -ltnp | rg ':5000'
В логах проверьте время старта, порт прослушивания, исключения, ошибки чтения конфигурации, подключения к базе данных и внешним сервисам. Ответ Nginx с кодом 200 не доказывает работоспособность backend, если Nginx отдал сохраненный HTML.
Права, секреты и состояние процесса
Процесс backend не должен зависеть от открытой SSH-сессии. Запускайте его через systemd, Docker или другой управляемый процесс-менеджер. Для классической Linux-установки подойдет отдельный systemd unit:
[Unit]
Description=Web application backend
After=network.target
[Service]
WorkingDirectory=/srv/app/current/backend
ExecStart=/usr/bin/dotnet /srv/app/current/backend/App.dll
User=app
EnvironmentFile=/srv/app/shared/backend.env
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
После создания unit выполните:
sudo systemctl daemon-reload
sudo systemctl enable --now app.service
sudo systemctl status app.service
sudo journalctl -u app.service -n 100 --no-pager
Проверьте, что после перезапуска сервис получает те же переменные окружения и открывает нужный порт. У пользователя app должен быть доступ к опубликованным DLL, конфигурации, временным каталогам и загрузкам. Доступ к файлу секретов ограничьте отдельно. Логи отправляйте в journald или в выделенный каталог с контролем ротации.
При необходимости запуск через Docker или Kubernetes можно связать с CI/CD и registry. Схема рабочего контура от commit до production разобрана в руководстве по DevOps-инструментам.
Миграции базы данных и совместимость релиза
Код backend и схема базы данных должны быть совместимы в момент переключения трафика. Безопасная последовательность выглядит так:
- сохранить текущие артефакты и резервную копию базы;
- проверить миграции на копии production-данных;
- применить обратно совместимые изменения схемы;
- запустить новый backend;
- выполнить health-check и smoke-тест API;
- проверить ошибки базы данных и ключевые запросы;
- переключить frontend на новый backend, если адрес API меняется.
Несовместимые изменения выполняйте поэтапно. Сначала добавьте новые поля или таблицы, затем выпустите код, который умеет работать со старой и новой схемой, и только после этого удаляйте устаревшие элементы. Такой порядок уменьшает риск, что rollback старого backend столкнется с уже удаленной колонкой.
Домен, DNS и обратный прокси Nginx
Какие DNS-записи проверить перед публикацией
DNS отвечает за сопоставление имени с IP-адресом. Он не перенаправляет HTTP-запросы к внутреннему порту и не превращает соединение в VPN или proxy-маршрут.
Для домена приложения проверьте:
- A-запись для IPv4;
- AAAA-запись, если сервер действительно доступен по IPv6;
- отсутствие старой A или AAAA-записи, ведущей на прежний сервер;
- отдельные записи для API-поддомена, если frontend и backend используют разные имена;
- TTL и время обновления записей у внешних резолверов.
dig +short app.example A
dig +short app.example AAAA
dig +short api.app.example A
Сравните ответы с публичными адресами сервера. Проверяйте запись из нескольких сетей, потому что локальный resolver может хранить старый ответ. Если AAAA указывает на сервер без настроенного IPv6, часть пользователей получит ошибку соединения, хотя IPv4-проверка пройдет.
После изменения DNS подождите обновления кэшей. Не меняйте сертификат и конфигурацию backend, пока запросы не попадают на ожидаемый сервер.
Маршрутизация frontend и /api в Nginx
Reverse proxy принимает внешний запрос и выбирает внутренний обработчик. Для приложения с React и ASP.NET Core Nginx может отдавать frontend с диска, а API передавать на 127.0.0.1:5000. Внешний порт backend при этом не требуется открывать в firewall.
Пример server block для первичной HTTP-проверки:
server {
listen 80;
listen [::]:80;
server_name app.example www.app.example;
root /srv/app/current/frontend;
index index.html;
client_max_body_size 20m;
location /api/ {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
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_connect_timeout 5s;
proxy_read_timeout 60s;
}
location / {
try_files $uri $uri/ /index.html;
}
}
Замените домен, root, внутренний порт и лимит тела запроса. Если приложение использует WebSocket, добавьте отдельную настройку Upgrade-заголовков и проверьте таймауты. Если backend принимает большие файлы, согласуйте client_max_body_size с лимитами самого приложения и хранилища.
Заголовок X-Forwarded-Proto сообщает приложению исходную схему запроса. ASP.NET Core должен доверять forwarded-заголовкам только от разрешенного reverse proxy. Иначе приложение может строить HTTP-ссылки вместо HTTPS или зациклить редирект.
Проверка конфигурации и безопасное применение изменений
Перед reload проверяйте синтаксис и активную конфигурацию:
sudo nginx -t
sudo nginx -T
sudo systemctl reload nginx
sudo journalctl -u nginx -n 100 --no-pager
Команда nginx -t должна завершиться успешно. Команда nginx -T помогает увидеть, какой server block содержит нужный server_name и не перекрывается ли он другим virtual host.
После reload проверьте раздельно frontend и API:
curl -i http://app.example/
curl -i http://app.example/settings
curl -i http://app.example/api/health
Ожидаемый ответ для /settings зависит от SPA: сервер может вернуть index.html, а само приложение показать страницу 404. Для /api/health должен вернуться ответ backend, а не HTML frontend.
HTTPS и SSL-сертификат Let's Encrypt через Certbot
Что подготовить до выпуска сертификата
Certbot подтверждает контроль над доменом через внешний запрос. До выпуска сертификата проверьте:
- DNS возвращает IP именно этого сервера;
- server block содержит все нужные имена в
server_name; - порт 80 доступен из интернета;
- Nginx запущен и отвечает по HTTP;
- нет конфликтующего virtual host, который принимает запрос первым;
- в firewall и у облачного провайдера разрешены порты 80 и 443;
- в сертификат включены основной домен,
wwwи API-поддомен, если они используются.
Для wildcard-сертификата требуется отдельный DNS-сценарий подтверждения владения. Простая команда с параметром -d не покрывает wildcard автоматически.
Выпуск и подключение сертификата в Nginx
Пример ниже рассчитан на Ubuntu или Debian с пакетным Nginx:
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d app.example -d www.app.example
Certbot найдет Nginx-конфигурацию, запросит сертификат и предложит изменить server block. Перед подтверждением проверьте список доменов. Пути к сертификатам обычно находятся в каталоге:
/etc/letsencrypt/live/app.example/fullchain.pem
/etc/letsencrypt/live/app.example/privkey.pem
После изменения конфигурации повторно выполните:
sudo nginx -t
sudo systemctl reload nginx
sudo certbot certificates
Сертификат не исправляет DNS, ошибки backend или неправильный proxy_pass. Сначала должен работать HTTP-вход через правильный Nginx server block, затем подключается TLS.
Редирект HTTP на HTTPS и автопродление
Для отдельного HTTP server block используйте перенаправление:
server {
listen 80;
listen [::]:80;
server_name app.example www.app.example;
return 301 https://$host$request_uri;
}
Проверьте, что HTTPS-конфигурация не отправляет запрос обратно на HTTP. В браузере и через curl проверьте отсутствие mixed content: frontend, API, изображения и шрифты должны загружаться по HTTPS.
Сертификаты Let's Encrypt обычно действуют 90 дней. На сервере должен работать таймер или другой планировщик Certbot:
sudo certbot renew --dry-run
systemctl list-timers | rg 'certbot|letsencrypt'
Тестовый запуск renewal не заменяет наблюдение за результатом. После реального продления проверьте reload Nginx, новый срок действия сертификата и доступность домена.
Как проверить, что сертификат установлен правильно
Проверяйте сертификат по нескольким признакам:
- имя домена входит в Subject Alternative Name;
- срок действия еще не закончился;
- браузер не показывает предупреждение о недоверенной цепочке;
- сертификат отдается нужным HTTPS server block;
- соединение работает по IPv4 и IPv6, если AAAA-запись опубликована;
- в логах Nginx нет ошибок TLS и неверного SNI.
openssl s_client -connect app.example:443 -servername app.example </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
curl -I http://app.example/
curl -I https://app.example/
Первая команда показывает имя, издателя и даты сертификата. Вторая должна вернуть редирект, третья проверяет конечный HTTPS-ответ.
Проверка приложения после выкладки
Проверка домена, HTTPS и маршрутизации
Проводите post-release проверку по уровням. Сначала убедитесь, что имя разрешается правильно, затем проверяйте TLS и Nginx.
- Сравните DNS-ответы с IP production-сервера.
- Откройте HTTP и убедитесь, что возвращается код 301 или 308 с адресом HTTPS.
- Откройте HTTPS и проверьте сертификат.
- Загрузите корневой маршрут.
- Обновите страницу на двух вложенных маршрутах SPA.
- Откройте неизвестный путь и проверьте согласованное поведение приложения.
- Сопоставьте запросы с access log и error log Nginx.
curl -I http://app.example/
curl -I https://app.example/
curl -i https://app.example/settings
curl -i https://app.example/unknown-route
Не делайте вывод о состоянии backend по одному ответу главной страницы. Nginx может корректно отдать index.html, даже когда API-процесс остановлен.
Проверка статических ресурсов и frontend
В DevTools откройте вкладку Network и перезагрузите страницу без использования старых результатов кэша. Проверьте:
- статус JavaScript и CSS;
- загрузку изображений, шрифтов и favicon;
- отсутствие 404 на ассеты;
- отсутствие mixed content;
- ошибки Content Security Policy, если политика включена;
- соответствие адреса API production-домену;
- обновление страницы на вложенном маршруте;
- совпадение версии
index.htmlс именами загружаемых ассетов.
Пустая страница часто означает JavaScript-ошибку, неправильный base URL, несовместимый старый asset или исключение при инициализации приложения. Смотрите Console и полный URL каждого неуспешного запроса.
Проверка API, CORS и авторизации
Проверьте health-check и несколько безопасных методов через публичный HTTPS-адрес. Для каждого запроса фиксируйте метод, URL, заголовки, тело ответа, HTTP-код и время ответа:
curl -i https://app.example/api/health
curl -i https://app.example/api/version
curl -i -X OPTIONS https://app.example/api/profile \
-H 'Origin: https://app.example' \
-H 'Access-Control-Request-Method: GET'
Если frontend и API используют один origin, браузерный CORS обычно не создает отдельного междоменного запроса. При API-поддомене или другом домене проверьте:
- точное значение
Originбез лишнего завершающего слеша; - разрешенные методы и заголовки;
- ответ на OPTIONS;
- флаг
Access-Control-Allow-Credentials, если используются cookies; - флаги Secure и SameSite для cookies;
- срок действия и формат токена;
- коды 2xx, 4xx и 5xx на реальных сценариях.
Проверяйте API из браузера и напрямую через curl. Прямой ответ curl подтверждает сетевую доступность, но не доказывает, что frontend отправляет правильные cookies, токены и заголовки.
Логи, метрики и контроль после релиза
Во время smoke-теста держите открытыми логи Nginx и backend:
sudo tail -f /var/log/nginx/access.log /var/log/nginx/error.log
sudo journalctl -u app.service -f
Проверьте, что:
- запросы к frontend получают ожидаемые статусы;
- запросы к
/apiпоявляются в логах backend; - время ответа не выросло из-за таймаутов или медленной базы;
- процесс не перезапускается циклически;
- диск не заполняется логами или загрузками;
- подключения к базе данных и внешним сервисам не завершаются ошибками.
Диагностические endpoints ограничьте по доступу или не включайте в публичную конфигурацию. Зафиксируйте базовые значения времени ответа, частоты 4xx и 5xx, количества перезапусков и свободного места. Наблюдайте за системой после релиза достаточно долго, чтобы пройти обычный пользовательский сценарий и фоновые задачи.
Типовые ошибки при деплое и порядок диагностики
DNS и Nginx: сайт не открывается или ведет не туда
Начинайте с DNS, затем переходите к Nginx. Не меняйте TLS, пока домен не попадает на правильный сервер.
| Симптом | Вероятный слой | Проверка | Исправление |
|---|---|---|---|
| Домен не разрешается | DNS | dig +short, проверка A и AAAA | Исправить записи и дождаться обновления кэшей |
| Открывается чужой сервер | DNS или server_name | Сравнить IP, server_name и nginx -T | Убрать старую запись или исправить virtual host |
| HTTP работает, HTTPS выдает ошибку | TLS или firewall | Проверить порт 443, сертификат и error log | Открыть порт и подключить сертификат к нужному server block |
| Браузер показывает TLS-предупреждение | Сертификат | Проверить имя, срок и цепочку | Выпустить сертификат для фактического домена и перезагрузить Nginx |
Frontend доступен, но приложение не работает
Если HTML загружается, а интерфейс пустой, проверяйте браузерную Console и Network. Частые причины:
- JavaScript или CSS получили 404;
- production-сборка содержит localhost;
- неверно задан base URL;
- SPA fallback отсутствует;
- index.html ссылается на старые или удаленные assets;
- браузер, CDN или service worker отдает старую версию;
- API заблокирован CORS или обращается по HTTP.
Сравните фактические URL ресурсов с каталогом релиза на сервере. Проверьте вложенный маршрут после прямого ввода адреса и после обновления страницы. Если frontend отдает HTML для запроса к /api, исправьте порядок и содержание Nginx location.
502, 500 и ошибки API после релиза
| Симптом | Что проверить сначала | Где искать причину |
|---|---|---|
| 502 Bad Gateway | Процесс, внутренний порт, адрес proxy_pass | Статус systemd, локальный curl, error log Nginx |
| 500 Internal Server Error | Исключение backend и production-переменные | journalctl, подключение к базе, миграции и права |
| 401 или 403 | Cookies, токен, срок действия и policy авторизации | Network браузера, заголовки и логи backend |
| 404 на API | Путь с префиксом /api и правило proxy | Маршруты backend и итоговая конфигурация Nginx |
| Медленный ответ | Таймауты, база данных и внешние сервисы | Метрики, логи запросов и состояние зависимостей |
Код 502 обычно означает, что Nginx не получил корректный ответ от upstream. Код 500 приходит уже от приложения или промежуточного обработчика. Один и тот же HTTP-код на разных этапах может иметь разные причины, поэтому фиксируйте полный ответ, метод, время, заголовки и связанные записи логов.
Откат и повторный выпуск
Откат должен быть заранее подготовленной операцией. Храните версии frontend и backend раздельно, сохраняйте предыдущий Nginx server block и не удаляйте рабочие артефакты сразу после релиза.
При критичной ошибке:
- зафиксируйте симптом и время начала;
- остановите дальнейшее распространение нового релиза;
- верните ссылку
currentна предыдущий frontend и backend; - восстановите прежнюю конфигурацию Nginx, если она менялась;
- проверьте совместимость схемы базы данных;
- при необходимости восстановите резервную копию;
- перезапустите сервис и выполните полный smoke-тест;
- сохраните причину сбоя и условия, при которых повторный выпуск будет безопасным.
Если миграция необратима, простой возврат файлов не восстановит приложение. Для таких изменений нужен отдельный план совместимости или восстановление базы. Подходы blue-green, canary и rolling deployment, а также сценарии отката разобраны в статье об обновлениях и отказоустойчивости.
Итоговый чек-лист развертывания web-приложения
- Версии и зависимости: проверены Node.js, .NET runtime, пакетный менеджер, lock-файл и команды сборки.
- Production-конфигурация: задан режим окружения, адрес API, строка подключения, origins, секреты и параметры внешних сервисов.
- Секреты: не попали во frontend, публичный репозиторий и каталог статических файлов.
- Frontend: production-сборка завершилась успешно, каталог артефактов найден, localhost и тестовые адреса удалены.
- Backend: приложение запускается как управляемый сервис, слушает внутренний порт, пишет логи и перезапускается после сбоя.
- Health-check: endpoint процесса и readiness-проверка зависимостей дают ожидаемые ответы.
- База данных: резервная копия создана и проверена восстановлением, миграции протестированы.
- Файлы: права на приложение, конфигурацию, загрузки и логи разделены.
- DNS: A и при необходимости AAAA указывают на актуальный сервер, старые записи удалены.
- Nginx: frontend отдается из нужного каталога,
/apiпередается backend, forwarded-заголовки настроены. - SPA: корневой и вложенные маршруты открываются после обновления страницы, API не перехватывается fallback.
- HTTPS: сертификат Let's Encrypt выдан для всех нужных имен, порт 443 доступен, HTTP перенаправляется на HTTPS.
- Автопродление:
certbot renew --dry-runпроходит успешно, таймер или планировщик активен. - Статические ресурсы: JavaScript, CSS, изображения, шрифты и favicon загружаются без 404 и mixed content.
- API: health-check, безопасные методы, CORS, OPTIONS и авторизация проверены через браузер и curl.
- Наблюдение: логи Nginx и backend доступны, проверены ошибки 4xx и 5xx, свободное место и перезапуски.
- Откат: предыдущие артефакты, конфигурация и резервная копия доступны, порядок возврата проверен.
Команды и пути адаптируйте под фактическую ОС, версии runtime, сборщик frontend и маршруты backend. После каждого изменения отмечайте дату проверки, версию приложения и результат smoke-теста. Такой журнал помогает отличить ошибку нового релиза от изменения DNS, сертификата или конфигурации сервера.