Cài Ghost CMS trên Ubuntu 24.04 với aaPanel: Node.js 22, MariaDB, Nginx Reverse Proxy và SSL

Hướng dẫn cài đặt Ghost CMS 6 trên Ubuntu 24.04 với Node.js, MariaDB và Nginx quản lý qua aaPanel. Cấu hình domain, SSL, systemd, database và xử lý lỗi đăng nhập trên thiết bị mới.

NHT
· 25 phút đọc
Cài Ghost CMS trên Ubuntu 24.04 với aaPanel: Node.js 22, MariaDB, Nginx Reverse Proxy và SSL
Hướng dẫn triển khai Ghost CMS 6 trên Ubuntu 24.04 khi máy chủ đã cài sẵn aaPanel, Nginx và MariaDB. Ghost được chạy bằng systemd, đặt toàn bộ project trong /www/wwwroot/<domain>, còn aaPanel chịu trách nhiệm quản lý Nginx, domain và SSL.

Quy ước user trong bài viết

Trong quá trình triển khai, bạn sẽ sử dụng ba loại tài khoản chính:

UserKhi nào sử dụngNhiệm vụ chính
rootCác thao tác quản trị hệ thốngTạo user, tạo database, sửa quyền, cài package hệ thống, cấu hình sudo, Nginx và systemd
ghostCác thao tác cài đặt và quản trị GhostChạy ghost install, ghost setup, ghost ls, ghost log, ghost restart
User database example_comChỉ dùng bên trong MariaDBGhost sử dụng tài khoản này để kết nối database, không dùng để đăng nhập Linux

Nguyên tắc quan trọng

  • Không chạy ghost install, ghost setup hoặc các lệnh Ghost-CLI production bằng root.
  • Dùng root khi cần thay đổi hệ thống, quyền file, user, sudo, Nginx hoặc systemd.
  • Dùng user ghost khi thao tác với source code, cấu hình và Ghost-CLI.
  • Dùng tài khoản database example_com chỉ để kết nối MariaDB.
  • Khi cần chạy lệnh hệ thống từ user ghost, dùng sudo.
  • Khi cần chuyển từ root sang ghost, dùng:
su - ghost
  • Khi cần quay lại root, dùng:
exit

Mô hình triển khai hoàn chỉnh:

Internet
   │
   ▼
Domain
   │
   ▼
Nginx aaPanel :80 / :443
   │
   │ Reverse Proxy
   ▼
Ghost CMS :2369
   │
   ▼
MariaDB

Trong bài viết này, sử dụng domain mẫu example.com và các thông tin minh họa sau:

Domain:    example.com
Project:   /www/wwwroot/example.com
Node.js:   v22.x
Ghost:     6.59.0
Database:  example_com
DB User:   example_com
Backend:   127.0.0.1:2369
OS:        Ubuntu 24.04
Bạn cần thay example.com, example_com và các giá trị liên quan bằng domain, tên database và username thực tế của mình khi triển khai.

Tổng quan

Nếu bạn đã quen triển khai website bằng aaPanel nhưng muốn chạy Ghost CMS thay vì WordPress/PHP, cách triển khai sẽ hơi khác.

Ghost không chạy bằng PHP-FPM và cũng không nên chạy bằng PM2 trong mô hình production này. Thay vào đó, Ghost có thể chạy bằng Node.js + Ghost-CLI + systemd, trong khi Nginx của aaPanel đóng vai trò reverse proxy.

aaPanel quản lý:

Domain
Nginx
SSL
Virtual host

Ghost quản lý:

Application
Content
Config
Systemd service

MariaDB quản lý:

Database
Database user
Database permissions

1. Kiến trúc sau khi cài Ghost

Sau khi hoàn tất, hệ thống sẽ có cấu trúc gần như sau:

/www/wwwroot/example.com/
├── .ghost-cli
├── .pnpm-store/
├── config.production.json
├── config.production.json.bak
├── content/
├── current -> /www/wwwroot/example.com/versions/6.59.0
└── versions/
    └── 6.59.0/

Ghost chạy bằng:

systemd

với user:

ghost

Nginx của aaPanel sẽ proxy:

https://example.com
        ↓
http://127.0.0.1:2369

Điểm quan trọng là HTTPS được xử lý tại Nginx, không phải tại Ghost.


2. Yêu cầu trước khi cài

Server nên có sẵn:

  • Ubuntu 24.04 LTS
  • aaPanel
  • Nginx
  • MariaDB
  • Node.js 22.x
  • Domain đã trỏ về VPS

Các lệnh kiểm tra hệ thống có thể chạy bằng root:

cat /etc/os-release

Kiểm tra Node.js:

node -v
npm -v

Kiểm tra MariaDB:

mysql --version

Kiểm tra Nginx:

nginx -v

Ví dụ môi trường:

Ubuntu       24.04.4 LTS
Node.js      22.23.2
npm          10.9.8
MariaDB      10.11.10
Nginx        1.24.0
Nếu đang đăng nhập bằng user thường, chuyển sang root trước khi thực hiện các bước quản trị hệ thống:
sudo -i

3. Tạo database cho Ghost

User cần sử dụng

  • Linux user: root
  • MariaDB user: root để tạo database và tài khoản Ghost
  • Ghost database user: example_com để Ghost kết nối database sau này

