Chuyển đến nội dung
Jok Holk
EN | VI

Đăng ngày 7 phút đọc

Cách tôi setup Strapi trên VPS cho Portfolio của mình

Setup Strapi CMS trên VPS với Nginx Reverse Proxy để fix lỗi Mixed Content khi chạy HTTPS.

  • VPS
  • Strapi
Trong bài này

Tóm Tắt

  • Vấn đề 1: Ảnh từ Strapi bị trình duyệt chặn do Mixed Content (HTTP vs HTTPS).
  • Giải pháp 1: Nginx Reverse Proxy — cấp cho Strapi một subdomain HTTPS sạch.
  • Vấn đề 2: CORS mặc định của Strapi phản chiếu bất kỳ Origin nào — lỗ hổng bảo mật thực sự.
  • Giải pháp 2: Whitelist chỉ domain tin cậy trong cả Nginx và Strapi config.
  • Kết quả: Portfolio fetch bài viết và ảnh an toàn, không cảnh báo, không lỗ hổng.

Khi setup portfolio, tôi muốn một CMS thực sự cho phần blog — có admin panel, upload media, hỗ trợ đa ngôn ngữ, và API sạch. Tôi chọn Strapi chạy trên VPS.

Setup phần lớn khá đơn giản. Nhưng có hai vấn đề tôi không lường trước và mất nhiều thời gian hơn đáng lẽ. Bài này ghi lại cả hai — để nếu bạn đang setup tương tự, bạn không vấp phải những chỗ tôi đã vấp.

Bài viết dành cho những ai muốn tự host CMS và kết nối với frontend deploy trên Vercel hoặc Netlify.

Một số bước sẽ khác nhau tùy theo DNS provider của bạn. Tôi đang dùng Cloudflare — tự điều chỉnh nếu bạn dùng dịch vụ khác.


1. Thuê VPS và Chuẩn Bị

Tôi thuê VPS chạy Ubuntu. Việc đầu tiên sau khi SSH vào:

bash
sudo apt update && sudo apt upgrade -y

Luôn làm điều này trước khi cài bất cứ thứ gì. Tránh được những conflict dependency kỳ lạ về sau.


2. Cài Đặt Strapi

bash
npx create-strapi-app@4 your-project-name
cd your-project-name
npm run develop

Tôi dùng Strapi v4 — không phải phiên bản mới nhất. API calls của portfolio tôi được viết cho format response của v4. Nếu bạn đang bắt đầu mới hoàn toàn không có frontend cũ, dùng phiên bản mới nhất là ổn.

Khi chạy xong, bạn có thể vào admin panel tại:

http://<ip-vps>:1337/admin

Tạo tài khoản admin tại đây, sau đó setup các Collection Type cần thiết. Cho blog portfolio tôi tạo:

  • Article — với các field: title, description, content, slug, publish date, image, categories. Tôi cũng bật Internationalization cho collection này để hỗ trợ EN/VI.
  • Category — chỉ có field name, dùng để tag bài viết.

Sau khi tạo collection, vào Settings → Users & Permissions → Roles → Public và bật find và findOne cho cả hai collection. Không làm bước này, API calls sẽ trả về 403.


3. Vấn Đề: Mixed Content

Đây là chỗ phiền phức bắt đầu.

Portfolio của tôi deploy trên Vercel và chạy qua HTTPS. Strapi lúc này đang chạy HTTP tại http://<ip>:1337. Khi portfolio cố load ảnh từ Strapi, trình duyệt chặn:

Mixed Content: The page was loaded over HTTPS, but requested an insecure resource.

Ảnh không load. Không có lỗi nào trong terminal. Chỉ là placeholder hỏng khắp nơi.

Cách fix là cấp cho Strapi domain HTTPS riêng — nghĩa là setup subdomain và chạy Nginx như một reverse proxy trước Strapi.


4. Chạy Strapi Với PM2

Trước khi setup Nginx, hãy cho Strapi chạy như một tiến trình nền ổn định. Không làm điều này, Strapi dừng ngay khi bạn đóng terminal.

bash
npm run build
npm install -g pm2
pm2 start "npm run start" --name your-strapi
pm2 save
pm2 startup

