Zero‑downtime деплой на виртуальном хостинге — это вполне достижимая практика, даже если у вас нет root‑доступа и нельзя перезапускать веб‑сервер. Ключ — в релизных директориях, симлинке current
и атомарном переключении на новый релиз. Такой подход минимизирует риски, ускоряет релизы и даёт гарантированный откат за секунды.
Что такое zero‑downtime деплой и почему это работает на виртуальном хостинге
Zero‑downtime деплой — это публикация новой версии без перерывов в обслуживании. Посетитель в любой момент получает ответ, даже когда вы выкатываете свежую сборку. На виртуальном хостинге это достигается за счёт подготовки релиза в отдельной директории и атомарного обновления симлинка, на который смотрит DocumentRoot вашего сайта. Переключение симлинка — операция уровня файловой системы, она выполняется мгновенно и не требует перезапуска сервисов.
Идея проста: у вас есть папка releases/
с датированными релизами, папка shared/
с общими данными (загрузки, кэш, конфиги) и симлинк current
, который указывает на активный релиз. Веб‑сервер обслуживает current/public
. Вы загружаете новую версию в releases/2025XXXXXX
, прогреваете её, затем атомарно переключаете current
— и всё.

Базовая структура проекта
~/app/
releases/
20250101123000/
public/
vendor/
...
shared/
storage/
uploads/
.env
current -> releases/20250101123000
Что хранить в shared/
:
uploads/
,storage/
, директории с пользовательским контентом и файлами кэша;- конфигурацию и секреты (
.env
), чтобы не класть их в каждый релиз; - долгоживущие кэши и сессии, если они файловые.
В каждом релизе создаются симлинки на эти общие директории, например releases/<ts>/storage -> ../../shared/storage
, releases/<ts>/.env -> ../../shared/.env
. Это позволяет менять код, не трогая данные.
Связываем веб‑корень с релизом
На большинстве виртуальных хостингов DocumentRoot — это public_html/
или www/
. Идеально, если можно настроить путь на ~/app/current/public
. Если нельзя — сделайте симлинк из public_html
на ~/app/current/public
. Обязательно проверьте, что на веб‑сервере разрешены симлинки (на Apache — Options FollowSymLinks
), а права доступа позволяют чтение.
Совет: держитеpublic/
как корень сайта, чтобы не светить исходники. Если у вас CMS без каталогаpublic
, используйте .htaccess для блокировки доступа к служебным файлам.
Пайплайн деплоя: шаг за шагом
1) Сборка артефакта
На локальной машине или в CI собираем артефакт релиза: зависимости, минифицированные ассеты, скомпилированный фронтенд. Для PHP‑проектов практично собирать vendor/
локально под совместимую версию ОС/архитектуры и загружать как есть. Это ускорит выкладку на виртуальном хостинге и исключит долгую установку пакетов на сервере.
2) Выгрузка на сервер rsync‑ом
Для передачи файлов используйте rsync
. Он быстр, докачивает инкрементально и экономит трафик. Передаём в новую релизную директорию, названную по таймстемпу:
TS=$(date +%Y%m%d%H%M%S)
REL=releases/$TS
ssh user@host "mkdir -p ~/app/$REL"
rsync -az --delete \
--exclude ".git" \
--exclude "node_modules" \
--exclude "storage" \
./ user@host:~/app/$REL
Опцию --delete
используйте только при отправке в уникальную новую папку релиза, а не при обновлении shared/
. Если нужно экономить место, задействуйте --link-dest
к предыдущему релизу — неизменившиеся файлы будут хардлинкованы, но учтите, что на некоторых тарифах жёсткие ссылки могут быть ограничены.
3) Привязываем shared‑данные
После загрузки создайте внутри релиза симлинки на общие директории и конфиги:
ssh user@host "cd ~/app/$REL && \
ln -s ../../shared/storage storage && \
ln -s ../../shared/uploads uploads && \
ln -s ../../shared/.env .env"
Если проект требует прав на запись, проверьте, что umask и права на shared/
корректны (обычно 755 для директорий и 644 для файлов достаточно; для записываемых директорий — 775/777 в зависимости от модели пользователя веб‑сервера).
4) Предпродовые шаги
- Прогрев кэшей, генерация автозагрузки, оптимизация роутов — всё это делайте в каталоге нового релиза, ещё до переключения.
- Миграции БД — только обратно совместимые. Вносите сначала добавочные изменения (колонки по умолчанию, новые таблицы), а разрушающие шаги откладывайте на следующий релиз.
- Проверьте новый релиз через временный URL или симлинк, не задействующий
current
(например,current_canary
для теста).
5) Atomic deploy: атомарное переключение
Критический момент — сменить current
так, чтобы ни одна просьба посетителя не попала в «пустоту». Самый надёжный путь — создать новый симлинк и переименовать его поверх старого одной операцией rename(2)
:
ssh user@host "cd ~/app && \
ln -s $REL current.new && \
mv -Tf current.new current"
mv -T
заставляет трактовать цель как файл, а не каталог; в связке с -f
вы получаете атомарную замену старого симлинка на новый. На некоторых минималистичных окружениях mv
без -T
тоже корректно заменит симлинк, но поведение может отличаться.
Если на вашем окружении нет поддержки-T
, можно использоватьln -sfn
, но это не полностью атомарно (на миллисекунды симлинк исчезнет). В большинстве случаев это незаметно, однако для нагруженных сайтов лучше обеспечить именно переименование готового симлинка.
6) Пост‑деплой и здоровье
- Сделайте лёгкий health‑check (HTTP‑запрос к эндпойнту статуса, чтение версии из файла, простая SQL‑проверка).
- Обновите ассеты с версионированием (хеш в имени файла), чтобы клиенты не тянули старый кэш.
- OPcache: путь меняется вместе с релизом, поэтому кешированные скрипты не конфликтуют. На большинстве хостингов включён
opcache.validate_timestamps
, и новый путь автоматически загрузится. Подробнее — в статье про OPcache и оптимизации на шаред‑хостинге.
Откаты без боли
Zero‑downtime означает и мгновенный откат. Держите несколько прошлых релизов и переключайтесь назад тем же атомарным приёмом. Пример простого отката на предыдущий таймстемп:
ssh user@host "cd ~/app && \
PREV=$(ls -1dt releases/* | sed -n '2p') && \
[ -n \"$PREV\" ] && ln -s \"$PREV\" current.rollback && \
mv -Tf current.rollback current"
Храните историю релизов (например, 5–10 шт.) и автоматически чистите старые, чтобы не упираться в квоту. Удалять можно смело после нескольких успешных выкладок и отсутствия ошибок в логах.
Детали rsync для безопасного деплоя
-a
сохраняет права и симлинки;-z
сжимает трафик;--info=progress2
помогает контролировать прогресс.--delete
используйте только в пределах нового релиза.--link-dest=../<prev>
для дедупликации неизменившихся файлов между релизами — полезно при частых релизах.- Чётко задайте
--exclude
для.git
,node_modules
,tests
, локальных кэшей, чтобы не тянуть лишнее.
Миграции без простоя: базовые приёмы
- Разделяйте миграции на «добавочные» и «разрушающие». Сначала добавочные: новые таблицы, колонки с дефолтом и NULL‑совместимостью, индексы.
- Пишите код, совместимый со старой и новой схемой (feature flags, двусторонняя поддержка колонки в течение нескольких релизов).
- Тяжёлые DDL‑операции выполняйте ночью или используйте онлайн‑алгоритмы MySQL (
ALGORITHM=INPLACE
,LOCK=NONE
, когда возможно). - Делайте
mysqldump --single-transaction
как страховку; это не даёт zero‑downtime само по себе, но спасёт от потери данных.
Интеграция с Git и CI
Вместо того, чтобы держать git
на проде, надёжнее собирать артефакты в CI и отправлять их rsync‑ом. Подход «build once, deploy many» гарантирует повторяемость. Минимальный сценарий в CI: заархивировать сборку, передать на сервер, распаковать в releases/<ts>
, связать shared
, сделать health‑check и переключить current
. Результат: быстрый, предсказуемый и обратимый деплой.
Пример скрипта деплоя с откатом
# локально: ./deploy.sh user host ~/app
USER=$1
HOST=$2
ROOT=$3
TS=$(date +%Y%m%d%H%M%S)
REL=releases/$TS
ssh $USER@$HOST "mkdir -p $ROOT/$REL"
rsync -az --delete \
--exclude ".git" \
--exclude "node_modules" \
--exclude "storage" \
./ $USER@$HOST:$ROOT/$REL
ssh $USER@$HOST "cd $ROOT/$REL && \
ln -s ../../shared/storage storage && \
ln -s ../../shared/uploads uploads && \
ln -s ../../shared/.env .env && \
cd $ROOT && ln -s $REL current.new && mv -Tf current.new current"
# откат на предыдущий релиз
# ssh $USER@$HOST "cd $ROOT && PREV=$(ls -1dt releases/* | sed -n '2p'); \
# [ -n \"$PREV\" ] && ln -s \"$PREV\" current.rollback && mv -Tf current.rollback current"
Скрипт предельно простой: создаёт релиз, синхронизирует файлы, привязывает shared
, переключает current
. Откат — симметричный.
Оптимизация ассетов и кэшей
Чтобы уменьшить задержки при первом хите, прогревайте кэши до переключения. Для PHP‑фреймворков это может быть «компиляция» контейнера, предзагрузка маршрутов и конфигов. Статические ассеты следует версионировать по хешу файла, чтобы исключить коллизии кешей CDN/браузеров после релиза.
Лимиты и особенности виртуального хостинга
- Квота диска: держите только ограниченное число релизов и включайте дедупликацию через
--link-dest
, если доступна. - Симлинки: проверьте, что веб‑сервер следует симлинкам и что симлинки внутри DocumentRoot разрешены.
- Оптимизация по времени: крупные каталоги (например,
vendor/
) лучше собирать заранее и передавать целиком, чем устанавливать пакеты на сервере. - Ограничения по CPU/IO: избегайте тяжёлых операций во время пикового трафика.
Если задачам уже тесно на шаред‑хостинге (root, фоновые воркеры, очереди, кастомные демоны) — рассмотрите перенос на VDS. Подробный план миграции — в статье как переехать со shared на VDS.