Ví dụ tạo:

Database: example_com
Username: example_com
Password: YOUR_DATABASE_PASSWORD

Đăng nhập MariaDB bằng tài khoản MariaDB root. Đây là root của MariaDB, không nhất thiết giống hoàn toàn với user root của Linux:

mysql -u root -p

Tạo database:

CREATE DATABASE example_com;

Tạo user:

CREATE USER 'example_com'@'localhost'
IDENTIFIED BY 'YOUR_DATABASE_PASSWORD';

Cấp quyền:

GRANT ALL PRIVILEGES
ON example_com.*
TO 'example_com'@'localhost';

Áp dụng:

FLUSH PRIVILEGES;

Kiểm tra:

SHOW DATABASES;

Thoát:

EXIT;

Kiểm tra tài khoản database

Trước khi cài Ghost, nên test luôn bằng user database example_com:

mysql -u example_com -p -h 127.0.0.1 example_com

Nếu đăng nhập thành công:

SELECT VERSION();

và:

SHOW TABLES;

Nếu database mới tạo trả:

Empty set

thì hoàn toàn bình thường.

Thoát khỏi MariaDB:

EXIT;
Không dùng user Linux ghost để đăng nhập MariaDB nếu bạn chưa tạo một tài khoản MariaDB có tên đó. Ghost sẽ kết nối bằng database user example_com.

4. Node.js 22 trên aaPanel

User cần sử dụng

Các thao tác kiểm tra và cấu hình PATH có thể thực hiện bằng root. Tuy nhiên, cần kiểm tra lại Node.js bằng cả rootghost, vì mỗi user có thể có PATH khác nhau.

Trong trường hợp aaPanel đã có Node.js Manager và Node 22 được cài tại:

/www/server/nodejs/v22.23.2

cần đảm bảo shell sử dụng đúng Node này.

Kiểm tra bằng root:

which node
which npm

Mục tiêu:

/www/server/nodejs/v22.23.2/bin/node
/www/server/nodejs/v22.23.2/bin/npm

Thêm Node vào PATH cho phiên shell hiện tại:

export PATH="/www/server/nodejs/v22.23.2/bin:$PATH"

Để áp dụng lâu dài cho root:

echo 'export PATH="/www/server/nodejs/v22.23.2/bin:$PATH"' >> /root/.bashrc

Sau đó:

source /root/.bashrc

Kiểm tra:

node -v
npm -v

Sau khi tạo user ghost, cần cấu hình PATH tương tự cho user này. Phần đó được thực hiện ở mục 9.

Lưu ý: Không cần tạo một Node Project/PM2 Project trong aaPanel cho Ghost. Ghost production sẽ chạy bằng systemd.

5. Tạo thư mục project Ghost

User cần sử dụng

Dùng root để tạo thư mục website và sau đó chuyển quyền sở hữu cho user ghost.

Tạo thư mục:

mkdir -p /www/wwwroot/example.com

Kiểm tra:

ls -la /www/wwwroot/example.com

Ban đầu thư mục nên trống.

Không chạy ghost install trong thư mục này bằng root. Sau khi tạo user ghost và cấp quyền, các lệnh Ghost-CLI sẽ được chạy bằng user ghost.

6. Tạo user riêng cho Ghost

User cần sử dụng

Dùng root.

Ghost-CLI không cho phép chạy lệnh production bằng root. Vì vậy, cần tạo một Linux user riêng tên ghost.

Tạo user:

useradd --system --create-home --shell /bin/bash ghost

Kiểm tra:

id ghost

Cho Ghost sở hữu project:

chown -R ghost:ghost /www/wwwroot/example.com

Kiểm tra:

ls -ld /www/wwwroot/example.com

Kết quả nên tương tự:

drwxr-xr-x ghost ghost /www/wwwroot/example.com
Từ thời điểm này, các lệnh liên quan trực tiếp đến Ghost nên chạy bằng user ghost, không chạy bằng root.

7. Cài Ghost-CLI

User cần sử dụng

Cài Ghost-CLI bằng npm có thể thực hiện bằng root, vì đây là cài đặt global vào thư mục Node.js của aaPanel.

Cài Ghost-CLI:

npm install ghost-cli@latest -g

Sau đó kiểm tra:

ghost version

Ví dụ:

Ghost-CLI version: 1.32.2
Việc cài Ghost-CLI bằng root không có nghĩa là được phép chạy ghost install bằng root. Chỉ cài package global bằng root; quá trình cài Ghost production phải chạy bằng user ghost.

Xử lý lỗi ghost: command not found

Nếu npm install thành công nhưng:

ghost version

lại báo:

Command 'ghost' not found

thì kiểm tra:

npm config get prefix

Nếu kết quả:

/www/server/nodejs/v22.23.2

thì executable của Ghost nằm tại:

/www/server/nodejs/v22.23.2/bin/ghost

Thêm PATH cho phiên shell hiện tại:

export PATH="/www/server/nodejs/v22.23.2/bin:$PATH"

Sau đó:

which ghost

8. Xử lý quyền npm cache của aaPanel

User cần sử dụng

  • Kiểm tra và sửa quyền: root
  • Kiểm tra cache sau khi sửa: ghost

Một vấn đề có thể gặp khi aaPanel trước đó đã sử dụng npm bằng root là lỗi:

EACCES
Your cache folder contains root-owned files

Ví dụ npm cache nằm ở:

/www/server/nodejs/cache

Kiểm tra user Ghost:

id ghost

Kiểm tra quyền cache:

ls -ld /www/server/nodejs/cache

Nếu thư mục cache đang thuộc về root hoặc chứa file do root tạo, sửa quyền cho user ghost.

Có thể lấy UID và GID tự động:

GHOST_UID=$(id -u ghost)
GHOST_GID=$(id -g ghost)

chown -R "$GHOST_UID:$GHOST_GID" /www/server/nodejs/cache

Hoặc nếu UID/GID thực tế là:

uid=997
gid=988

thì có thể dùng:

chown -R 997:988 /www/server/nodejs/cache

Kiểm tra:

ls -ld /www/server/nodejs/cache

Sau đó chuyển sang user ghost:

su - ghost

Kiểm tra npm cache:

npm cache verify

Quay lại root khi cần:

exit

9. Đảm bảo user Ghost sử dụng đúng Node.js

User cần sử dụng

Thực hiện phần này bằng user ghost.

Chuyển sang user:

su - ghost

Kiểm tra:

which node
node -v

which npm
npm -v

which ghost

Các đường dẫn nên trỏ về Node của aaPanel:

/www/server/nodejs/v22.23.2/bin/node
/www/server/nodejs/v22.23.2/bin/npm
/www/server/nodejs/v22.23.2/bin/ghost

Nếu chưa đúng:

export PATH="/www/server/nodejs/v22.23.2/bin:$PATH"

Để lưu PATH lâu dài cho user ghost:

echo 'export PATH="/www/server/nodejs/v22.23.2/bin:$PATH"' >> ~/.bashrc

Nạp lại cấu hình:

source ~/.bashrc

Kiểm tra lại:

which node
node -v
which npm
npm -v
which ghost
Từ đây trở đi, bạn đang ở user ghost. Các lệnh cd, ghost install, ghost setup, ghost ls và các lệnh quản trị Ghost nên chạy trong phiên shell này.

10. Cài Ghost CMS

User cần sử dụng

Dùng user ghost.

Đi tới thư mục:

cd /www/wwwroot/example.com

Kiểm tra:

pwd

Kết quả:

/www/wwwroot/example.com

Cài Ghost:

ghost install 6.59.0

Bạn có thể thay 6.59.0 bằng phiên bản Ghost 6.x phù hợp tại thời điểm triển khai.

Không chạy lệnh trên bằng root. Nếu đang ở root, chuyển sang user ghost:
su - ghost

11. Điền thông tin trong Ghost-CLI

User cần sử dụng

Vẫn dùng user ghost.

Ghost-CLI sẽ hỏi các thông tin.

Blog URL

https://example.com

MySQL hostname

127.0.0.1

MySQL username

example_com

Đây là user của MariaDB, không phải Linux user ghost.

MySQL password

Nhập password database của user:

example_com

Ghost database name

example_com

Ghost sẽ dùng thông tin này để kết nối MariaDB.


12. Chọn Nginx = No

User cần sử dụng

Dùng user ghost trong quá trình Ghost-CLI hỏi cấu hình.

Ghost-CLI có thể hỏi:

Do you wish to set up Nginx?

Chọn:

No

Lý do: Nginx đã được aaPanel quản lý.

Không nên để Ghost-CLI tự sửa cấu hình /etc/nginx/ trong trường hợp aaPanel đã quản lý virtual host.

Nếu cần chỉnh Nginx, chuyển sang root sau khi thoát khỏi Ghost-CLI:

exit

hoặc mở một phiên SSH khác bằng root.


13. SSL = No

User cần sử dụng

Trong quá trình Ghost-CLI hỏi SSL, vẫn dùng user ghost.

Nếu Ghost-CLI hỏi setup SSL:

No

SSL sẽ được cấp và quản lý bằng aaPanel.

Mô hình:

Browser
   ↓ HTTPS
Nginx aaPanel
   ↓ HTTP
Ghost
Không cần cấp SSL trực tiếp trong Ghost-CLI khi aaPanel đã quản lý SSL cho domain.

14. Systemd = Yes

User cần sử dụng

Trong quá trình cài đặt, dùng user ghost.

Khi Ghost hỏi:

Do you want to set up Systemd?

chọn:

Yes

Ghost sẽ được quản lý bằng systemd.

Ví dụ service:

ghost_example-com.service

Sau khi Ghost-CLI tạo service, các lệnh kiểm tra systemd cần quyền hệ thống. Bạn có thể chạy bằng root hoặc dùng sudo từ user ghost nếu đã cấp sudo:

sudo systemctl status ghost_example-com

Hoặc chuyển sang root:

exit

rồi chạy:

systemctl status ghost_example-com

15. Cấp sudo cho user Ghost

User cần sử dụng

Dùng root.

Trong quá trình ghost setup, Ghost-CLI có thể cần sudo để:

  • thay đổi owner thư mục
  • tạo systemd service
  • reload systemd
  • start/enable service

Một cách đơn giản là tạo sudo rule riêng.

Nếu hiện tại đang ở user ghost, quay lại root:

exit

Tạo file sudoers:

cat > /etc/sudoers.d/ghost <<'EOF'
ghost ALL=(ALL) NOPASSWD: ALL
EOF

Sau đó:

chmod 440 /etc/sudoers.d/ghost

Kiểm tra syntax:

visudo -cf /etc/sudoers.d/ghost

Nếu trả:

parsed OK

thì chuyển sang user Ghost:

su - ghost

Test quyền sudo:

sudo -n whoami

Kết quả:

root
Cấu hình này được thực hiện bằng root, nhưng các lệnh Ghost-CLI sau đó vẫn chạy bằng user ghost. User ghost chỉ gọi sudo khi cần thực hiện thao tác hệ thống.
Sau khi triển khai thành công, có thể siết chặt sudo permission hơn nếu server yêu cầu hardening cao. Cấu hình trên ưu tiên sự đơn giản và tương thích với Ghost-CLI.

16. Nếu Ghost cài xong nhưng ghost setup chưa hoàn tất

User cần sử dụng

Chạy ghost setup bằng user ghost.

Trường hợp Ghost đã tải source và cài application nhưng fail ở systemd do sudo, không cần chạy lại ****ghost install.

Nếu đang ở root, chuyển sang ghost:

su - ghost

Chạy:

cd /www/wwwroot/example.com
ghost setup

Sau khi sudo hoạt động, Ghost-CLI có thể tiếp tục hoàn tất:

✔ Setting up "ghost" system user
✔ Setting up Systemd
✔ Starting Ghost
Các thao tác mà Ghost-CLI cần quyền hệ thống sẽ tự gọi sudo. Không cần chuyển toàn bộ quá trình sang root.

17. Kiểm tra Ghost đã chạy

User cần sử dụng

  • ghost ls: user ghost
  • systemctl status: root hoặc user ghost có sudo

Dùng user ghost:

cd /www/wwwroot/example.com
ghost ls

Ví dụ:

Name          Location                         Version   Status
example-com   /www/wwwroot/example.com        6.59.0    running

Kiểm tra systemd bằng sudo:

sudo systemctl status ghost_example-com --no-pager

Hoặc chuyển sang root:

exit
systemctl status ghost_example-com --no-pager

Mục tiêu:

Active: active (running)

18. Kiểm tra port Ghost

User cần sử dụng

Có thể chạy bằng ghost hoặc root. Nếu cần xem đầy đủ process, nên dùng root.

Kiểm tra:

ss -lntp | grep 2369

Nếu thấy:

LISTEN 127.0.0.1:2369

thì Ghost đang lắng nghe đúng.

Port Ghost chỉ bind trên 127.0.0.1, vì vậy không mở trực tiếp port này ra Internet. Người dùng bên ngoài chỉ truy cập thông qua Nginx.

19. Kiểm tra trực tiếp Ghost trước khi cấu hình Nginx

User cần sử dụng

Có thể chạy bằng user ghost hoặc root.

Test HTTP:

curl -I http://127.0.0.1:2369

Ghost có thể trả:

HTTP/1.1 301 Moved Permanently

với:

Location: https://example.com/

Điều này là bình thường khi URL chính thức của Ghost là HTTPS.

Không nên dùng curl https://127.0.0.1:2369 nếu backend Ghost đang phục vụ HTTP. HTTPS sẽ do Nginx xử lý.

20. Kiểm tra config.production.json

User cần sử dụng

File này thuộc về user ghost, vì vậy nên đọc hoặc chỉnh sửa bằng user ghost.

File quan trọng nhất của Ghost:

/www/wwwroot/example.com/config.production.json

Xem cấu hình server:

cd /www/wwwroot/example.com
jq '.server' config.production.json

Ví dụ:

{
  "port": 2369,
  "host": "127.0.0.1"
}

Kiểm tra URL:

jq '.url' config.production.json

Kết quả:

"https://example.com"

Kiểm tra database mà không lộ password:

jq '{
  client: .database.client,
  host: .database.connection.host,
  user: .database.connection.user,
  database: .database.connection.database
}' config.production.json
Nếu file không đọc được do sai quyền, không nên tùy tiện dùng chmod 777. Hãy kiểm tra owner và group bằng ls -l, sau đó sửa quyền bằng root nếu cần.

21. Cấu hình staffDeviceVerification

User cần sử dụng

  • Backup và chỉnh sửa file: user ghost
  • Nếu gặp lỗi quyền: root
  • Restart service: user ghost dùng sudo, hoặc root

Một thiết lập quan trọng sau khi cài Ghost là:

staffDeviceVerification

Trong một số môi trường, Ghost có thể yêu cầu xác minh thiết bị khi đăng nhập tài khoản Staff từ thiết bị mới. Nếu muốn tắt cơ chế này, mở:

/www/wwwroot/example.com/config.production.json

Tìm:

"staffDeviceVerification": true

Đổi thành:

"staffDeviceVerification": false

Có thể dùng jq để sửa

Đảm bảo đang ở user ghost:

whoami

Kết quả phải là:

ghost

Backup file trước:

cp /www/wwwroot/example.com/config.production.json \
/www/wwwroot/example.com/config.production.json.bak

Sau đó:

jq '.staffDeviceVerification = false' \
/www/wwwroot/example.com/config.production.json \
> /tmp/config.production.json \
&& mv /tmp/config.production.json \
/www/wwwroot/example.com/config.production.json