Chạy lệnh mà pm2 startup output ra — nó setup auto-start khi system reboot.


5. Setup Subdomain

Trong DNS provider của bạn, tạo một A record:

  • Name: strapi (hoặc cms, hoặc bất cứ gì bạn muốn)
  • Value: IP VPS của bạn
  • Proxy/CDN: tắt — DNS only

Nếu bạn dùng Cloudflare, đảm bảo icon proxy màu cam đã tắt cho record này. Traffic qua proxy không hoạt động ổn với Certbot khi issue SSL.

Bây giờ strapi.yourdomain.com trỏ về VPS của bạn.


6. Cài Đặt và Cấu Hình Nginx

bash
sudo apt install nginx -y
sudo nano /etc/nginx/sites-available/strapi
nginx
server {
    listen 80;
    server_name strapi.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:1337;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;

        # CORS hardening — chỉ cho phép frontend domain của bạn
        add_header 'Access-Control-Allow-Origin' 'https://yourdomain.com' always;
        add_header 'Access-Control-Allow-Credentials' 'false' always;
    }
}
bash
sudo ln -s /etc/nginx/sites-available/strapi /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

7. Thêm SSL Với Certbot

bash
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d strapi.yourdomain.com

Làm theo hướng dẫn. Certbot sẽ tự cập nhật Nginx config để xử lý HTTPS và setup auto-renewal.

Khi xong, admin panel của Strapi có thể truy cập tại:

https://strapi.yourdomain.com/admin

Và vấn đề Mixed Content biến mất — ảnh giờ load qua HTTPS.


8. Lỗ Hổng Bảo Mật Mà Hầu Hết Tutorial Bỏ Qua

Đến đây hầu hết hướng dẫn đều dừng lại. Nhưng có một lỗ hổng thực sự mà dễ bỏ sót.

Mặc định, Strapi phản chiếu lại bất kỳ Origin header nào nó nhận được. Nghĩa là nếu một trang web độc hại gửi request đến https://strapi.yourdomain.com/api/articles kèm credentials, Strapi sẽ trả về Access-Control-Allow-Origin: https://malicious-site.com — và trình duyệt sẽ cho phép đọc response.

Trong thực tế điều này có thể cho phép trang của kẻ tấn công thực hiện các API request được xác thực thay mặt admin đang đăng nhập, mà họ không hề biết.

Một người bạn làm bảo mật đã chỉ ra điều này sau khi xem setup của tôi. Cách fix đơn giản — whitelist chỉ domain frontend thực sự của bạn trong middleware config của Strapi.

bash
nano ~/your-project-name/config/middlewares.js
js
module.exports = [
  'strapi::errors',
  'strapi::security',
  {
    name: 'strapi::cors',
    config: {
      origin: ['https://yourdomain.com'],
      methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
      headers: ['Content-Type', 'Authorization'],
      credentials: false,
    },
  },
  'strapi::poweredBy',
  'strapi::logger',
  'strapi::query',
  'strapi::body',
  'strapi::session',
  'strapi::favicon',
  'strapi::public',
];
bash
pm2 restart your-strapi

Bằng cách khóa CORS ở cả Nginx lẫn Strapi config, bạn có hai lớp bảo vệ. Nếu một lớp bị misconfigure hoặc bị bypass, lớp kia vẫn giữ.


9. Cách Data Flow Hoạt Động

[Browser] --HTTPS--> [Nginx] --HTTP:1337--> [Strapi]

Người dùng chỉ thấy domain HTTPS sạch. Nginx xử lý SSL termination và forward request nội bộ đến Strapi trên port local. Strapi không cần tự quản lý SSL.


10. Tổng Kết

  1. Cài Strapi, tạo collections, mở API permissions.
  2. Chạy Strapi với PM2 để nó luôn hoạt động.
  3. Tạo DNS record subdomain trỏ về VPS.
  4. Cấu hình Nginx làm reverse proxy với CORS headers.
  5. Issue SSL với Certbot.
  6. Fix CORS trong config/middlewares.js để chỉ whitelist frontend domain của bạn.

Vấn đề Mixed Content biến mất. Lỗ hổng CORS đã đóng. Frontend của bạn có thể fetch data từ Strapi qua HTTPS một cách an toàn.