Как перенести Ghost CMS на vps с панелью FASTPANEL
Как перенести Ghost CMS на vps с панелью FASTPANEL

Появилась необходимость перенести уже установленный ghost cms на другой vps, на котором уже установлена FASTPANEL. Вдохновившись статьей "Установка Ghost на сервер под управлением ispmanager", я подумал, что это будет просто. На деле же оказалось несколько сложнее.

Конечно можно установить в docker контейнер и не париться с панелью. Но я решил остановиться на варианте без контейнеризации. В этой статье у меня был 1 чистый сервер на котором был установлен ghost cms без докера. Второй сервер уже в работе с установленной FASTPANEL.

Начнем пожалуй с бэкапов.

Делаем бэкапы

Здесь стоит сразу сделать важную оговорку. Даже на этом этапе может возникнуть ошибка.

ghost backup
Love open source? We’re hiring JavaScript Engineers to work on Ghost full-time.
https://careers.ghost.org
+ sudo systemctl is-active ghost_site-com
+ sudo mkdir -p /var/www/site.com/backup
? Enter your Ghost administrator email address site@gmail.com
? Enter your Ghost administrator password [hidden]
✖ Backing up site
An error occurred.
Message: 'Response code 403 (Forbidden)'

Это был баг который зарепортили на гитхабе и исправили в версии ghost-cli 1.28.3.

npm install -g ghost-cli@latest

После обновления консольной утилиты, все должно пройти нормально. Где найти токен который спросят после команды - Enter your Ghost Staff access token. Его можно найти в админке, нажать на аватарку своего профиля внизу, пункт Your Profile, в самом низу будет набор букв и символов.

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

А мы едем дальше и пробуем установить на не "чистый" сервер.

Установка движка ghost на vps с уже установленной панелью FASTPANEL

Имеем Ubuntu 24.04 и установленную панель FASTPANEL, которая имеет как свои плюсы так и минусы. Однако для меня плюсов больше, поэтому я все же решил заморочиться.

Я уже пробовал устанавливать и писал об этом в статье Ghost CMS: движок c открытым исходным кодом не для бедных. Однако получал ошибку "Message: Could not communicate with Ghost".

В этот раз капнул глубже и установка прошла успешно.

Я пропущу шаги установки самой панели, это делается одной командой.

wget https://repo.fastpanel.direct/install\\_fastpanel.sh -O - | bash -

Также предполагается, что запись А уже привязана к ip адресу нового сервера.

Но сначала создадим сайт в FASTPANEL. Create site - Blank(Static) - Домен.

На этом этапе не спешите нажимать кнопку create site. Здесь нужно создать юзера, backend, базу данных и опционально можно настроить автоматический бэкап данных. Юзера и базу данных я оставил ту, которую мне предложила система и сохранил эти данные в отдельный файл. А вот Backend нужно выбрать - Reverse Proxy - add - и добавить host - http://127.0.0.1:2368. Поскольку Ghost CMS работает на порту 2368 добавляем его.

Далее Save - Create Site. Для начала нужно внести пару правок в nginx и создать ssl сертификат. Создать сертификат в панели очень просто. SSL sertificates - New Certificate - вводим домен. Готово, панель сама создаст и пропишет пути к сертификатам. Заходим в пункт Manual Settings и немного правим конфиг.

Вместо стандартного:

location / {        proxy_pass http://123.ru3;        include /etc/nginx/proxy_params;    }

Меняем домен на айпи с портом и дописываем заголовки.

location / {    proxy_pass http://127.0.0.1:2368;    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_buffering off;
}

Пока что с фастпанелью закончили. Переходим к терминалу. Подключаемся по ssh любым удобным способом. У меня Void Linux поэтому у меня это стандартный терминал. Для Windows - terminus, говорят неплохая вещь.

Поскольку у меня уже был сервак с пользователем с правами sudo, я пропущу несколько базовых команд. Однако все равно нужно заглядывать в офф документацию и смотреть какие требования для конкретной версии движка.

Видно, что nodejs нужен именно 22 версии. Поэтому устанавливаем.

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=22 # Use a supported version
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node\\_$NODE\_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt-get update
sudo apt-get install nodejs -y

Далее устанавливаем ghost-cli.

sudo npm install ghost-cli@latest -g

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

Еще одно важное замечания. Имя пользователя в системе Ubuntu, по которому я логинюсь по ssh и имя которое было создано в FASTPANEL разные. Поэтому в моем случае, установка не проходила корректно, если бы я не поторопился, то возможно удалось бы установить ghost меньшими костылями. Но я рассказываю как было.

Сайты в FASTPANEL создаются по пути /var/www/user/data/www/site.com

cd /var/www/user/data/www/site.com
# Права для Ghost
chown -R имя_по_ssh:имя_по_ssh /var/www/user/data/www/site.com
chmod 775 /var/www/user/data/www/site.com

Пробуем установить:

ghost install \  --url https://site.com \  --port 2368 \  --db mysql \  --dbhost localhost \  --dbuser ghost_user \  --dbpass ТВОЙ_ПАРОЛЬ_ИЗ_FASTPANEL \  --dbname ghost_name \  --no-setup-nginx \  --no-setup-ssl \  --no-prompt \  --process local

Флаги --no-setup-nginx и --no-setup-ssl важны — они говорят Ghost не трогать nginx, потому что мы уже настроили его через FastPanel. Флаг --process local запустит Ghost без systemd. После этой команды получил ошибку:

1) SystemError
Message: The directory /var/www/user/ is not readable by other users on the system.