Kiểm tra:

jq '.staffDeviceVerification' \
/www/wwwroot/example.com/config.production.json

Kết quả phải là:

false
Nếu lệnh mv hoặc ghi file bị lỗi quyền, chuyển sang root để kiểm tra owner của file. Không nên đổi toàn bộ thư mục sang quyền ghi cho mọi user.

22. Restart Ghost sau khi thay đổi config

User cần sử dụng

Có thể dùng user ghost với sudo:

sudo systemctl restart ghost_example-com

Kiểm tra:

sudo systemctl status ghost_example-com --no-pager

Hoặc dùng lệnh Ghost-CLI bằng user ghost:

cd /www/wwwroot/example.com
ghost restart

Nếu vẫn:

running (production)

thì cấu hình đã được áp dụng.

Nếu đang ở root, có thể chạy trực tiếp:
systemctl restart ghost_example-com
Lưu ý bảo mật: Tắt staffDeviceVerification làm giảm một lớp bảo vệ trong quá trình đăng nhập Staff. Chỉ nên sử dụng khi bạn hiểu rõ nhu cầu vận hành và đang có các lớp bảo vệ khác như mật khẩu mạnh, HTTPS và giới hạn truy cập khu vực quản trị.

23. Tạo website trên aaPanel

User cần sử dụng

Thực hiện trong giao diện aaPanel bằng tài khoản quản trị aaPanel. Trên hệ thống, các thao tác tương ứng thường được aaPanel thực hiện với quyền hệ thống.

Trong aaPanel:

Website
→ Add Site

Domain:

example.com

Thư mục:

/www/wwwroot/example.com

Ghost không cần PHP.

Không cần tạo:

PM2 Project

Không cần:

npm start

Không cần:

pm2 start
aaPanel chỉ tạo website và virtual host. Application Ghost vẫn do user ghost chạy bằng systemd.

24. Cấu hình Nginx reverse proxy trên aaPanel

User cần sử dụng

Thực hiện trong giao diện aaPanel hoặc chỉnh file Nginx bằng root.

Trong website:

aaPanel
→ Website
→ example.com
→ Config

Các phần SSL của aaPanel có thể giữ nguyên.

Bạn cần proxy request từ Nginx tới:

http://127.0.0.1:2369

24.1. Cấu hình gốc cần thay thế

aaPanel thường tạo sẵn một cấu hình virtual host cho website. Cấu hình gốc có thể khác nhau tùy phiên bản aaPanel, nhưng thường có dạng tương tự:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    root /www/wwwroot/example.com;
    index index.php index.html index.htm;

    include /www/server/panel/vhost/rewrite/example.com.conf;

    location ~ \.php$ {
        fastcgi_pass unix:/tmp/php-cgi-82.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
    }

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
        expires 30d;
        access_log off;
    }

    location ~ .*\.(js|css)?$ {
        expires 12h;
        access_log off;
    }

    access_log /www/wwwlogs/example.com.log;
    error_log /www/wwwlogs/example.com.error.log;
}

Nếu website đã bật SSL, aaPanel thường có thêm một block HTTPS tương tự:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name example.com www.example.com;

    root /www/wwwroot/example.com;
    index index.php index.html index.htm;

    ssl_certificate     /www/server/panel/vhost/cert/example.com/fullchain.pem;
    ssl_certificate_key /www/server/panel/vhost/cert/example.com/privkey.pem;

    include /www/server/panel/vhost/rewrite/example.com.conf;

    location ~ \.php$ {
        fastcgi_pass unix:/tmp/php-cgi-82.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
    }

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
        expires 30d;
        access_log off;
    }

    location ~ .*\.(js|css)?$ {
        expires 12h;
        access_log off;
    }

    access_log /www/wwwlogs/example.com.log;
    error_log /www/wwwlogs/example.com.error.log;
}
Đây chỉ là cấu hình minh họa. Không nên sao chép nguyên xi các đường dẫn PHP, socket PHP hoặc đường dẫn chứng chỉ nếu chúng không đúng với server của bạn.

24.2. Cấu hình thay thế cho Ghost

Với Ghost, bạn không cần root, index, PHP-FPM hoặc các block xử lý static file theo kiểu website PHP. Hãy thay phần xử lý request của virtual host bằng cấu hình reverse proxy tới Ghost.

Cấu hình HTTP

Nếu chưa bật SSL hoặc muốn kiểm tra HTTP trước, dùng:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:2369;

        proxy_http_version 1.1;

        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_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_buffering off;

        proxy_read_timeout 300;
        proxy_send_timeout 300;

        proxy_redirect off;
    }

    access_log /www/wwwlogs/example.com.log;
    error_log /www/wwwlogs/example.com.error.log;
}

Cấu hình HTTPS đầy đủ

Sau khi đã cấp SSL trong aaPanel, dùng cấu hình tương tự sau:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name example.com www.example.com;

    ssl_certificate     /www/server/panel/vhost/cert/example.com/fullchain.pem;
    ssl_certificate_key /www/server/panel/vhost/cert/example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:2369;

        proxy_http_version 1.1;

        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 https;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_buffering off;

        proxy_read_timeout 300;
        proxy_send_timeout 300;

        proxy_redirect off;
    }

    access_log /www/wwwlogs/example.com.log;
    error_log /www/wwwlogs/example.com.error.log;
}
Đường dẫn chứng chỉ trong ví dụ có thể khác với server thực tế. Hãy lấy đúng đường dẫn certificate và private key do aaPanel hiển thị trong phần SSL của website.

