Хостинг

Переносим готовый Laravel-проект на хостинг: почему не по FTP и что сделать после загрузки

Эдгар Косулин 7 мин чтения

Серия «Хостинг без магии» · часть 17 из 17

Содержание

6 ноября клиент переносил к нам на пробный хостинг готовый интернет-магазин на Laravel, рабочий проект на несколько тысяч файлов. К вечеру сайт отдавал 500, часть файлов так и не загрузилась, а сертификат для домена не выпускался.

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

Почему одни и те же файлы не доходили по FTP

Клиент заливал проект FTP-программой, файл за файлом. Почти всё дошло, а шесть PHP-файлов каждый раз застревали на одном месте. Сервер получал первые 1 452 байта (у одного файла 7 260), подтверждал приём и ждал продолжения. Дальше от клиента не приходило ничего по 40-60 минут.

Программа повторяла загрузку снова и снова. В итоге три файла остались на сервере обрезанными, а один JSON-файл пустым. Обрезанный PHP-файл роняет с 500 любую страницу, которой нужен этот класс.

Почему застревали именно эти файлы, мы точно не знаем. С сервера видно, что данные не пришли, но не видно, где они остановились. Так обычно ведёт себя антивирус или фильтр на компьютере либо по пути: он проверяет передачу и задерживает файлы с определённым содержимым. Обычный FTP идёт без шифрования, и такому фильтру есть во что заглянуть.

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

До загрузки проверьте, какой PHP в консоли

Подключитесь по SSH и выполните:

php -v

У сайта и у команды php в консоли настройки разные. У нашего клиента сайт работал на PHP 8.4, а в SSH команда php запускала 7.2. Свежие версии Laravel требуют PHP 8.2 или 8.3, так что Composer отказался бы ставить пакеты, а artisan сообщил бы, что зависимостям нужна версия новее. Версия в консоли должна совпадать с версией сайта, иначе всё дальнейшее бессмысленно.

Подробнее, почему так бывает и что ещё сверить, кроме версии, мы писали в статье Современный PHP-хостинг: зачем Laravel и Symfony нужны SSH, Composer, Node.js, cron и отдельная public-директория.

Как загрузить проект одним архивом

Надёжнее передать проект одним файлом и распаковать его на сервере. Если архив дошёл целиком, дошли и все файлы. Если нет, распаковка сразу скажет, что архив повреждён.

  1. Упакуйте проект в zip без папок vendor и node_modules: их на хостинге поставят Composer и npm, а без них архив в разы меньше. Файлы composer.lock и package-lock.json в архив положите обязательно.
  2. Загрузите архив в домашнюю папку по SFTP или через файловый менеджер панели. SFTP работает поверх SSH, с теми же логином и паролем, и шифрует передачу целиком.
  3. Распакуйте его по SSH в папку сайта.

В Linux и macOS архив собирается так:

zip -r project.zip . -x "vendor/*" "node_modules/*" ".git/*"

На Windows то же делает любой архиватор: выделите всё, кроме двух папок. Распаковка на хостинге:

unzip -o ~/project.zip -d ~/www/example.ru

Если проект лежит в Git, архив не нужен: склонируйте репозиторий прямо на хостинге. Как на этом построить весь дальнейший деплой с откатом, мы разбирали в статье Деплой без FTP на виртуальном хостинге: Git, Composer и безопасный откат.

Удалите кеш с компьютера разработчика

В папке bootstrap/cache Laravel хранит то, что собрал на компьютере, где проект запускали. Её легко увезти вместе с проектом, и это первая причина 500 после переноса.

Файлы packages.php и services.php содержат список пакетов и их сервис-провайдеров, в том числе пакетов для разработки. На хостинге их обычно не ставят (composer install --no-dev), а Laravel при старте ищет класс из списка и не находит. Падает и сайт, и сам artisan, включая команду, которая этот список пересобрала бы. Если разработчик запускал php artisan config:cache, рядом лежит ещё и config.php с его локальными настройками базы, и .env на хостинге Laravel просто не читает.

Удалите эти файлы, Laravel создаст их заново:

cd ~/www/example.ru
rm -f bootstrap/cache/*.php

Заполните .env под хостинг

В .env после переноса обычно лежат настройки с компьютера разработчика. Для рабочего сайта нужны свои:

APP_ENV=production
APP_DEBUG=false
APP_URL=https://example.ru

DB_CONNECTION=mysql
DB_HOST=localhost
DB_DATABASE=имя_базы
DB_USERNAME=пользователь_базы
DB_PASSWORD=пароль_базы

APP_DEBUG=false обязателен: с true страница ошибки показывает любому посетителю пути к файлам, куски кода и детали настроек. APP_URL пишите с https и новым доменом: по нему Laravel строит ссылки в письмах и везде, где адрес не берётся из запроса.

APP_KEY при переносе живого сайта оставьте прежним. Им зашифрованы сессии и всё, что проект шифровал в базе. Новый ключ нужен только новому сайту. Подробнее об этом и других настройках в статье Ошибка 500 после загрузки сайта: где искать причину до обращения в поддержку.

Сначала дамп базы, потом миграции

Если на старом сайте есть данные, импортируйте дамп в новую базу до первого запуска миграций. Миграции на пустой базе создадут таблицы, и дамп потом упрётся в «таблица уже существует». В обратном порядке всё проще: Laravel увидит в таблице migrations, что старые миграции уже выполнены, и запустит только новые.

Установите зависимости и соберите проект

Дальше в папке сайта:

composer install --no-dev --optimize-autoloader
npm ci
npm run build
php artisan storage:link
php artisan migrate --force
php artisan optimize

composer.lock нужен здесь. Без него Composer подберёт свежие версии пакетов в рамках composer.json, и на хостинге окажется не тот код, который проверяли у себя. По той же причине npm ci работает только с package-lock.json. Если Node.js на хостинге нет, соберите фронт у себя и загрузите готовую папку public/build.

--force у миграций нужен потому, что в режиме production Laravel спрашивает подтверждение. optimize кеширует настройки и маршруты, поэтому после любой правки .env его надо запускать заново.

Добавьте cron для планировщика и очереди

На виртуальном хостинге нет постоянно работающего обработчика очереди, поэтому и расписание, и очередь запускает cron раз в минуту:

* * * * * cd ~/www/example.ru && php artisan schedule:run >> /dev/null 2>&1
* * * * * cd ~/www/example.ru && php artisan queue:work --stop-when-empty >> /dev/null 2>&1

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

Почему не выпускается SSL-сертификат

У нашего клиента A-запись домена уже указывала на наш сервер, а www по-прежнему на старый хостинг. Let's Encrypt проверяет каждое имя, которое попадёт в сертификат. Проверка www не проходила, и сертификат не выдавался ни на одно из двух имён.

Лечится одной записью в DNS: A-запись www на адрес нового сервера или CNAME www на основной домен. Панель повторяет попытку выпуска сама, и после обновления DNS сертификат появляется.

Что было не так на нашей стороне

Первое касалось опции «Подготовить окружение под фреймворк». С 19 августа по 6 ноября она собирала окружение не полностью. Сайт, база и корень сайта в папке public создавались, а PHP для SSH, скрипт деплоя, файл с доступами к базе и задания cron нет. Поэтому у клиента в консоли и оказался PHP 7.2.

Эти шаги биллинг выполняет на сервере хостинга по SSH, своим ключом. Ключ был разрешён только со старого адреса биллинга, а 19 августа биллинг переехал на новый сервер. Сбой записывался в карточку заказа, но уведомления о нём не было, поэтому он долго оставался незамеченным. Теперь, если какой-то шаг не прошёл, сотрудникам сразу приходит уведомление с текстом ошибки. Двум клиентам, у которых окружение собралось не полностью, мы доделали его вручную.

Второе касалось настроек FTP. 6 ноября с 19:15 примерно до 19:52 по Москве FTP не пускал никого из клиентов. Зависшие загрузки из этой истории заняли все 20 мест, которые FTP-сервер выделял на всех сразу, а ограничения на одного клиента не было. Зависшее соединение держалось шесть часов. Это значение осталось с весенней отладки: его ставили временно и не вернули к обычному. По умолчанию такое соединение рвётся через час.

После переустановки сервера в ночь на 8 ноября зависшая передача рвётся через 5 минут, соединение без передачи через 15. С одного адреса можно открыть не больше 8 соединений, на одного пользователя не больше 10, а всего мест 60.

Как это устроено на Siteko. SSH, Composer и Node.js есть на тарифах Master и Expert. При заказе там можно включить опцию «Подготовить окружение под фреймворк»: одна версия PHP для сайта и консоли, корень сайта в public, база с доступами в файле DB-CREDENTIALS.txt, cron для планировщика и очереди и скрипт laravel-deploy.sh в домашней папке. Скрипт выполняет установку и сборку из раздела выше, а .env и дамп базы остаются за вами. Если версия PHP в консоли не совпадает с сайтом, напишите в поддержку. Если переносить самим не хочется, пришлите архив проекта и дамп базы или доступ к репозиторию: перенос сайта к нам бесплатный.

Если после всех шагов сайт всё равно отдаёт 500, откройте storage/logs/laravel.log: там почти всегда написано, какого класса, файла или настройки не хватает. Порядок поиска по шагам есть в статье Ошибка 500 после загрузки сайта.

Серия

Хостинг без магии

  1. 1. Как выбрать хостинг для сайта и не купить себе вторую работу
  2. 2. Виртуальный хостинг, VPS или облако: что действительно нужно вашему сайту
  3. 3. Гигабайты ничего не решают: как читать характеристики тарифа хостинга
  4. 4. «Безлимитный» хостинг: что заканчивается раньше дискового пространства
  5. 5. Домен подключён, а сайт не открывается: DNS без мистики
  6. 6. HTTPS включён, а браузер всё равно ругается: SSL без паники
  7. 7. Письма с сайта пропадают: SMTP, SPF, DKIM и DMARC на пальцах
  8. 8. Современный PHP-хостинг: зачем Laravel и Symfony нужны SSH, Composer, Node.js, cron и отдельная public-директория
  9. 9. Деплой без FTP на виртуальном хостинге: Git, Composer и безопасный откат
  10. 10. Ошибка 500 после загрузки сайта: где искать причину до обращения в поддержку
  11. 11. Сайт тормозит на хостинге: виноват тариф, код, база или внешний сервис
  12. 12. Бэкап есть, восстановить нельзя: проверяем резервные копии до аварии
  13. 13. Переезд на другой хостинг без потери сайта, писем и заказов
  14. 14. Когда виртуальный хостинг стал тесен: пора на VPS или ещё можно остаться
  15. 15. Коды подтверждения не приходят на почту: лимит отправки писем на хостинге
  16. 16. Старый сайт на PHP 5.4 после переезда на MySQL 8: «Got packets out of order» и как его оживить
  17. 17. Переносим готовый Laravel-проект на хостинг: почему не по FTP и что сделать после загрузки

Laravel Переезд сайта Деплой

Поделиться: Telegram ВКонтакте

Новые статьи и разборы — в канале Siteko: Telegram MAX

Подписчикам: +30 дней к пробному хостингу, клиентам: +150 ₽ на баланс

Пошаговые инструкции по нашим услугам — в базе знаний

Ещё по теме

504 без видимой причины: один медленный запрос занимает общий пул PHP-процессов и кладёт соседние сайты. Что мы поменяли и что проверить у себя.

Хостинг 5 мин чтения

PHP2Shell: как мы закрывали уязвимость PHP на хостинге

Две критические уязвимости PHP, CVE-2026-109618 и CVE-2026-109619: какие версии затронуты, как закрыть дыру на хостинге и VDS и почему phpinfo может показывать старую версию.

Хостинг 9 мин чтения

Корпоративная почта: где её держать и сколько ящиков нужно на самом деле

Где держать корпоративную почту (на хостинге вместе с сайтом или в почтовом сервисе), сколько ящиков нужно на самом деле, кому принадлежат ящики сотрудников и при чём тут новый закон о цифровых почтовых ящиках.

«Почта на своём домене», часть 2