Cách cài Ghost CMS production trên shared hosting cPanel: Application Root, Passenger và domain

Hướng dẫn cài Ghost CMS production trên shared hosting cPanel có Node.js và Passenger; chọn Application Root linh hoạt, triển khai trong thư mục domain riêng, cấu hình Ghost-CLI, database, content path và permalink.

NHT
· 14 phút đọc
Minh họa cài Ghost CMS production trên shared hosting cPanel với Node.js, Passenger và MariaDB

Có thể cài Ghost CMS production trên shared hosting cPanel hay không? Có, nếu nhà cung cấp hỗ trợ Node.js Application, Phusion Passenger và cho phép ứng dụng Node.js chạy ổn định qua một port nội bộ. Tuy nhiên, cần nói chính xác rằng đây là một cấu hình tương thích thực tế, không phải stack production được Ghost hỗ trợ chính thức như Ubuntu, NGINX, Node.js LTS và MySQL 8.

Bài viết này trình bày cách cài Ghost CMS production trên shared hosting cPanel có Node.js, CloudLinux và Passenger. Điểm được làm rõ trong phiên bản này là Application Root không bị cố định ở /home/USERNAME/ghost-app. Người dùng có thể đặt ứng dụng trong thư mục riêng của domain, chẳng hạn /home/techntn2/domain.com/ghost-app, miễn là cấu hình cPanel, Passenger, Ghost URL và filesystem path nhất quán.

Quy trình dưới đây dựa trên case study đã triển khai Ghost 6.57.1 trên shared hosting cPanel Vietnix với LiteSpeed, CloudLinux 9.8, Node.js 22.23.2, Passenger và MariaDB 10.11.18. Nếu bạn đang chuyển website Laravel cũ sang Ghost, hãy xem thêm phần hướng dẫn migrate và restore dữ liệu Laravel sang Ghost CMS.

Ghost trên shared hosting cPanel có phù hợp cho production không?

Cần phân biệt hai khái niệm: có thể chạyđược Ghost hỗ trợ chính thức. Ghost CLI được thiết kế cho stack Ubuntu, NGINX, Node.js LTS và MySQL; công cụ này cũng có các bước tự động cho NGINX, systemd, SSL và Linux user trong môi trường chuẩn [1]. Shared hosting cPanel thường thay các thành phần đó bằng LiteSpeed hoặc Apache, Passenger và giới hạn CloudLinux.

Trong case study này, Ghost vẫn hoạt động trên MariaDB 10.11.18. Tuy nhiên, tài liệu Ghost xác nhận MySQL 8 là database duy nhất được hỗ trợ trong production [2]. Vì vậy, MariaDB nên được mô tả là khả năng tương thích đã quan sát trong một môi trường cụ thể, không phải cam kết tương thích cho mọi phiên bản Ghost.

Thành phầnCase studyĐiểm cần lưu ý
HostingShared hosting cPanel, CloudLinux 9.8Có giới hạn process, worker, memory và quyền hệ thống
Web serverLiteSpeed/PassengerPassenger chuyển request public vào Node.js
Node.js22.23.2Phải phù hợp với engine của phiên bản Ghost đang cài
Ghost6.57.1Cài production, bỏ các bước yêu cầu root
DatabaseMariaDB 10.11.18Chạy được trong case study nhưng ngoài database production được Ghost hỗ trợ chính thức
Process managerPassenger qua cPanelKhông dùng systemd như trên VPS

Phân biệt public URL, Application Root và Ghost directory

Đây là phần dễ gây nhầm nhất khi triển khai Ghost trên cPanel. Một website có ít nhất bốn khái niệm khác nhau:

Khái niệmVí dụÝ nghĩa
Public URLhttps://domain.comĐịa chỉ người đọc và Google truy cập
Filesystem Application Root/home/techntn2/domain.com/ghost-appThư mục chứa startup wrapper, config và Ghost install
Application Path trong cPaneldomain.com/ghost-appĐường dẫn source tương đối với home directory cPanel
Ghost-CLI install directory/home/techntn2/domain.com/ghost-app/currentThư mục được truyền cho ghost install --dir current
Ghost content path/home/techntn2/domain.com/ghost-app/current/contentNơi lưu ảnh, theme, routes và dữ liệu content
Internal listen address127.0.0.1:2368Địa chỉ Node.js lắng nghe phía sau Passenger, không phải port public

cPanel Application Manager tách Deployment Domain, Base Application URL và Application Path. Application Path được nhập tương đối với home directory, còn Passenger đóng vai trò process manager và reverse proxy [3]. Vì vậy, việc dùng thư mục domain.com/ghost-app không có nghĩa website sẽ tự chạy ở URL /ghost-app/; URL public vẫn do mapping của cPanel và giá trị url trong Ghost quyết định.

Layout khuyến nghị: Application Root riêng, Ghost nằm trong thư mục con

Để tránh trộn file do cPanel tạo với cấu trúc core của Ghost-CLI, nên dùng một Application Root làm lớp wrapper và cài Ghost vào thư mục con current:

/home/techntn2/domain.com/ghost-app/       # Application Root của cPanel
├── app.js                                   # startup file cho Passenger
├── package.json                             # metadata Node.js tối thiểu
├── config.production.json                   # config Ghost, chmod 600
├── tmp/                                     # thư mục restart của Passenger
│   └── restart.txt                          # tạo khi cần restart thủ công
└── current/                                 # Ghost-CLI install directory
    ├── content/                             # media, theme, routes, settings
    ├── current -> versions/<version>       # version Ghost đang active
    ├── versions/                            # các version Ghost đã cài
    └── .ghost-cli                           # metadata của Ghost-CLI

Ghost-CLI có hỗ trợ cờ --dir để chạy lệnh trong một directory chỉ định và tạo cấu trúc gồm content, currentversions [1]. Trong layout trên, --dir current là đường dẫn tương đối với Application Root, không phải thư mục current nằm trực tiếp dưới home directory.

Chuẩn hóa biến cấu hình trước khi cài

Không nên sao chép nguyên xi /home/USERNAME/ghost-app vào mọi server. Hãy thay các giá trị dưới đây bằng thông tin thật của tài khoản cPanel:

export CPANEL_HOME="/home/techntn2"
export DOMAIN="domain.com"
export APP_ROOT="$CPANEL_HOME/$DOMAIN/ghost-app"
export APP_PATH="$DOMAIN/ghost-app"
export GHOST_DIR="$APP_ROOT/current"
export GHOST_URL="https://$DOMAIN"
export GHOST_PORT="2368"
export NODE_VERSION="22"
export GHOST_VERSION="6.57.1"
export GHOST_CONTENT="$GHOST_DIR/content"

printf 'APP_ROOT=%s\n' "$APP_ROOT"
printf 'APP_PATH=%s\n' "$APP_PATH"
printf 'GHOST_DIR=%s\n' "$GHOST_DIR"
printf 'GHOST_URL=%s\n' "$GHOST_URL"

Với primary domain, bạn có thể chọn một đường dẫn như /home/techntn2/ghost-app. Với addon domain domain.com, ví dụ phù hợp là /home/techntn2/domain.com/ghost-app. Với subdomain, có thể dùng một thư mục riêng như /home/techntn2/apps/blog-ghost. Điều quan trọng là Application Path, startup file, Ghost URL và content path phải cùng trỏ về một layout.

1. Kiểm tra điều kiện của hosting

Trước khi tạo application, hãy xác nhận nhà cung cấp có các mục Setup Node.js App, Application Manager hoặc tính năng tương đương. cPanel yêu cầu hosting bật Passenger và các package Node.js phù hợp; nhà cung cấp cũng có thể giới hạn phiên bản Node.js, số process, memory, worker và quyền restart [4].