Cấu hình thay thế khi aaPanel đã có sẵn block SSL

Nếu aaPanel đã tạo sẵn block server cho HTTPS, bạn không nhất thiết phải xóa toàn bộ file. Chỉ cần:

  1. Giữ lại các dòng listen, server_name, ssl_certificate, ssl_certificate_key, access_logerror_log.
  2. Xóa hoặc vô hiệu hóa các block PHP-FPM.
  3. Xóa hoặc vô hiệu hóa các block static file không cần thiết.
  4. Thay phần xử lý request bằng block sau:
location / {
    proxy_pass http://127.0.0.1:2369;

    proxy_http_version 1.1;

    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_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    proxy_buffering off;

    proxy_read_timeout 300;
    proxy_send_timeout 300;

    proxy_redirect off;
}

24.3. Cấu hình qua giao diện aaPanel

Nếu chỉnh trong giao diện aaPanel:

  1. Vào Website.
  2. Chọn example.com.
  3. Chọn Config.
  4. Sao lưu cấu hình hiện tại.
  5. Tìm block server đang phục vụ domain.
  6. Giữ lại cấu hình SSL nếu đã bật HTTPS.
  7. Xóa các block PHP-FPM và các block xử lý static file không cần thiết.
  8. Thêm hoặc thay thế bằng block location / reverse proxy.
  9. Lưu cấu hình.
  10. Kiểm tra cấu hình Nginx.
  11. Reload Nginx.

24.4. Cấu hình bằng file với root

Nếu chỉnh trực tiếp bằng SSH, trước tiên xác định file virtual host:

grep -R "server_name.*example.com" /www/server/panel/vhost/nginx/ -n

Sao lưu file trước khi sửa:

cp /www/server/panel/vhost/nginx/example.com.conf \
/www/server/panel/vhost/nginx/example.com.conf.bak

Mở file:

nano /www/server/panel/vhost/nginx/example.com.conf

Sau khi chỉnh xong, kiểm tra:

nginx -t

Nếu hợp lệ:

systemctl reload nginx
aaPanel có thể ghi đè file cấu hình khi bạn thay đổi website, SSL hoặc một số thiết lập trong giao diện. Vì vậy, nên ưu tiên cấu hình reverse proxy trong phần Config của chính website và kiểm tra lại sau mỗi lần aaPanel cập nhật virtual host.

24.5. Các lỗi cấu hình thường gặp ở mục này

Dùng sai giao thức backend

Đúng:

proxy_pass http://127.0.0.1:2369;

Sai:

proxy_pass https://127.0.0.1:2369;

Ghost thường phục vụ HTTP nội bộ, còn HTTPS được xử lý tại Nginx.

Dùng sai port

Kiểm tra port thực tế trong Ghost:

jq '.server' /www/wwwroot/example.com/config.production.json

Nếu Ghost dùng port khác, ví dụ 2371, phải sửa:

proxy_pass http://127.0.0.1:2371;

Giữ lại cấu hình PHP-FPM

Không nên giữ các block như:

location ~ \.php$ {
    fastcgi_pass unix:/tmp/php-cgi-82.sock;
}

Ghost không chạy bằng PHP-FPM.

Có nhiều block location /

Trong cùng một server block, chỉ nên có một block chính:

location / {
    proxy_pass http://127.0.0.1:2369;
}

Nếu có nhiều block location /, Nginx có thể xử lý không đúng như mong muốn.

Thiếu header proxy

Tối thiểu nên có:

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;

Các header này giúp Ghost nhận biết đúng domain, IP client và giao thức HTTPS ban đầu.


25. Không để aaPanel xử lý static file theo kiểu PHP

User cần sử dụng

Thực hiện trong aaPanel hoặc bằng root khi chỉnh file Nginx.

Nếu aaPanel tự tạo các block như:

location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$

hoặc:

location ~ .*\.(js|css)?$

cần xem xét bỏ các block này khi sử dụng Ghost làm reverse proxy toàn site.

Ghost nên nhận toàn bộ request thông qua:

location /

để application tự quản lý route, asset và admin.


26. SSL trên aaPanel

User cần sử dụng

Thực hiện trong giao diện aaPanel bằng tài khoản quản trị.

Trong:

Website
→ example.com
→ SSL

Cấp Let's Encrypt cho:

example.com
www.example.com

Sau đó có thể bật:

Force HTTPS

Mô hình lúc này:

https://example.com
        │
        ▼
Nginx aaPanel :443
        │
        ▼
http://127.0.0.1:2369
        │
        ▼
Ghost CMS

27. Kiểm tra cấu hình Nginx

User cần sử dụng

Dùng root, vì Nginx là dịch vụ hệ thống.

Nếu đang ở user ghost, quay lại root:

exit

Kiểm tra:

nginx -t

Phải nhận:

syntax is ok
test is successful

Sau đó:

systemctl reload nginx

28. Kiểm tra website

User cần sử dụng

Có thể chạy bằng root, ghost hoặc user thường có quyền truy cập mạng.

Test từ VPS:

curl -I https://example.com