Так получается, потому что FastPanel создаёт директории с ограниченными правами. Нужно исправить права на всю цепочку:

sudo chmod 755 /var/www/user
sudo chmod 755 /var/www/user/data
sudo chmod 755 /var/www/user/data/www
sudo chmod 775 /var/www/user/data/www/site.com

После данных манипуляций установка прошла успешно.Однако в таком случае я не могу проводить никаких операций с файлами через FastPanel.

FastPanel error not writable for user
FastPanel error not writable for user

Проблема: FastPanel не может писать файлыGhost установка сменила владельца на имя_по_ssh , а FastPanel работает от имя_по_fastpanel . Поэтому костыль:

sudo chown -R имя_по_фастпанель:имя_по_фастпанель /var/www/user/data/www/site.com

Теперь можно редактировать и проводить другие операции с файлами через фастпанель. Но в таком случае если нужно будет переагрузить движок командой ghost restart , вылезет очередная ошибка...

✖ Restarting Ghost
A CliError occurred.
Message: An unexpected error occurred while stopping Ghost.

Снова меняем права:

sudo chown -R имя_по_ssh:имя_по_ssh /var/www/user/data/www/site.com

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

Перенос данных из бэкапа

Перенос простой. Нужно разархивировать zip папку с данными в папку content. Без разницы как это делать либо по scp через ssh, либо через FastPanel - Files. Довольно удобный GUI файловый менеджер.Далее переходим а админку, шестеренка внизу - Import/Export - Universal Import.

В архиве папка должна содержать файл content-from-73487.json. Кидаем его в импорт. Пару-тройку минут приведение подумает и подгрузит посты.Дальше в Labs - Redirects и Routes по аналогии импортируем, только уже из папки settings. Напоследок осталось импортировать пользователей если таковые были. В админке шестеренка теперь уже которая сверху.

Стало быть все, на этом перенос должен быть завершен и все должно работать. Так то оно так, однако я также перенес и старый конфиг, в котором указан mail transport - SMTP. Он не завелся, позже выяснил почему.

Если в конфиге config.production.json указать Direct как по умолчанию, то все рассылки перестанут работать. Как временное решение можно использовать, но лучше все же настроить сервис Mailgun.

Настройка Mailgun

Поскольку я не менял домен, а только ip, то в моем понимании он должен был заработать как говорится из коробки. Но что то пошло не так.

По прошествию времени, я уже конечно знаю в чем причина. Но вот когда настраивал сервис для нового сервера, то достаточно сложно понять, что не так когда ошибка There was a problem on the server. Или например Failed to send email. Please check your site configuration and try again.

Это мне напомнило Ералаш...

Файл config.production.json привел к следующему виду:

"mail": {    "transport": "SMTP",    "options": {      "host": "smtp.eu.mailgun.org",      "port": 465,      "secure": true,      "auth": {        "user": "noreply@site.com",        "pass": "pass"            }        }    },

Решил пингануть порты mailgun

nc -zv smtp.eu.mailgun.org 587
nc -zv smtp.eu.mailgun.org 465

Ответ был что-то вроде:

DNS fwd/rev mismatch: smtp.eu.mailgun.org != 

Первая проблема заключалась, в том что порты 587 и 465 были заблокированы хостером. Позже я узнал что это типичная защита от спама на VPS.

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

Порты открыты, однако теперь ошибка сменилась на 535 Authentication failed. Первое, что приходит в голову, это ошибка в пароле либо логине. Проверил, сменил, добавил новое подключение, даже поменял API подключения. Результат нулевой.

Ушел гуглить дальше и читать форум поддержки Ghost cms. Вопрос про сервис рассылок задают достаточно часто, но конкретного решения не было. Только общие рекомендации.

Вообщем я решил написать в техподдержку Mailgun и выяснить почему же сервис не может запуститься на моем сервере. Написал все достаточно подробно, запихну запрос под спойлер...

Запрос в mailgun

Скрытый текст

Hello Mailgun Support,

I'm experiencing persistent SMTP authentication failures (535 Authentication failed) for my domain site.com (EU region).

Issue Details:

All SMTP credentials return "535 Authentication failed"

Domain is fully verified (DKIM, SPF, MX records all green)

Account is on Free plan, in good standing

No 2FA or additional security restrictions enabled

What I've tried:Created multiple SMTP users: postmaster@, noreply@, test@, den@

Reset passwords multiple times for each user

Tested with both short auto-generated passwords and long hash passwords

Tried both smtp.eu.mailgun.org and smtp.mailgun.org