Hãy kiểm tra các điểm sau:

  • Node.js version nào đang được cPanel cung cấp và có đáp ứng engine của Ghost hay không.
  • Passenger có được bật cho domain hoặc addon domain hay không.
  • Application Manager có cho chọn Deployment Domain, Base Application URL và Application Path riêng hay không.
  • Ứng dụng có được phép lắng nghe trên 127.0.0.1 và một port nội bộ hay không.
  • Nhà cung cấp hỗ trợ restart Passenger bằng nút Restart, tmp/restart.txt hay cơ chế riêng nào.
  • Giới hạn process, memory, worker và cách xem log Node.js/Passenger.

2. Tạo database và user riêng

Trong cPanel, tạo database và database user riêng cho Ghost. Không dùng lại user của Laravel, không commit password vào Git và không truyền password trực tiếp trong command line nếu shell history được lưu.

mysql -u ghost_user -p -e \
  "SELECT VERSION() AS db_version, @@version_comment AS engine;"

Nếu hosting chỉ cung cấp MariaDB, hãy ghi nhận đây là một quyết định tương thích có rủi ro. Ghost khuyến nghị MySQL 8 cho production; nếu website có membership, newsletter, traffic lớn hoặc yêu cầu uptime cao, VPS với stack được Ghost hỗ trợ sẽ an toàn hơn [2].

3. Tạo Node.js Application trong cPanel

Tạo application trước khi đăng ký trong Application Manager. Với ví dụ addon domain, cấu hình khái niệm sẽ là:

Trường cPanelGiá trị ví dụ
Deployment Domaindomain.com
Application Pathdomain.com/ghost-app
Application Root đầy đủ/home/techntn2/domain.com/ghost-app
EnvironmentProduction
Startup Fileapp.js
Base Application URL/ hoặc giá trị tương ứng giao diện hosting yêu cầu

Tên trường có thể khác giữa các nhà cung cấp. Hãy ưu tiên mô tả trong giao diện cPanel thực tế. cPanel mặc định khuyến nghị startup file có tên app.js vì Passenger tìm file này khi khởi chạy Node.js application [4].

Tạo thư mục và giữ quyền tối thiểu cần thiết:

mkdir -p "$APP_ROOT"
chmod 755 "$APP_ROOT"
cd "$APP_ROOT"
pwd

Nếu muốn đặt app dưới thư mục domain, hãy kiểm tra document root của domain. Không mặc nhiên đặt file config, database dump hoặc source nhạy cảm vào một thư mục được web server phục vụ tĩnh. Nếu application path khác document root của addon domain, HTTPS/SSL include và mapping Passenger có thể cần nhà cung cấp hosting xử lý; người dùng shared hosting không nên tự chạy lệnh rebuild Apache khi không có quyền root.

4. Xác định Node.js runtime do cPanel cung cấp

Đường dẫn virtual environment không thống nhất giữa các hosting. Vì vậy, không nên luôn dùng mẫu /home/USERNAME/nodevenv/ghost-app/22/bin/activate. Hãy lấy lệnh activate hoặc binary path từ giao diện Node.js App của cPanel.

Một ví dụ minh họa có thể là:

# Chỉ dùng sau khi đã xác nhận đường dẫn thực tế trong cPanel
source "$CPANEL_HOME/nodevenv/${DOMAIN}/22/bin/activate"
node --version
npm --version
which node
which npm

Nếu hosting không tạo virtual environment theo domain, hãy dùng đúng lệnh mà cPanel cung cấp hoặc binary Node.js tương ứng, chẳng hạn /opt/cpanel/ea-nodejs22/bin/node. Không đoán đường dẫn chỉ từ tên application.

5. Cài Ghost-CLI cục bộ

cd "$APP_ROOT"
npm install --no-save --ignore-scripts ghost-cli@1.30.1
./node_modules/.bin/ghost --version

Cài Ghost-CLI cục bộ giúp application dùng đúng CLI trong environment của cPanel. ghost install local là development mode, thường dùng SQLite và local process manager; không dùng lệnh đó nếu mục tiêu là production [1].