Kiểm tra trang admin:

curl -I https://example.com/ghost/

Mở trình duyệt:

https://example.com

Trang quản trị:

https://example.com/ghost/

29. Kiểm tra database sau khi cài

User cần sử dụng

  • Lệnh kiểm tra database: có thể dùng root hoặc user database
  • Ghost kết nối database bằng user example_com
  • Không dùng Linux user ghost thay cho database user

Trước khi cài Ghost:

SHOW TABLES;

có thể trả:

Empty set

Sau khi Ghost chạy, hãy kiểm tra bằng tài khoản database example_com:

mysql -u example_com -p -h 127.0.0.1 example_com \
-e "SHOW TABLES;"

Lúc này database phải có rất nhiều bảng Ghost.


30. Kiểm tra log khi website lỗi

Ghost log

User cần sử dụng

Chạy bằng user ghost:

cd /www/wwwroot/example.com
ghost log

Hoặc dùng journalctl bằng user ghost có sudo:

sudo journalctl -u ghost_example-com -n 100 --no-pager

Hoặc chuyển sang root:

exit
journalctl -u ghost_example-com -n 100 --no-pager

Nginx error log

User cần sử dụng

Dùng root hoặc user có quyền đọc file log:

tail -f /www/wwwlogs/example.com.error.log

Nginx access log

tail -f /www/wwwlogs/example.com.log

31. Các lỗi thường gặp

Lỗi ghost: command not found

User cần sử dụng

Kiểm tra bằng user đang gặp lỗi, thường là ghost.

Nguyên nhân thường là PATH của Node aaPanel.

Kiểm tra:

npm config get prefix

Nếu:

/www/server/nodejs/v22.23.2

thêm:

export PATH="/www/server/nodejs/v22.23.2/bin:$PATH"

Sau đó kiểm tra:

which ghost

Lỗi EACCES trong npm cache

User cần sử dụng

  • Kiểm tra và sửa quyền: root
  • Xác nhận lại cache: ghost

Ví dụ:

npm error EACCES
/www/server/nodejs/cache/_cacache/...

Từ root, sửa quyền:

GHOST_UID=$(id -u ghost)
GHOST_GID=$(id -g ghost)

chown -R "$GHOST_UID:$GHOST_GID" /www/server/nodejs/cache

Sau đó chuyển sang ghost:

su - ghost
npm cache verify

Lỗi You can't run commands as the 'root' user

User cần sử dụng

Ghost-CLI production phải chạy bằng user ghost.

Không chạy Ghost-CLI production bằng root.

Chuyển sang:

su - ghost

Sau đó chạy lại lệnh cần thiết:

ghost setup

hoặc:

ghost ls

Lỗi Current directory is not empty

User cần sử dụng

Kiểm tra thư mục bằng root hoặc ghost.

Ghost-CLI yêu cầu thư mục cài đặt mới phải sạch.

Kiểm tra:

ls -la /www/wwwroot/example.com

Một file thử nghiệm như:

ghost-6.59.0.tgz

có thể khiến install bị từ chối.

Nếu cần xóa file, chỉ xóa bằng root khi chắc chắn file không cần thiết:

rm -f /www/wwwroot/example.com/ghost-6.59.0.tgz

Sau đó đảm bảo owner vẫn là ghost:

chown -R ghost:ghost /www/wwwroot/example.com

Lỗi ETARGET No matching version found

User cần sử dụng

Kiểm tra bằng user đang chạy npm, thường là ghost.

Trước tiên kiểm tra:

npm config get registry

Nên là:

https://registry.npmjs.org/

Sau đó:

npm view ghost version
npm view ghost@6.59.0 version

Có thể test trực tiếp:

npm pack ghost@6.59.0 --json

Nếu npm pack thành công nhưng Ghost-CLI thất bại, hãy kiểm tra environment của npm và Ghost-CLI trước khi hạ version Ghost.


Lỗi 502 Bad Gateway

User cần sử dụng

  • Kiểm tra port và curl: root hoặc ghost
  • Kiểm tra systemd: root hoặc ghost có sudo
  • Kiểm tra Nginx: root

Kiểm tra:

ss -lntp | grep 2369

và:

curl -I http://127.0.0.1:2369

Nếu Ghost không chạy:

sudo systemctl status ghost_example-com

Nếu Ghost chạy nhưng Nginx trả 502, kiểm tra:

proxy_pass http://127.0.0.1:2369;

Lỗi SSL wrong version number

User cần sử dụng

Có thể chạy bằng root, ghost hoặc user thường.

Nếu chạy:

curl -k -I https://127.0.0.1:2369

và nhận:

SSL routines::wrong version number

không nhất thiết là Ghost bị lỗi.

Backend Ghost đang có thể phục vụ HTTP:

http://127.0.0.1:2369

Còn HTTPS xử lý tại Nginx:

https://example.com

32. Lệnh quản trị Ghost thường dùng

User cần sử dụng

Các lệnh Ghost-CLI chạy bằng user ghost.

Vào thư mục project:

su - ghost
cd /www/wwwroot/example.com

Kiểm tra instance:

ghost ls

Khởi động:

ghost start

Restart:

ghost restart

Dừng:

ghost stop

Xem log:

ghost log

Kiểm tra hệ thống:

ghost doctor