Tested on ports 587 and 465

Verified TLS connection works (TLSv1.3 handshake successful)

Test command used:

swaks --to user@gmail.com \

--from user@site.com \

--server smtp.eu.mailgun.org \

--port 587 \

--auth LOGIN \

--auth-user "user@site.com" \

--auth-password "[password]" \

--tls

Response:

<~ 334 UGFzc3dvcmQ6

~> [base64 encoded password]

<~* 535 Authentication failed

The connection establishes successfully, TLS works, but authentication always fails regardless of which SMTP credential I use.

Could you please check if there's any restriction on my account or domain that's preventing SMTP authentication?

Account email: user@gmail.com

Domain: site.com

Region: EU

Thank you for your help!

Мой запрос обработали за 2 дня и вернулись с ответом.

Ответ техподдержки Mailgun

Скрытый текст

Hello Denys,Thank you for contacting Sinch Mailgun Support.Our platform released an update in April 2024 which aimed at improving our IP Allowlist functionality for SMTP users. The IP Allowlist functionality now also covers SMTP connections, giving you the same enhanced security for both methods of sending. We sent out a couple notifications to the account administrators regarding this change. More information can be found in our help site article: https://help.mailgun.com/hc/en-us/articles/360012244474-IP-AllowlistMoving forward, this means if your connecting client IP is not listed in your Mailgun allowlist, it will permanently fail and generate an "Authentication Failed error message. To continue sending from the SMTP client, you'd either need to add the client's IP to your Mailgun IP Allowlist, or remove all other entries from the list so you are no longer making use of the allowlist. The steps for how to add an IP to the allowlist can be found below:1. First, log in to the Mailgun Control Panel (if you have not already done so).
2. Then, at the top-right corner of the page, click the Profile drop-down menu to expand its list of options.
3. Next, click the IP Access Management option. Alternatively, you can use this direct link (https://app.mailgun.com/settings/ip-access-management).
4. Click the Add IP addresses button.
5. Finally, enter the IP address or CIDR range as well as a description before clicking the Save button.Please let us know if you have any additional questions or concerns!
We hope this information is helpful. Please let us know if we can further assist.Regards,
John T | Sinch Mailgun Support~ ~ ~
P.S. Have questions? Experiencing an issue? Answers and solutions can be found quickly within our Help Center!
~ ~ ~

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

Благо поддержка выручила, после добавления айпи адреса в белый список, все заработало.Осталось настроить редиректы и настроить кэширование в nginx.

Настройка редиректов, заголовков и кеширования в nginx

Редактировать конфиг nginx можно из панели, раздел Manual Settings.Please do not edit configuration files manually. You may do that in your control panel.

Полный конфиг не буду выкладывать здесь, выделю на мой взгляд главное.Редирект HTTP → HTTPS и с www на без www.

server {    listen ваш_ip:80;    server_name site.com www.site.com;    return 301 https://site.com$request\_uri;
}
server {    listen ваш_ip:443 ssl;    http2 on;    server_name www.site.com;        ssl_certificate "/var/www/httpd-cert/site.com_2025-12-17-21-05_09.crt";    ssl_certificate_key "/var/www/httpd-cert/site.com_2025-12-17-21-05_09.key";        return 301 https://site.com$request\_uri;
}

Добавить заголовки против межсайтового скриптинга в секцию location / {:

add_header X-Content-Type-Options "nosniff" always;    add_header X-Frame-Options "SAMEORIGIN" always;    add_header Referrer-Policy "strict-origin-when-cross-origin" always;    add_header Permissions-Policy "accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()" always;    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;    add_header Content-Security-Policy "upgrade-insecure-requests" always;

Про кэширование nginx есть отличная статья Как сократить время ответа сервера? WordPress, Joomla, DLE — оптимизация для информационных сайтов на CMS и… дорвеев. Она немного устарела, однако все еще можно подчерпнуть полезные знания.

proxy_cache_path /var/www/cache levels=1:2 keys_zone=ghostcache:10m max_size=120m inactive=7d;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_lock on;
location / {        ...                # Proxy Cache        proxy_cache ghostcache;        proxy_cache_key $request_uri$usercookie;        proxy_cache_bypass $skip_cache;        proxy_no_cache $skip_cache;        proxy_cache_valid 200 7d;        proxy_cache_valid 404 1m;                # Игнорируем заголовки кеша от Ghost (но не Set-Cookie!)        proxy_ignore_headers Cache-Control Expires;                # Cache status header для отладки        add_header X-Cache $upstream_cache_status always;                # Browser cache для HTML        add_header Cache-Control "public, max-age=3600, stale-while-revalidate=86400" always;

Заворачиваем

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

Возможно кто-то бы прошел этот путь за пару часов, но у меня ушло на это 3 дня с момента переноса. Скорее всего еще есть ошибки, но они уже не настолько значительны.

Ghost CMS если сравнивать с операционной системой, то он похож на Ubuntu. Вначале было много ошибок, но когда настроишь его - работает отлично.