Диагностика и логирование
Сохраняйте номер релиза в файле REVISION
и отдавайте его через скрытый эндпойнт для быстрой диагностики. При проблемах по логам веб‑сервера и PHP‑FPM быстро понятно, на каком релизе возникла ошибка. Логи тоже можно положить в shared/
, чтобы не терять историю при переключениях.
Мини‑чек‑лист перед релизом
- Свежий бэкап БД и файлов
shared/
. - Релиз собирается в CI, зависимости зафиксированы (
composer.lock
,package-lock.json
). - Релизная папка создана,
shared
привязан симлинками. - Критичные миграции либо онлайн, либо отложены.
- Health‑check успешен на canary‑путь.
- Переключение
current
— черезmv -Tf
. - План отката понятен: какой релиз «предыдущий» и как его вернуть.
FAQ
Нужно ли перезапускать веб‑сервер для подхвата нового кода?
Нет. Мы переключаем путь, а не переписываем файлы на месте. OPcache загрузит скрипты из нового пути, и они не конфликтуют со старыми.
Можно ли делать деплой без SSH?
Технически можно через SFTP, но без SSH сложно обеспечить атомарность и пост‑деплой шаги. Для надёжного zero‑downtime понадобится именно SSH.
Как безопасно чистить старые релизы?
Периодически удаляйте всё, что старше N последних релизов, кроме того, на который указывает current
. Убедитесь, что нет активных откатов.
Что с миграциями, блокирующими таблицы?
Ищите онлайн‑варианты, разбивайте на шаги или переносите на низкую нагрузку. В крайнем случае используйте короткое техническое окно.
Как хранить секреты?
Вынесите их в shared/.env
и ссылайтесь симлинком. Не включайте секреты в git и релизные архивы.
Итоги
Деплой через релизные директории и симлинки — простой и надёжный способ получить zero‑downtime на виртуальном хостинге. Вы готовите релиз «в стороне», проверяете его, затем «атомно» переключаете current
одной операцией. В довесок получаете мгновенные откаты и контролируемый жизненный цикл релизов. Такой подход одинаково хорошо работает для PHP‑фреймворков и популярных CMS — нужен лишь SSH и дисциплина в миграциях.