Cập nhật Ghost:

ghost update

Các lệnh trên không nên chạy bằng root.

Nếu cần kiểm tra service systemd trực tiếp:

sudo systemctl status ghost_example-com

Nếu cần thao tác Nginx hoặc service hệ thống, chuyển sang root:

exit

33. Checklist sau khi hoàn thành

[ ] Đã phân biệt rõ root, ghost và database user
[ ] Ubuntu 24.04
[ ] Node.js 22.x
[ ] npm hoạt động bằng root
[ ] npm hoạt động bằng user ghost
[ ] Ghost-CLI đã cài
[ ] User ghost đã tạo
[ ] Project nằm tại /www/wwwroot/<domain>
[ ] Owner project là ghost:ghost
[ ] Database Ghost đã tạo bằng MariaDB root
[ ] Database user đã tạo
[ ] Ghost kết nối bằng database user riêng
[ ] Ghost 6.x đã cài bằng user ghost
[ ] Nginx được Ghost-CLI bỏ qua
[ ] SSL được Ghost-CLI bỏ qua
[ ] systemd hoạt động
[ ] Ghost đang listening trên 127.0.0.1:<port>
[ ] config.production.json có URL chính xác
[ ] staffDeviceVerification đã kiểm tra theo nhu cầu
[ ] Ghost đã restart sau khi đổi config
[ ] Nginx aaPanel proxy tới Ghost
[ ] Đã thay cấu hình PHP-FPM bằng reverse proxy
[ ] Đã kiểm tra không có nhiều block location /
[ ] SSL Let's Encrypt hoạt động
[ ] https://domain.com mở được
[ ] https://domain.com/ghost/ mở được
[ ] Database Ghost có bảng
[ ] Nginx không trả 502

34. Cấu hình kiến trúc khuyến nghị

Đối với server đang sử dụng aaPanel, cấu trúc này đơn giản và dễ quản lý:

                         INTERNET
                            │
                            ▼
                      example.com
                            │
                            ▼
                  ┌──────────────────┐
                  │   aaPanel Nginx  │
                  │     :80 / :443   │
                  │   SSL / Domain   │
                  └────────┬─────────┘
                           │
                           │ Reverse Proxy
                           │
                           ▼
                  ┌──────────────────┐
                  │    Ghost 6.x     │
                  │ Node.js 22.x     │
                  │ systemd          │
                  │ User: ghost      │
                  │ 127.0.0.1:2369   │
                  └────────┬─────────┘
                           │
                           ▼
                  ┌──────────────────┐
                  │    MariaDB       │
                  │  example_com     │
                  │ DB User:         │
                  │  example_com     │
                  └──────────────────┘

Phân quyền trách nhiệm:

root
├── Tạo user ghost
├── Tạo database và database user
├── Sửa quyền file
├── Cấu hình sudo
├── Quản lý systemd
└── Quản lý Nginx

ghost
├── Ghost installation
├── Ghost-CLI
├── Content
├── Config
├── ghost ls
├── ghost log
└── ghost restart

example_com
└── Kết nối MariaDB từ Ghost

Đây là mô hình phù hợp khi muốn giữ aaPanel làm nơi quản lý domain + Nginx + SSL, nhưng để Ghost tự quản lý application bằng Node.js + systemd.


Kết luận

Cài Ghost CMS trên Ubuntu với aaPanel không khó, nhưng cần tránh trộn lẫn trách nhiệm giữa aaPanel, root, user ghost và tài khoản database.

Cách triển khai nên phân chia:

root
├── User hệ thống
├── Quyền file
├── Sudo
├── Systemd
└── Nginx

aaPanel
├── Domain
├── Nginx
└── SSL

ghost
├── Ghost installation
├── Content
├── Config
└── Ghost-CLI

MariaDB
└── Ghost database

Quan trọng nhất:

  • Dùng root cho các thao tác quản trị hệ thống.
  • Dùng ghost để chạy Ghost-CLI và quản trị Ghost.
  • Dùng user database riêng như example_com để Ghost kết nối MariaDB.
  • Không chạy ghost install, ghost setup hoặc ghost update bằng root.
  • Không tạo PM2 Project để chạy Ghost.
  • Không để Ghost-CLI tự sửa Nginx khi aaPanel đã quản lý Nginx.
  • Không để Ghost-CLI tự cấp SSL nếu aaPanel đã quản lý SSL.
  • Ghost chạy bằng user riêng ghost.
  • Ghost chạy bằng systemd.
  • Nginx reverse proxy tới 127.0.0.1:<Ghost_Port>.
  • Khi cấu hình Nginx, cần thay phần xử lý PHP-FPM bằng location / reverse proxy tới đúng port Ghost.
  • Sau khi thay đổi config.production.json, cần restart Ghost.
  • Kiểm tra staffDeviceVerification theo nhu cầu vận hành và cân nhắc rủi ro bảo mật trước khi tắt.

Với mô hình này, bạn có thể triển khai nhiều website Ghost trên cùng một VPS bằng cách dùng các port localhost khác nhau, ví dụ:

example.com  → 127.0.0.1:2369
site2.com    → 127.0.0.1:2371
site3.com    → 127.0.0.1:2372

Trong khi toàn bộ domain, SSL và reverse proxy vẫn được quản lý tập trung từ aaPanel.