6. Xử lý giới hạn worker của CloudLinux

Shared hosting có thể hiển thị nhiều CPU nhưng tài khoản vẫn bị giới hạn process hoặc thread. Trong case study, pnpm từng gặp:

ERR_WORKER_INIT_FAILED EAGAIN

Trước tiên, giảm concurrency nếu hosting có Corepack/pnpm:

COREPACK=/opt/alt/alt-nodejs22/root/usr/bin/corepack
"$COREPACK" pnpm config set child-concurrency 1
"$COREPACK" pnpm config set network-concurrency 1
"$COREPACK" pnpm config get child-concurrency
"$COREPACK" pnpm config get network-concurrency

Nếu vẫn lỗi, kiểm tra mức parallelism:

command -v taskset
nproc
node -e "console.log({availableParallelism:require('os').availableParallelism(), cpuCount:require('os').cpus().length})"
taskset -c 0 node -e "console.log({availableParallelism:require('os').availableParallelism(), cpuCount:require('os').cpus().length})"

taskset và các biến concurrency chỉ là workaround phụ thuộc môi trường. Không nên biến chúng thành yêu cầu bắt buộc của mọi shared hosting.

7. Cài Ghost production vào thư mục con của Application Root

Với Layout A, chạy installer từ APP_ROOT và truyền --dir current. Nếu APP_ROOT=/home/techntn2/domain.com/ghost-app, Ghost sẽ được cài dưới /home/techntn2/domain.com/ghost-app/current.

cd "$APP_ROOT"

(
  OLD_MODE=$(stat -c '%a' "$CPANEL_HOME")
  chmod 755 "$CPANEL_HOME"
  trap 'chmod "$OLD_MODE" "$CPANEL_HOME"; echo "Home permission restored to $OLD_MODE"' EXIT

  PNPM_CHILD_CONCURRENCY=1 \
  PNPM_NETWORK_CONCURRENCY=1 \
  taskset -c 0 ./node_modules/.bin/ghost install "$GHOST_VERSION" \
    --dir current \
    --url "$GHOST_URL" \
    --port "$GHOST_PORT" \
    --ip 127.0.0.1 \
    --db mysql \
    --dbhost localhost \
    --dbuser ghost_user \
    --dbname ghost_database \
    --setup-linux-user=false \
    --no-stack \
    --no-setup \
    --no-start \
    --no-enable \
    --no-check-mem
)

Không truyền password database trực tiếp vào lệnh. Khi installer hỏi, nhập password trong Terminal. Nếu cài đặt dừng giữa chừng, đổi tên thư mục partial install thành backup rồi chạy lại sau khi xác định nguyên nhân; không xóa database hoặc thư mục content một cách vội vàng.

8. Tạo config production cho Passenger

Tạo config.production.jsonAPP_ROOT, không đặt nhầm trong APP_ROOT/current nếu bạn đang dùng Layout A:

cat > "$APP_ROOT/config.production.json" <<JSON
{
  "url": "https://domain.com",
  "server": {
    "host": "127.0.0.1",
    "port": 2368
  },
  "database": {
    "client": "mysql",
    "connection": {
      "host": "localhost",
      "user": "ghost_user",
      "password": "NHAP_TRUC_TIEP",
      "database": "ghost_database"
    }
  },
  "paths": {
    "contentPath": "/home/techntn2/domain.com/ghost-app/current/content"
  },
  "logging": {
    "level": "info",
    "transports": ["stdout"]
  }
}
JSON
chmod 600 "$APP_ROOT/config.production.json"
test -d "$GHOST_CONTENT" && echo 'content directory exists'
stat -c '%A %a %n' "$APP_ROOT/config.production.json"

Trong bài hướng dẫn thực tế, hãy thay /home/techntn2, domain.com và password placeholder bằng giá trị của chính bạn; không copy thông tin database thật lên bài viết.

Nếu package.json do cPanel tạo thiếu metadata, bổ sung tối thiểu:

node <<'NODE'
const fs = require('fs');
const file = 'package.json';
const pkg = JSON.parse(fs.readFileSync(file, 'utf8'));
pkg.name = pkg.name || 'ghost-site';
pkg.version = '6.57.1';
pkg.private = true;
fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + '\n');
NODE

9. Tạo startup wrapper cho Passenger

Passenger sẽ khởi chạy app.js tại Application Root:

cat > "$APP_ROOT/app.js" <<'JS'
'use strict';

process.env.NODE_ENV = 'production';
process.chdir(__dirname);

require('./current/current');
JS

cd "$APP_ROOT"
node --check app.js
node --check current/current/index.js
node -e "console.log(require.resolve('./current/current'))"

require('./current/current') là đúng cho Layout A vì current/current là symlink version đang active bên trong Ghost-CLI install directory. Nếu bạn chọn cài Ghost trực tiếp tại Application Root với --dir ., phải dùng một layout khác hoàn toàn; không trộn hai cách này.

10. Restart Passenger và kiểm tra log

Sau khi tạo hoặc sửa file, dùng nút Restart trong cPanel nếu giao diện có nút này. Một số hosting cho phép tạo restart marker:

mkdir -p "$APP_ROOT/tmp"
touch "$APP_ROOT/tmp/restart.txt"

cPanel ghi log Node.js trong thư mục log của application, nhưng vị trí cụ thể có thể phụ thuộc nhà cung cấp. Nếu trang trả maintenance hoặc lỗi 503, kiểm tra log Passenger/Node.js trước khi sửa database. Không tự chạy các lệnh rebuild Apache hoặc restart service hệ thống nếu bạn không có quyền root; hãy yêu cầu nhà cung cấp hỗ trợ.

Kiểm tra public URL và Admin:

curl -I "$GHOST_URL/"
curl -I "$GHOST_URL/ghost/"
curl -sS "$GHOST_URL/ghost/api/admin/site/" | head

11. Xử lý migration lock khi Ghost khởi động lần đầu

Nếu Ghost đã tạo bảng nhưng dừng giữa migration, lần khởi động sau có thể báo MigrationsAreLockedError. Kiểm tra lock trước:

SELECT * FROM migrations_lock;
SELECT COUNT(*) AS migration_count FROM migrations;

Chỉ mở lock khi xác định locked = 1released_at IS NULL:

UPDATE migrations_lock
SET locked = 0,
    released_at = NOW()
WHERE lock_key = 'km01'
  AND locked = 1;

Không xóa bảng migrations, không xóa database và không rollback tùy tiện. Sau khi xử lý lock mồ côi, restart một process Ghost duy nhất rồi kiểm tra lại migration.

Nếu blog dùng URL dạng https://domain.com/slug-bai-viet/, cấu hình Ghost giữ permalink root-level. Trong Settings → Labs → Routes, upload routes.yaml:

routes:

collections:
  /:
    permalink: /{slug}/
    template: index

taxonomies:
  tag: /category/{slug}/
  author: /author/{slug}/

Tài liệu Ghost cho biết routes file nằm trong content/settings/routes.yaml và được upload từ Admin [5]. Kiểm tra cả URL hợp lệ và URL không tồn tại:

curl -I "$GHOST_URL/bai-viet-hop-le/"
curl -I "$GHOST_URL/url-khong-ton-tai/"

URL không tồn tại phải trả HTTP 404 native. Không redirect mọi URL lỗi về homepage, vì cách này có thể tạo soft-404 và khiến công cụ tìm kiếm index các URL không có nội dung.

13. Checklist nghiệm thu

