Cách cài Ghost CMS production trên shared hosting cPanel có Node.js
Có thể cài Ghost CMS trên shared hosting cPanel hay không? Có, nếu nhà cung cấp hỗ trợ Node.js Application, Passenger và cho phép ứng dụng Node.js chạy ổn định trên một port nội bộ. Tuy nhiên, đây là cấu hình tương thích thực tế chứ không phải stack production được Ghost hỗ trợ chính thức như Ubuntu, NGINX, Node.js LTS và MySQL 8.
Ghost CMS thường được triển khai trên VPS, nhưng một số shared hosting có cPanel, CloudLinux và Node.js Application vẫn có thể chạy Ghost production. Bài viết này ghi lại quy trình thực tế đã triển khai Ghost 6.57.1 trên shared hosting cPanel Vietnix với LiteSpeed, Passenger, Node.js 22.23.2 và MariaDB 10.11.18.
Phạm vi của bài chỉ tập trung vào cài đặt và vận hành Ghost production. Nếu bạn đang chuyển một website Laravel cũ sang Ghost, hãy xem tiếp Phần 2: Cách 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 rõ hai khái niệm: có thể chạy và được Ghost hỗ trợ chính thức. Tài liệu Ghost khuyến nghị production trên Ubuntu, NGINX, Node.js LTS và MySQL 8 1. Vì vậy, shared hosting dùng LiteSpeed, Passenger hoặc MariaDB không nên được mô tả là cấu hình chính thức của Ghost.
Trong case study này, Ghost 6.57.1 vẫn hoạt động ổn định trên CloudLinux và MariaDB 10.11.18. Tuy nhiên, nhà vận hành phải tự xử lý giới hạn worker, permission, process manager và cách restart ứng dụng. Nếu website có traffic lớn, membership trả phí, newsletter quan trọng hoặc yêu cầu uptime cao, VPS với stack được Ghost hỗ trợ sẽ an toàn hơn.
| Thành phần | Cấu hình thực tế | Lưu ý |
|---|---|---|
| Hosting | Shared hosting cPanel, CloudLinux 9.8 | Có giới hạn process và worker |
| Web server | LiteSpeed | Passenger chuyển request vào Node.js |
| Node.js | 22.23.2 | Đáp ứng engine của Ghost 6.57.1 |
| Ghost | 6.57.1 | Cài ở production mode |
| Database | MariaDB 10.11.18 | Chạy được trong case study nhưng không phải database production được Ghost hỗ trợ chính thức |
| Process manager | Passenger qua cPanel | Không dùng systemd như trên VPS |
| Domain | Domain chính, HTTPS | Có thể chạy root domain trực tiếp |
1. Kiểm tra điều kiện trước khi cài
Trước khi tạo ứng dụng, hãy xác nhận nhà cung cấp có Setup Node.js App, Application Manager hoặc tính năng tương đương. Bạn cần biết phiên bản Node.js, đường dẫn virtual environment, database client, giới hạn process và cách restart Passenger.
Sau khi cPanel tạo Node.js Application, activate virtual environment trong Terminal:
source /home/USERNAME/nodevenv/ghost-app/22/bin/activate
node --version
npm --version
which node
which npm
Ghost 6.57.1 yêu cầu Node.js phù hợp với engine của phiên bản Ghost đang cài. Không nên tự chọn Node.js quá cũ chỉ vì hosting cung cấp sẵn; hãy kiểm tra yêu cầu phiên bản trước khi bắt đầu.
Ghost-CLI phân biệt ghost install production với ghost install local development. Lệnh ghost install local thường dành cho môi trường thử nghiệm với SQLite và local process manager, không phải cách cài production 1.
2. Tạo database và user riêng trong cPanel
Trong cPanel, tạo một database và database user riêng cho Ghost. Không dùng lại user database của Laravel và không đặt password thật trong bài viết, Git repository hoặc command history.
Kiểm tra kết nối bằng cách nhập password trực tiếp khi Terminal hỏi:
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 điểm cần đánh giá rủi ro. Ghost chính thức hỗ trợ MySQL 8 cho production 2. MariaDB có thể hoạt động trong một số môi trường tương thích, nhưng không nên xem đó là cam kết tương thích cho mọi phiên bản Ghost.
3. Tạo Node.js Application trong cPanel
Mở Setup Node.js App hoặc Application Manager, sau đó tạo application mới. Application root nên đặt trong một thư mục riêng, chẳng hạn ghost-app, để không chồng lên document root hoặc source code khác.
| Trường cPanel | Giá trị đề xuất |
|---|---|
| Node.js version | Phiên bản đáp ứng engine của Ghost |
| Application mode | Production |
| Application root | ghost-app |
| Application URL | Domain chính hoặc URL đã chọn |
| Startup file | app.js |
Sau khi tạo application, cPanel thường tạo package.json, thư mục public, tmp, log và symlink node_modules tới virtual environment. Không nên bấm Run NPM Install một cách máy móc trước khi hiểu cấu trúc Ghost-CLI. Ghost-CLI sẽ tự xử lý Ghost core và dependency theo cấu trúc riêng.
Giữ nguyên application cũ và các file backup trong suốt quá trình cài. Nếu cần reset, đổi tên thư mục thay vì xóa ngay:
cd /home/USERNAME
mv ghost-app "ghost-app.before-reset-$(date +%Y%m%d-%H%M%S)"
mkdir ghost-app
chmod 755 ghost-app
Ghost-CLI có thể kiểm tra permission của home directory và yêu cầu quyền đọc tạm thời. Trên cPanel, home thường có mode 711, đây là permission phổ biến để bảo vệ file người dùng. Chỉ mở quyền trong subshell khi chạy installer rồi khôi phục ngay sau khi lệnh kết thúc.
4. Cài Ghost-CLI cục bộ
Activate virtual environment, vào application root và cài Ghost-CLI cục bộ:
source /home/USERNAME/nodevenv/ghost-app/22/bin/activate
cd /home/USERNAME/ghost-app
npm install --no-save --ignore-scripts ghost-cli@1.30.1
./node_modules/.bin/ghost --version
Cài cục bộ giúp application sử dụng đúng Ghost-CLI trong virtual environment của cPanel. Ghost-CLI hỗ trợ các tham số như --dir, --url, --port, --ip, --db, --dbhost, --dbuser, --dbname, --no-stack, --no-setup, --no-start và --no-enable 1.
Trên shared hosting, không có quyền root để tạo systemd service, Linux user hoặc NGINX virtual host. Vì vậy, ta dùng Ghost-CLI để cài core và tự cấu hình startup wrapper cho Passenger ở các bước sau.
5. Xử lý giới hạn worker của CloudLinux
Một server có thể hiển thị rất nhiều CPU nhưng tài khoản shared hosting vẫn bị giới hạn process hoặc thread. Trong case study, pnpm gặp lỗi:
ERR_WORKER_INIT_FAILED EAGAIN
Trước tiên, giảm concurrency của 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 pnpm vẫn tạo worker theo số CPU mà Node.js nhìn thấy, kiểm tra taskset và 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})"
Nếu lệnh cuối trả về availableParallelism: 1, có thể giới hạn tiến trình cài đặt trên CPU đầu tiên. Đây là workaround phụ thuộc môi trường, không phải yêu cầu chung cho mọi shared hosting.
6. Chạy Ghost installer production
Tạo thư mục current rỗng nếu Ghost-CLI yêu cầu. Sau đó chạy installer với concurrency thấp, CPU affinity và các option bỏ qua bước dành cho máy chủ root:
cd /home/USERNAME/ghost-app
(
OLD_MODE=$(stat -c '%a' /home/USERNAME)
chmod 755 /home/USERNAME
trap 'chmod "$OLD_MODE" /home/USERNAME; echo "Home permission restored to $OLD_MODE"' EXIT
PNPM_CHILD_CONCURRENCY=1 \
PNPM_NETWORK_CONCURRENCY=1 \
taskset -c 0 ./node_modules/.bin/ghost install 6.57.1 \
--dir current \
--url https://example.com \
--port 2368 \
--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 nếu shell history được lưu. Khi Ghost-CLI hỏi password, nhập trực tiếp trong Terminal. Nếu installer dừng giữa chừng, đổi tên thư mục partial install thành backup, tạo lại current rỗng rồi chạy lại.
Sau khi cài xong, Ghost-CLI thường tạo các thư mục current, versions, content và .ghost-cli. Không tùy tiện di chuyển hoặc đổi tên các thư mục này vì entry point và content path phụ thuộc vào cấu trúc đó.
7. Tạo config production cho Passenger
Vì các bước setup root đã được bỏ qua, hãy tạo config.production.json ở application root. Password dưới đây chỉ là placeholder; không điền password thật vào file bài viết hoặc Git.
{
"url": "https://example.com",
"server": {
"host": "127.0.0.1",
"port": 2368
},
"database": {
"client": "mysql",
"connection": {
"host": "localhost",
"user": "ghost_user",
"password": "NHAP_TRUC_TIEP_TRONG_TERMINAL",
"database": "ghost_database"
}
},
"paths": {
"contentPath": "/home/USERNAME/ghost-app/current/content"
},
"logging": {
"level": "info",
"transports": ["stdout"]
}
}
Dùng đường dẫn tuyệt đối cho contentPath để Passenger không tìm nhầm thư mục khi current working directory khác với application root:
test -d /home/USERNAME/ghost-app/current/content && echo 'content directory exists'
chmod 600 config.production.json
stat -c '%A %a %n' config.production.json
Nếu package.json do cPanel tạo thiếu trường version, package @tryghost/version có thể khiến Ghost lỗi khi khởi động. Bổ sung metadata 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
Tạo startup wrapper app.js cho Passenger:
'use strict';
process.env.NODE_ENV = 'production';
process.chdir(__dirname);
require('./current/current');
Kiểm tra syntax trước khi restart:
node --check app.js
node --check current/current/index.js
node -e "console.log(require.resolve('./current/current'))"
Sau đó bấm Restart trong cPanel, chờ khoảng 20–60 giây và mở /ghost/. Nếu ứng dụng vẫn trả trang maintenance, đọc log Passenger hoặc chạy Ghost trực tiếp để xem lỗi thật thay vì đoán.
8. 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 = 1 và released_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 mở lock mồ côi, restart một process Ghost duy nhất rồi kiểm tra lại trạng thái.
9. Cấu hình permalink root-level và 404 native
Nếu blog dùng URL dạng https://example.com/slug-bai-viet/, hãy cấu hình Ghost giữ nguyên root-level permalink. Trong Settings → Labs → Routes, upload file routes.yaml:
routes:
collections:
/:
permalink: /{slug}/
template: index
taxonomies:
tag: /category/{slug}/
author: /author/{slug}/
Tài liệu Ghost cho biết routes.yaml nằm trong content/settings/routes.yaml và có thể upload từ Admin; sửa file thủ công thường cần restart 3.
Sau khi upload route, kiểm tra cả URL hợp lệ và URL không tồn tại:
curl -I https://example.com/bai-viet-hop-le/
curl -I https://example.com/url-khong-ton-tai/
URL không tồn tại phải trả HTTP 404 native. Không nên redirect mọi URL lỗi về homepage vì cách đó tạo soft-404 và có thể khiến Google index các URL không có nội dung.
10. Checklist nghiệm thu Ghost production
Trước khi đưa website vào sử dụng, hãy kiểm tra application root, database, Passenger, HTTPS, admin, permalink và 404. Một website chạy được trang chủ nhưng chưa chắc đã sẵn sàng cho production.
| Hạng mục | Điều kiện đạt |
|---|---|
| Node.js | Đúng engine requirement của Ghost |
| Database | Kết nối được bằng user riêng |
| Config | config.production.json có mode 600 |
| Content path | Trỏ đến current/content bằng đường dẫn tuyệt đối |
| Passenger | Restart xong trả HTML của Ghost |
| Admin | /ghost/ mở được và đăng nhập được |
| Permalink | Dùng một chuẩn trailing slash duy nhất |
| URL lỗi | Trả HTTP 404 native |
| HTTPS | Canonical dùng HTTPS và domain chính |
| Backup | SQL/storage backup được giữ nguyên cho giai đoạn chuyển đổi |
Nếu mục tiêu tiếp theo là đưa dữ liệu Laravel cũ sang Ghost, không import SQL Laravel trực tiếp vào database Ghost. Hãy xem Phần 2: Cách migrate và restore dữ liệu Laravel sang Ghost CMS để thực hiện mapping bài viết, tags, pages, media và SEO một cách an toàn.
Câu hỏi thường gặp
Có cần VPS để chạy Ghost không?
Không phải lúc nào cũng cần. Shared hosting có Node.js Application, Passenger và khả năng chạy process có thể chạy Ghost. Tuy nhiên, môi trường này có giới hạn CloudLinux và không có quyền root để dùng systemd, NGINX hoặc Linux user như stack chuẩn trên VPS.
Có nên dùng ghost install local trên hosting không?
Không nếu mục tiêu là production. ghost install local dành cho development và thường dùng SQLite. Production nên dùng ghost install với database production phù hợp 1.
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 với MariaDB 10.11.18, 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 child-concurrency, network-concurrency và thử giới hạn CPU affinity bằng taskset nếu hosting có sẵn utility này.
Có nên xóa database và 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 không phải quy trình one-click. Phần khó nhất nằm ở việc điều chỉnh Ghost-CLI cho giới hạn worker của CloudLinux, cấu hình Passenger thay cho systemd, đặt contentPath tuyệt đối, bảo vệ database password và xác minh 404/canonical sau khi chạy.
Nếu website đang chuyển từ Laravel, hãy tách riêng phần migration thay vì trộn vào bài cài đặt. Cách này giúp người đọc dễ theo dõi, giúp bài viết có một intent SEO rõ ràng và cho phép bạn kiểm tra từng giai đoạn mà không ảnh hưởng dữ liệu nguồn.