Hạng mụcĐiều kiện đạt
Application PathĐúng path tương đối với home và trỏ tới Application Root đã chọn
Deployment DomainĐúng primary domain, addon domain hoặc subdomain
Node.jsĐúng engine requirement và đúng runtime cPanel cung cấp
DatabaseKết nối được bằng user riêng, password không lộ trong shell history
Configconfig.production.json có mode 600
Content pathTrỏ tuyệt đối tới đúng APP_ROOT/current/content trong Layout A
PassengerRestart xong trả HTML của Ghost, không phải 503/maintenance
Admin/ghost/ mở được và đăng nhập được
HTTPSDomain addon/primary đều có SSL mapping đúng
PermalinkDùng một chuẩn trailing slash duy nhất
URL lỗiTrả HTTP 404 native
BackupSQL dump, content storage và backup độc lập vẫn còn

Câu hỏi thường gặp

Có bắt buộc phải dùng /home/USERNAME/ghost-app không?

Không. Đó chỉ là ví dụ. Bạn có thể dùng /home/techntn2/domain.com/ghost-app, /home/techntn2/apps/mikka-ghost hoặc thư mục khác mà tài khoản cPanel có quyền truy cập. Hãy cập nhật đồng nhất Application Path, APP_ROOT, contentPath và startup wrapper.

Đặt Ghost trong thư mục domain có làm URL thành /ghost-app/ không?

Không tự động. Tên thư mục filesystem và URL public là hai lớp khác nhau. URL phụ thuộc mapping của cPanel/Passenger và giá trị url trong Ghost. Nếu muốn chạy root domain, hãy đăng ký Base Application URL phù hợp và đặt Ghost URL là https://domain.com.

Nên cài Ghost trực tiếp tại Application Root hay trong current?

Với shared hosting cPanel, nên dùng Application Root làm wrapper và cài Ghost trong thư mục con current. Cách này tách file Passenger/cPanel khỏi cấu trúc core của Ghost-CLI và phù hợp với quy trình nâng cấp, rollback. Nếu chọn --dir ., bạn phải dùng contentPath và entrypoint của layout trực tiếp, không trộn với Layout A.

MariaDB có chạy được Ghost không?

MariaDB có thể hoạt động trong một số môi trường như case study này, nhưng Ghost chính thức hỗ trợ MySQL 8 cho production [2]. Nếu website quan trọng, hãy ưu tiên stack được Ghost hỗ trợ.

Vì sao pnpm báo ERR_WORKER_INIT_FAILED EAGAIN?

Đó thường là dấu hiệu tài khoản CloudLinux không được tạo thêm worker hoặc thread. Hãy giảm concurrency và chỉ dùng CPU affinity nếu hosting có sẵn utility này. Đây là workaround theo môi trường, không phải bước bắt buộc của Ghost.

Có nên xóa database hoặc backup sau khi Ghost chạy không?

Không. Giữ SQL dump, thư mục storage và bản backup độc lập cho đến khi đã kiểm tra đủ nội dung, media, permalink, canonical, sitemap và 404 trong một khoảng thời gian theo dõi.

Kết luận

Cài Ghost CMS production trên shared hosting cPanel có Node.js là khả thi, nhưng cần hiểu rõ giới hạn của Passenger, CloudLinux, database và quyền hệ thống. Vấn đề quan trọng nhất không phải tên thư mục ghost-app, mà là bảo đảm bốn lớp cấu hình cùng nhất quán: domain public, Application Path cPanel, Ghost-CLI directory và contentPath.

Với addon domain domain.com, layout an toàn và dễ bảo trì là /home/techntn2/domain.com/ghost-app làm Application Root, Ghost-CLI cài vào thư mục con current, Passenger khởi chạy app.js và Ghost dùng /home/techntn2/domain.com/ghost-app/current/content. Nếu website đang chuyển từ Laravel, hãy tách riêng migration và restore để kiểm tra từng giai đoạn mà không ảnh hưởng dữ liệu nguồn.

Tài liệu tham khảo

  1. Ghost CLI — Ghost Developer Docs.
  2. What Databases Are Supported In Production? — Ghost Developer Docs.
  3. Application Manager — cPanel & WHM Documentation.
  4. How to Install a Node.js Application — cPanel Documentation.
  5. Routing — Ghost Developer Docs.