FAQ

Đây là hướng dẫn cài đặt, cấu hình và bảo trì Arcivéo Security Monitor. Các phần được nhóm lại: tổng quan chung, triển khai bảng điều khiển, kết nối các công cụ bảo mật, mô-đun tích hợp và chẩn đoán. Có thể sao chép lệnh bằng nút bên phải.

Bắt đầu từ đâu

01. Cài đặt bảng điều khiển — chọn phương thức

Việc cài đặt bảng điều khiển nằm trên các trang hướng dẫn từng bước riêng. Hãy chọn phương thức:

Nếu chưa chắc — hãy chọn cách tự động. Cẩm nang này vẫn là nguồn thông tin thống nhất về SSL, công cụ, cron và chẩn đoán — các trang cài đặt tham chiếu đến những mục của nó mà không lặp lại nội dung.

Tổng quan

02. Arcivéo Security Monitor là gì

Arcivéo Security Monitor — bảng điều khiển bảo mật máy chủ. Nó thu thập dữ liệu từ các công cụ đã cài đặt (Fail2ban, UFW, Lynis, ModSecurity, AIDE, ClamAV, Auditd, CrowdSec, Suricata, Falco v.v.) và hiển thị chúng trong một giao diện thống nhất với dashboard, bản đồ tấn công và các trang chi tiết cho từng công cụ.

Monitor không phải là công cụ bảo vệ chủ động — bản thân nó không chặn các cuộc tấn công. Nhiệm vụ của nó là tổng hợp thông tin từ các công cụ đang hoạt động và trình bày một cách tiện lợi.

03. Cách trình giám sát hoạt động trên máy chủ

Trình giám sát chỉ hoạt động cục bộ — phải được cài trên chính máy chủ mà nó giám sát. Không dùng SSH hay API từ xa.

Mọi lệnh (fail2ban-client, ufw status, ipset list v.v.) được dashboard chạy dưới danh nghĩa người dùng máy chủ web (thường là www-data, trên panel hosting là tài khoản site) với bộ quyền hạn hẹp sudo — chỉ cho các tiện ích cụ thể, không có quyền root chung. Kết quả được phân tích và hiển thị trong trình duyệt.

Hai nhịp làm việc. Các trang của panel hỏi công cụ ngay lúc bạn mở chúng — bạn luôn thấy trạng thái hiện thời. Ngoài ra, các tác vụ nền chạy theo lịch (cron):

  • 5 phút một lần — ảnh chụp tải cho trang “Hiệu năng”;
  • 5 phút một lần — cảm biến sự kiện bảo mật: lệnh chặn mới, cảnh báo IDS, một dịch vụ bảo vệ đã dừng, một cổng vừa mở (mục “Sự kiện bảo mật”);
  • mỗi ngày một lần — báo cáo bảo mật gửi tới Telegram và email, cùng các kiểm tra nặng (Lynis, AIDE, ClamAV, debsums, Logwatch);
  • 15 phút một lần — bản tóm tắt máy chủ gửi về tài khoản cá nhân, nếu đã bật.

Danh sách đầy đủ các tác vụ nằm ở mục “Tất cả tác vụ cron ở một nơi”.

Với nhiều máy chủ, hãy cài trình giám sát trên từng máy riêng, mỗi máy một tên miền. Để thấy tất cả trong một danh sách, hãy bật tổng quan máy chủ (mục “Tổng quan máy chủ trong tài khoản cá nhân”).

04. Cách tính Điểm bảo mật

Điểm bắt đầu từ mức tối đa và giảm dần theo từng vấn đề được phát hiện:

  • UFW không hoạt động — −30
  • Fail2ban chưa chạy (không có jail hoạt động) — −25
  • Không có khóa WebAuthn — −15
  • Lynis hardening index < 60 — −20; 60–79 — −10
  • IPset ipsum chưa được nạp — −10
  • ClamAV phát hiện mối đe dọa — −20
  • AIDE phát hiện thay đổi tệp — −15
  • SSL đã hết hạn — −30, hết hạn trong <14 ngày — −15, <30 ngày — −5
  • CrowdSec đã cài nhưng chưa chạy — −5
  • Suricata đã cài nhưng chưa chạy — −5
  • CSDL/bộ đệm (MySQL, PostgreSQL, Redis…) truy cập được từ bên ngoài — −10
  • Cho phép đăng nhập root qua SSH (PermitRootLogin yes) — −20
  • Cho phép đăng nhập bằng mật khẩu qua SSH (PasswordAuthentication yes) — −10
  • Thiết lập SSH nhỏ: bật X11Forwarding, MaxAuthTries lớn hơn 3 hoặc mọi tài khoản đều đọc được sshd_config — −5
  • Gia cố máy chủ web: phản hồi để lộ phiên bản máy chủ web hoặc PHP, phương thức TRACE đang bật, hoặc thiếu header Strict-Transport-Security hay X-Frame-Options — −5
  • Có bản cập nhật bảo mật đang chờ cài — −5

Kết quả: 80+ = Được bảo vệ, 60–79 = Cần chú ý, <60 = Đang bị đe dọa.

Các mức trừ cho ClamAV, AIDE, CrowdSec và Suricata chỉ áp dụng nếu công cụ đã được cài đặt. Lynis và AIDE khi chưa khởi tạo cơ sở dữ liệu sẽ hiển thị là “không có dữ liệu” và không làm giảm điểm. Số lượng tấn công trong hôm nay được hiển thị trên dashboard, nhưng không ảnh hưởng đến Điểm bảo mật. Mục gia cố máy chủ web không được tính nếu không trang nào trả lời lần kiểm tra.

Cài đặt và giấy phép

05. WebAuthn — xác thực hai yếu tố

WebAuthn — chuẩn xác thực không cần mật khẩu thông qua khóa phần cứng. Hỗ trợ YubiKey, Touch ID, Face ID, Windows Hello, Passkey.

Sau khi đăng nhập bằng mật khẩu, hệ thống yêu cầu xác nhận qua khóa đã đăng ký. Ngay cả khi mật khẩu bị lộ — không có khóa vật lý hoặc sinh trắc học thì cũng không thể đăng nhập.

Để thiết lập, mở Khóa WebAuthn trong menu bên và nhấn “Đăng ký khóa”. Hãy đăng ký ngay hai khóa: nếu khóa duy nhất bị mất hoặc hỏng, bạn sẽ không thể đăng nhập vào dashboard bằng khóa đó.

WebAuthn chỉ hoạt động qua HTTPS. Trên kết nối HTTP, việc đăng ký và đăng nhập bằng khóa không khả dụng.

06. Thông báo: Telegram, Email, webhook và push

Có bốn kênh: Telegram, Email, webhook và push vào trình duyệt. Chúng độc lập với nhau — bật kênh nào cũng được, thậm chí cả bốn. Cũng qua các kênh đó, panel gửi báo cáo bảo mật hằng ngày, cảnh báo về tải và các phát hiện của cảm biến sự kiện; tất cả được cấu hình một lần, tại đây, trong mục “Cài đặt”.

Telegram. Cần token của bot và chat id:

  1. Trong Telegram, nhắn cho @BotFather → /newbot → nhận token dạng 123456:ABC....
  2. Gửi cho bot mới của bạn một tin nhắn bất kỳ (để bot có thể trả lời bạn).
  3. Tìm chat id của bạn: nhắn cho bot @userinfobot, hoặc mở https://api.telegram.org/bot<TOKEN>/getUpdates và tìm "chat":{"id":...}.
  4. Dán token và chat id vào “Cài đặt” → Telegram, nhấn “Lưu và gửi thử”.

Email. Hai cách để chọn trong “Cài đặt” → Email:

  • SMTP — host, cổng (465/SSL hoặc 587/TLS), tên đăng nhập và mật khẩu hộp thư của bạn;
  • Resend — API hiện đại: nhập API key (re_...) và tên miền người gửi đã xác minh.

Webhook. Một địa chỉ duy nhất nhận POST là đủ cho Slack, Discord, Mattermost, n8n và trình xử lý của riêng bạn. “Cài đặt” → Webhook: dán địa chỉ và bật công tắc.

  • tới Slack và Mattermost gửi {"text": …}, tới Discord gửi {"content": …} (Discord từ chối tin nhắn dài hơn 2000 ký tự nên phần văn bản bị cắt bớt);
  • mọi địa chỉ khác nhận JSON có cấu trúc: text, event, severity, source, count, host, ts — không cần phân tích lại nội dung tin nhắn.

Thông báo đẩy. Tin nhắn đến thẳng trình duyệt, và nếu panel đã được thêm vào màn hình chính của điện thoại thì hiện ra như một thông báo ứng dụng bình thường — không cần bot, không cần máy chủ thư ở giữa. “Cài đặt” → Thông báo đẩy: bật công tắc và nhấn “Kết nối thiết bị này” trên từng thiết bị cần nhận thông báo. Bộ đếm “Thiết bị đã kết nối” cho biết có bao nhiêu thiết bị.

Push chỉ hoạt động qua HTTPS (giống WebAuthn), và cần cấp quyền hai lần: trước hết trong trình duyệt — lời hỏi xuất hiện sau nút “Kết nối thiết bị này” — rồi trong chính hệ điều hành. Nếu trên Windows công tắc chung Cài đặt → Hệ thống → Thông báo đang tắt, trình duyệt vẫn cấp quyền, panel vẫn báo đã gửi, mà màn hình thì chẳng hiện gì. Chế độ “Không làm phiền” và việc tắt thông báo trình duyệt trên macOS (Cài đặt Hệ thống → Thông báo) cũng cho kết quả như vậy.
Mỗi kênh đều có nút “Gửi thử” riêng để kiểm tra ngay lập tức. Lịch gửi báo cáo tự động chạy qua cron (mục “Tất cả tác vụ cron”): cron kích hoạt việc gửi, còn các kênh được lấy từ cài đặt.

Trạng thái báo cáo: “CHÚ Ý” hoặc “OK”. Tiêu đề chuyển thành “CHÚ Ý” chỉ khi có sự cố thực sự hoặc có hành động đang chờ: phát hiện mối đe dọa ClamAV, thay đổi tệp trong AIDE, sự kiện nghiêm trọng của Falco (Emergency/Alert/Critical trong 24 giờ qua), dịch vụ bị sập trong Monit, cần khởi động lại, SSL sắp hết hạn (≤14 ngày) hoặc đang chờ cập nhật bảo mật. Nhiễu nền — bot dò SSH, IP bị fail2ban cấm, cảnh báo Suricata, cảnh báo Lynis và các yêu cầu đã bị ModSecurity chặn — không làm tăng trạng thái, nên những con số đó trong báo cáo tự thân không có nghĩa là “CHÚ Ý”.

07. Giấy phép — nhập và kích hoạt

Các module giám sát chi tiết (Lynis, UFW, ModSecurity, bản đồ tấn công, AIDE, ClamAV v.v.) sẽ mở khi có giấy phép hợp lệ. Nếu không có, dashboard, cài đặt và tài khoản vẫn hoạt động, còn các module hiển thị thẻ “Cần giấy phép”.

Sau khi mua trong tài khoản cá nhân, bạn có mã kích hoạt dạng ARCIVEO-XXXX-XXXX-XXXX-XXXX. Cần “kích hoạt” mã này cho domain của panel — thao tác này biến mã thành tệp giấy phép đã ký (khối [license]), rồi bạn dán vào panel.

Cách kích hoạt (3 bước):

  1. Lấy mã kích hoạt. Tài khoản cá nhân my.arciveo.com → mục “Giấy phép” / “Kích hoạt giấy phép” — sao chép mã ARCIVEO-….
  2. Kích hoạt mã cho domain của bạn. Cũng trong tài khoản cá nhân, mở “Kích hoạt giấy phép”, nhập: mã kích hoạt, email của bạn và domain của panel (địa chỉ mở monitor, ví dụ monitor.example.com). Nhấn kích hoạt — hệ thống tạo tệp giấy phép gắn với domain này và hiển thị trong ô có nút “Sao chép”.
  3. Dán key vào panel. Sao chép toàn bộ nội dung giấy phép → trong panel mở “Cài đặt” → khối “Giấy phép”, dán và nhấn “Lưu”. Các module mở khóa ngay.

Panel kiểm tra key bằng mật mã: chữ ký, sự gắn kết với domain và thời hạn hiệu lực.

Domain khi kích hoạt phải trùng khớp chính xác với địa chỉ panel. Lấy nó từ hằng APP_URL trong config.php và chỉ nhập tên host — không có https:// và không có tiền tố www. Kích hoạt chỉ một lần: mã biến thành giấy phép cho domain đã nhập và không kích hoạt lại được — nếu nhập sai domain, key sẽ không hợp với panel của bạn, còn mã thì đã bị tiêu tốn. Vì vậy hãy nhập domain thật cẩn thận.
Hết hạn hoặc đổi domain — trên đầu panel sẽ hiện cảnh báo. Giấy phép gắn với domain vĩnh viễn và không chuyển sang domain khác: với thời hạn mới hoặc domain mới cần key mới (mua trong tài khoản cá nhân và kích hoạt một lần).

08. Tệp config.php — toàn bộ cài đặt của panel

Tất cả tham số chính của panel được khai báo trong một tệp duy nhất config.php ở thư mục gốc (cạnh thư mục public/) bằng các hằng define() thông thường. Tệp được tạo khi cài đặt; hiếm khi cần sửa thủ công — chủ yếu khi đổi domain, di chuyển hoặc kết nối tới cơ sở dữ liệu khác. Sau mỗi lần sửa, hãy khởi động lại PHP-FPM (nếu không, do OPcache mà thay đổi sẽ không được áp dụng).

Điền giá trị của bạn vào những chỗ được tô sáng; phần còn lại giữ nguyên:

// --- Cơ sở dữ liệu --- define('DB_HOST', 'localhost'); // giữ nguyên define('DB_NAME', 'db_name'); // giá trị đã đặt khi tạo CSDL define('DB_USER', 'user'); // giá trị đã đặt khi tạo CSDL define('DB_PASS', 'db_password'); // giá trị đã đặt khi tạo CSDL define('DB_CHARSET', 'utf8mb4'); // giữ nguyên // --- Ứng dụng --- define('APP_URL', 'https://monitor.example.com'); // địa chỉ panel, không có dấu gạch chéo ở cuối define('TIMEZONE', 'Asia/Ho_Chi_Minh'); // múi giờ của bạn // --- Thời gian phiên --- define('SESSION_LIFETIME', 28800); // thời gian nhàn rỗi trước khi đăng nhập lại, giây (28800 = 8 giờ)

Cơ sở dữ liệu. Thông tin kết nối tới MySQL/MariaDB:

  • DB_HOST — máy chủ CSDL, hầu như luôn là localhost;
  • DB_NAME — tên cơ sở dữ liệu của panel;
  • DB_USER — người dùng CSDL (chỉ truy cập được cơ sở dữ liệu của mình);
  • DB_PASS — mật khẩu của người dùng này;
  • DB_CHARSET — bảng mã kết nối, hãy giữ utf8mb4.

Ứng dụng.

  • APP_URL — địa chỉ đầy đủ của panel (vd. https://monitor.example.com). Phải trùng với domain mà giấy phép đã được kích hoạt — nếu không, khóa sẽ bị từ chối (xem mục “Giấy phép”);
  • TIMEZONE — múi giờ PHP: chỉ ảnh hưởng tới cách panel hiển thị ngày và giờ. Không ảnh hưởng tới thời điểm chạy các tác vụ cron — nơi đó dùng múi giờ của hệ thống (xem “Tất cả tác vụ cron”).

Thời gian phiên. SESSION_LIFETIME — thời gian chờ nhàn rỗi của phiên tính bằng giây (trượt: làm mới khi có hoạt động). Mặc định 28800 = 8 giờ; sau khoảng thời gian không hoạt động này, panel sẽ yêu cầu đăng nhập lại. Ví dụ, 3600 = 1 giờ, 86400 = một ngày.

Ghi nhật ký lỗi. Lỗi không bao giờ hiển thị cho khách truy cập mà được ghi vào logs/php_errors.log — có thể xem chúng ở trang “Nhật ký ứng dụng”. Các dòng này (display_errors=0, log_errors=1, đường dẫn error_log) thường không cần đổi — cài đặt được khai báo ngay trong tệp và không phụ thuộc vào php.ini.

config.php — tệp bí mật. Nó chứa mật khẩu CSDL. Tệp nằm ở thư mục gốc của panel (cạnh public/), và với panel này thì web-root (DocumentRoot) chính là thư mục gốc của panel, không phải public/. Bản thân tệp không bị “rò rỉ”: trong .htaccess ở gốc đã có lệnh cấm rõ ràng cho nó (Require all denied) — máy chủ trả về 403. Ngay cả khi không có quy tắc này thì mã nguồn cũng không rò rỉ: đây là PHP — máy chủ thực thi nó chứ không trả về dưới dạng văn bản. Để chắc chắn: đừng đưa nó lên các kho công khai và đừng gửi cho bộ phận hỗ trợ với mật khẩu thật. Quyền của tệp là 640.
Khi di chuyển hoặc khôi phục quyền truy cập, tệp này là nguồn thông tin chính: tên CSDL, người dùng và mật khẩu đều lấy từ đây (xem các mục “Cập nhật và di chuyển panel” và “Khôi phục quyền truy cập”).

09. API chỉ đọc (JSON và Prometheus)

Panel được đóng bằng đăng nhập có mật khẩu và, nếu đã bật, bằng bộ lọc IP. Với một chương trình thì cả hai đều không dùng được, nên các hệ thống bên ngoài có lối vào riêng — hai địa chỉ chỉ đọc, được bảo vệ bằng mã. Mã này không thay đổi gì trên máy chủ: dùng nó chỉ có thể lấy về các chỉ số.

Bật lên — “Cài đặt” → “API chỉ đọc”: công tắc “Bật API”; mã được tạo tự động ở lần lưu đầu tiên. Nút “Mã mới” cấp một mã khác và ngay lúc đó ngắt tất cả những ai đang dùng mã cũ.

  • /api/status.php — JSON: điểm bảo mật, trạng thái các mô-đun, lần đo tải mới nhất kèm các tiến trình nặng nhất và các sự kiện bảo mật trong 24 giờ qua;
  • /api/metrics.php — cũng những con số đó ở định dạng văn bản của Prometheus (các số đo arciveo_*: arciveo_security_score, arciveo_fail2ban_banned, arciveo_cpu_busy_pct và tương tự).
curl -H "Authorization: Bearer <TOKEN>" https://monitor.example.com/api/status.php curl -H "Authorization: Bearer <TOKEN>" https://monitor.example.com/api/metrics.php

Nếu máy khách của bạn không đặt được tiêu đề, mã cũng được chấp nhận dưới dạng tham số — ?token=…. Lưu ý rằng ở dạng đó nó sẽ lọt vào nhật ký truy cập của máy chủ web, nên dùng tiêu đề vẫn hơn.

Prometheus. Khối dựng sẵn cho scrape_configs:

scrape_configs: - job_name: arciveo metrics_path: /api/metrics.php scheme: https authorization: credentials: <TOKEN> static_configs: - targets: ['monitor.example.com']
Phản hồi được lưu đệm 5 phút: đằng sau những con số này là chính phép thu thập nặng nề vốn đứng sau bảng điều khiển. Vì vậy hỏi thường xuyên chẳng tốn gì của máy chủ — các giá trị đơn giản lặp lại cho tới lần làm mới sau — nhưng khoảng cách dưới một phút cũng không có ý nghĩa gì.
401 — mã sai hoặc không được gửi. 404 kèm dòng chữ api disabled — API đang tắt trong cài đặt. Các yêu cầu bị từ chối được ghi vào nhật ký ứng dụng cùng IP người gửi, nên việc dò mã sẽ thấy được ở trang “Nhật ký ứng dụng”.

10. Tổng quan máy chủ trong tài khoản cá nhân

Panel được cài trên từng máy chủ riêng và trả lời rất tốt câu hỏi “máy này đang thế nào”. Câu hỏi “tất cả các máy của tôi đang thế nào” thì nó không trả lời — vì vậy mỗi panel có thể gửi một bản tóm tắt ngắn về tài khoản cá nhân, nơi các máy chủ hiện ra trong một danh sách duy nhất.

Những gì cần lấy trong tài khoản cá nhân my.arciveo.com → mục “Máy chủ của tôi”: ở đó cấp một mã (dạng fk_…) và ghi rõ địa chỉ nơi nhận https://my.arciveo.com/api/fleet.php. Mã là một cho mỗi tài khoản — dùng chung cho mọi máy chủ của bạn.

Những gì cần đặt trong panel — “Cài đặt” → “Tổng quan máy chủ”: địa chỉ nơi nhận, mã, tên máy chủ (cách nó hiện trong danh sách; để trống thì lấy tên miền của panel) và công tắc “Gửi bản tóm tắt”. Nút “Gửi ngay” kiểm tra kết nối tức thì, không phải chờ lịch.

Sau đó bản tóm tắt đi theo cron, 15 phút một lần:

# 15 phút một lần — bản tóm tắt máy chủ gửi về tài khoản cá nhân 3,18,33,48 * * * * /usr/local/bin/fleet-push-all.sh >> /path/to/monitor/logs/cron.log 2>&1

Chính xác thì những gì được gửi đi: tên và tên miền máy chủ, hệ điều hành, thời gian hoạt động, phiên bản PHP; điểm bảo mật; trạng thái các mô-đun (UFW, số jail của Fail2ban, số lệnh chặn hôm nay, CrowdSec, Suricata và Falco có đang chạy không, mối đe dọa ClamAV, thay đổi AIDE, chỉ số Lynis, các bản cập nhật đang chờ, có cần khởi động lại không, số ngày còn lại của chứng chỉ, số cổng mở nguy hiểm); lần đo tải gần nhất (load, CPU, RAM, đĩa, inode); bộ đếm sự kiện trong 24 giờ và sự kiện nghiêm trọng gần nhất. Bản tóm tắt không chứa nhật ký, mật khẩu hay nội dung tệp.

Máy chủ gửi đi, chứ tài khoản không hỏi tới. Vì thế không cần mở lối vào mạng của bạn từ bên ngoài, và một panel bị đóng sau bộ lọc IP vẫn báo cáo bình thường. Nút mạng được nhận ra qua tên miền của panel, nên danh sách không bị nhân đôi sau khi khởi động lại hay đổi IP.
Một máy chủ bị đánh dấu là im lặng trong tài khoản nghĩa là bản tóm tắt đã lâu không đến. Hãy kiểm tra lần lượt: công tắc “Gửi bản tóm tắt”, dòng cron (sudo crontab -l) và câu trả lời của nút “Gửi ngay”. Lỗi gửi được ghi vào nhật ký ứng dụng với nhãn FLEET — ở đó sẽ thấy mã bị từ chối (HTTP 401) hay bản thân kết nối không thiết lập được.

Công cụ bảo mật

11. Tường lửa UFW

UFW (Uncomplicated Firewall) — giao diện đơn giản cho nftables/iptables. Nó chặn mọi cổng đến, trừ những cổng được cho phép rõ ràng. Trang “Tường lửa UFW” hiển thị trạng thái và các quy tắc.

sudo apt install ufw # Cho phép SSH (bắt buộc TRƯỚC khi bật!) và web sudo ufw allow OpenSSH sudo ufw allow 80,443/tcp # Chặn truy cập DB từ bên ngoài (chỉ truy cập nội bộ) sudo ufw deny 3306 # Bật và kiểm tra sudo ufw enable sudo ufw status verbose
Trước khi chạy ufw enable, hãy chắc chắn cho phép SSH (ufw allow OpenSSH), nếu không bạn sẽ mất quyền truy cập máy chủ.
“Mức phơi bày ra bên ngoài” trên dashboard có tính đến UFW: cổng bị chặn bởi quy tắc deny không được xem là truy cập được từ bên ngoài.
Skipping adding existing rule — đây không phải lỗi. Đó là cách UFW báo rằng quy tắc y hệt đã tồn tại và không thêm lại nó. Khi chạy lại tự động cấu hình (vốn có tính idempotent), đây là thông báo bình thường — không cần xử lý gì.

12. Cài đặt Fail2ban

Tự động chặn IP khi vượt quá số lần đăng nhập thất bại. Phân tích log của SSH, nginx, Apache và các dịch vụ khác.

sudo apt install fail2ban sudo systemctl enable --now fail2ban # Kiểm tra trạng thái: sudo fail2ban-client status
Cấu hình hoạt động (jail.local với hàng chục jail và tự động chặn từ ipsum) — ở phần tiếp theo.

13. Cấu hình hoạt động Fail2ban + ipsum

Cài đặt cơ bản đã ở trên. Ở đây là cấu hình thực tế cho hàng chục jail đang hoạt động và hàng nghìn lượt chặn: thiết lập chung, các jail chính và tự động chặn IP độc hại từ danh sách ipsum.

Tệp /etc/fail2ban/jail.local — thiết lập chung và các jail quan trọng nhất:

[DEFAULT] bantime = 1w findtime = 900 maxretry = 3 backend = systemd banaction = nftables-multiport ignoreip = 127.0.0.1/8 ::1 <YOUR_IP> <TRUSTED_NETS> # Chặn lũy tiến: mỗi lần tái phạm — chặn lâu hơn bantime.increment = true bantime.factor = 2 bantime.maxtime = 5w bantime.rndtime = 300 [sshd] enabled = true maxretry = 5 bantime = -1 # chặn vĩnh viễn khi dò mật khẩu SSH findtime = 3600 # Kẻ tái phạm: ai bị chặn nhiều lần sẽ bị chặn vĩnh viễn [recidive] enabled = true logpath = /var/log/fail2ban.log banaction = %(banaction_allports)s bantime = -1 findtime = 86400 maxretry = 2 [http-get-dos] enabled = true maxretry = 100 findtime = 300 bantime = 1w # Dịch vụ web (apache-*, nginx-*, php-url-fopen, phpmyadmin-syslog): [nginx-http-auth] enabled = true port = http,https [apache-badbots] enabled = true port = http,https # … và các jail còn lại theo dịch vụ (dovecot, exim, postfix-sasl, # mysqld-auth, vsftpd, portscan, pam-generic) — enabled = true
Trong ignoreip nhớ nhập IP của bạn và các mạng tin cậy, nếu không bạn có thể tự chặn chính mình. Sau khi sửa: sudo fail2ban-client reload.

Tự động nạp danh sách chặn ipsum — vào cron của root (sudo crontab -e): level 1 (hơn 100 nghìn IP) được nạp vào tập ipsum và bị cắt ở tường lửa (chi tiết ở mục “Danh sách chặn IPset”):

# 04:00 — cập nhật ipset ipsum (level 1, độ bao phủ tối đa): 0 4 * * * /usr/local/bin/load-ipsum.sh >> /path/to/monitor/logs/cron.log 2>&1
Tập phải có tên ipsum — đây chính là tập mà dashboard đọc (thẻ “IPset ipsum”). Cấp độ: levels/1.txt — bao phủ tối đa, levels/3.txt — chính xác hơn (từ 3 nguồn trở lên).

Vì sao “Màn hình giám sát an ninh” được chia thành hai vùng. Việc bảo vệ hoạt động ở hai cấp độ và dashboard không trộn lẫn chúng:

  • Tấn công thực tế (phản ứng) — tất cả những gì fail2ban bắt được: các nỗ lực xâm nhập thực (jail sshd, apache-*, nginx-* v.v.) và kẻ tái phạm nguy hiểm (jail recidive — những kẻ đã bị chặn nhiều lần). Đây là các IP thực sự đã tấn công vào bạn — chúng hiện trên bản đồ tấn công và “Dòng thời gian”.
  • Chặn phòng ngừa (chủ động) — danh sách chặn công khai gồm các IP độc hại đã biết ipset ipsum, bị cắt ở tường lửa bằng quy tắc DROP. Phần lớn các địa chỉ này còn chưa hề chạm tới máy chủ của bạn — chúng bị chặn từ trước; bộ đếm “IPset ipsum” cho biết đã cắt được bao nhiêu địa chỉ theo cách phòng ngừa.

Khác biệt rất đơn giản: phản ứng — “những kẻ này đã tấn công và bị chặn”, phòng ngừa — “những kẻ này bị chặn trước cả khi ra tay”. Trước đây list-3 ipsum bị nhồi giả tạo vào recidive (từ đó có cách chia cũ “recidive theo danh sách”); nay recidive chỉ gồm kẻ tái phạm thực sự, còn phòng ngừa hoàn toàn nằm ở tường lửa.

14. Danh sách chặn IPset (ipsum)

ipsum — danh sách IP độc hại công khai, cập nhật hàng ngày. Monitor hiển thị số địa chỉ đã tải trên dashboard và bản đồ tấn công, đồng thời tính vào Điểm bảo mật (−10 nếu tập chưa được tải).

Phương án tối giản không dùng fail2ban — tập ipsum riêng với việc chặn qua iptables:

# Tạo tập (một lần duy nhất): sudo ipset create ipsum hash:ip # Script cập nhật /usr/local/bin/update-ipsum.sh: #!/bin/bash ipset flush ipsum for ip in $(curl -s https://raw.githubusercontent.com/stamparm/ipsum/master/levels/3.txt); do ipset add ipsum "$ip" 2>/dev/null done iptables -C INPUT -m set --match-set ipsum src -j DROP 2>/dev/null \ || iptables -I INPUT -m set --match-set ipsum src -j DROP # Cron (sudo crontab -e, hàng ngày lúc 4:00): 0 4 * * * /usr/local/bin/update-ipsum.sh
Phương án mở rộng với fail2ban-recidive — xem mục “Cấu hình hoạt động Fail2ban + ipsum”.
ipset nằm trong bộ nhớ và mất khi khởi động lại. Chỉ riêng cron hàng ngày sẽ để tập rỗng từ lúc reboot đến lần chạy tiếp theo (dashboard hiển thị 0). Hãy nạp tập cả khi khởi động — tách phần nạp thành script và gắn vào @reboot. Đồng thời lệnh create … -exist đặt giới hạn maxelem 300000 (mặc định 65536 — level 1 không vừa, sẽ báo “Hash is full”):
# /usr/local/bin/load-ipsum.sh #!/bin/bash curl -s https://raw.githubusercontent.com/stamparm/ipsum/master/levels/1.txt \ | grep -v '^#' | sed 's/^/add ipsum /' \ | (echo "create ipsum hash:ip hashsize 131072 maxelem 300000 -exist"; echo "flush ipsum"; cat) \ | ipset restore -exist # sudo crontab -e — hàng ngày lúc 04:00 VÀ mỗi lần khởi động: 0 4 * * * /usr/local/bin/load-ipsum.sh @reboot sleep 60 && /usr/local/bin/load-ipsum.sh
Cơ chế khi cài đặt tự động. Script nạp toàn bộ danh sách level 1 (hơn 100 nghìn IP) vào tập ipsum và, nếu tường lửa do trình cài đặt quản lý (VPS mới — profile “Đầy đủ”/“Rút gọn”), gắn tập vào UFW bằng quy tắc DROP — lưu lượng từ các IP này thực sự bị chặn. Quy tắc nằm sau ESTABLISHED,RELATED, nên các kết nối hiện tại (kể cả SSH của bạn) không bị ngắt — chỉ chặn các kết nối mới từ danh sách. Tập được khôi phục khi dịch vụ ipsum-load.service khởi động trước tường lửa (nếu không UFW sẽ không lên được), và được cron cập nhật lúc 04:00. Trên máy chủ đã cấu hình sẵn (dashboard, tường lửa riêng), trình cài đặt không đụng vào tường lửa — ở đó ipsum vẫn là danh sách cho dashboard và bản đồ tấn công, còn quy tắc DROP nếu muốn thì thêm thủ công (phương án tối giản với iptables … --match-set ipsum … -j DROP — ở trên). Khi cài đặt tự động thì không cần làm gì thủ công.

15. Cài đặt CrowdSec

Giải pháp thay thế hiện đại cho Fail2ban với threat intelligence cộng đồng: chặn từ cộng đồng cùng với luật riêng của bạn. Cần một bouncer riêng để áp dụng lệnh chặn vào tường lửa.

curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash sudo apt install crowdsec sudo systemctl enable --now crowdsec # Bouncer cho iptables/nftables: sudo apt install crowdsec-firewall-bouncer-iptables # Kiểm tra trạng thái: sudo systemctl status crowdsec sudo cscli decisions list sudo cscli bouncers list
Trạng thái “Chưa chạy” trong bảng điều khiển = dịch vụ đã cài nhưng dịch vụ chưa hoạt động (monitor kiểm tra qua systemctl is-active crowdsec). Khởi động: sudo systemctl enable --now crowdsec; nếu bị lỗi hãy xem sudo journalctl -u crowdsec -n 30. Áp dụng tương tự cho mọi dịch vụ có trạng thái “Chưa chạy” (Suricata, Falco, Monit, MySQL).
“0 kịch bản” hoặc “0 bouncer” trên dashboard. CrowdSec mặc định gần như trống rỗng — không có collection thì nó không phát hiện gì, và không có bouncer đã đăng ký thì lệnh chặn không được áp dụng vào tường lửa. Hãy cài các collection cơ bản và đảm bảo bouncer có trong danh sách:
# Collection cơ bản (Linux + SSH + máy chủ web): sudo cscli collections install crowdsecurity/linux crowdsecurity/sshd crowdsecurity/base-http-scenarios sudo systemctl reload crowdsec # Bouncer phải có trong danh sách và ở trạng thái kết nối hoạt động: sudo cscli bouncers list
Trong log của bouncer có stream halted / lệnh chặn không được áp dụng. Đây là api-key mồ côi: bouncer đã bị xóa khỏi cscli bouncers list, nhưng key cũ của nó vẫn còn trong /etc/crowdsec/bouncers/*.yaml. Hãy đăng ký lại bouncer và ghi key mới vào:
sudo cscli bouncers add fw-bouncer # sẽ xuất ra api_key mới # ghi key này vào api_key: trong /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml sudo systemctl restart crowdsec-firewall-bouncer
Cài đặt tự động (hồ sơ “Bảo vệ toàn diện”) sẽ tự cài các collection và đăng ký firewall-bouncer — chỉ cần làm thủ công khi cài đặt thủ công hoặc sau khi can thiệp thủ công vào CrowdSec.

16. Cài đặt AIDE

AIDE (Advanced Intrusion Detection Environment) tạo ảnh chụp hệ thống tập tin và mỗi lần kiểm tra sẽ báo cáo các thay đổi trong /etc, /bin, /usr. Sau khi cài đặt bắt buộc phải khởi tạo cơ sở dữ liệu (aideinit).

sudo apt install aide # Khởi tạo cơ sở dữ liệu (5–15 phút): sudo aideinit sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db # Ubuntu 24.04: thư mục /var/lib/aide được tạo ở chế độ 700 (chủ sở hữu _aide), # và dashboard (www-data) không thấy cơ sở dữ liệu → hiển thị “Chưa khởi tạo”. # Mở quyền truy cập vào thư mục (các tập tin cơ sở dữ liệu vẫn giữ 600): sudo chmod 755 /var/lib/aide # Lần kiểm tra đầu tiên CÓ GHI vào log mà monitor đọc. # Trên Ubuntu/Debian aide cần --config rõ ràng (nếu không sẽ báo “missing configuration”; # bản binary aide.wrapper không còn được cung cấp trong các phiên bản mới): sudo bash -c 'aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1'
Trong lúc aideinit terminal sẽ đứng ở dòng Running aide --init... trong 5–15 phút — đây là bình thường (băm toàn bộ hệ thống tập tin, tải nặng lên đĩa). Đừng ngắt bằng Ctrl+C. Nếu tiến trình “treo” mà không hiển thị gì — có thể nó đang chờ trả lời một yêu cầu ẩn Overwrite existing aide.db.new [Yn]? (nhấn Y). Kiểm tra hoạt động từ một phiên khác: pgrep -af aide.
Lỗi aideinit: “21_aide_spamassassin … printf: invalid number” (return code 20) — lỗi đã biết của đoạn cấu hình AIDE trong Ubuntu 22.04. Cơ sở dữ liệu không được tạo. Hãy chuyển đoạn snippet bị lỗi ra ngoài rồi thử lại:
sudo mv /etc/aide/aide.conf.d/21_aide_spamassassin /etc/aide/21_aide_spamassassin.disabled sudo aideinit sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Trạng thái trên dashboard. “Chưa khởi tạo” = panel không thấy tập tin cơ sở dữ liệu: hoặc aideinit chưa chạy, hoặc (Ubuntu 24.04) thư mục /var/lib/aide được tạo ở chế độ 700 và www-data không truy cập được — khắc phục bằng sudo chmod 755 /var/lib/aide (xem khối trên). “Chưa có lần kiểm tra nào” = đã có cơ sở dữ liệu nhưng chưa chạy kiểm tra — đây không phải lỗi. Monitor đọc kết quả từ /var/log/aide/aide.log.
Kiểm tra định kỳ → log cho panel. /etc/cron.daily/aide mặc định trên Ubuntu/Debian mới có thể không ghi /var/log/aide/aide.log đúng định dạng (và aide.wrapper cũng không còn trong đó). Đáng tin cậy hơn là thêm cron riêng với --config rõ ràng — nó ghi log dưới quyền root ở chế độ 644, và monitor đọc được mà không cần nhóm bổ sung:
# sudo crontab -e — kiểm tra hằng ngày lúc 02:00: 0 2 * * * /usr/bin/aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1 # Chạy ngay bây giờ, không cần đợi lịch: sudo bash -c 'aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1'
Cài đặt tự động đã làm toàn bộ những việc này: chmod 755 /var/lib/aide và cron kiểm tra lúc 02:00 — không cần làm thủ công gì cả.
Hãy thực hiện khởi tạo lần đầu trên server sạch — trước khi cài các ứng dụng web. Sau các thay đổi hợp lệ, hãy tạo lại cơ sở dữ liệu: sudo aideinit && sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db.

17. Cài đặt ClamAV

Trình quét diệt virus cho Linux. Đặc biệt hữu ích để kiểm tra /var/www tìm PHP-shell và mã độc.

sudo apt install clamav clamav-daemon sudo systemctl enable --now clamav-daemon # Cập nhật cơ sở dữ liệu chữ ký: sudo freshclam # Quét thư mục thủ công: sudo clamscan -r /var/www --infected
Daemon clamd báo “Không hoạt động” sau enable --now? Ba nguyên nhân thường gặp:

1. Trong cấu hình còn dòng Example — clamd không chịu khởi động khi dòng này còn:

sudo sed -i '/^Example/d' /etc/clamav/clamd.conf sudo systemctl restart clamav-daemon

2. Chưa tải cơ sở dữ liệu chữ ký — clamd không khởi động khi thiếu nó:

sudo systemctl stop clamav-freshclam sudo freshclam sudo systemctl start clamav-freshclam sudo systemctl restart clamav-daemon

3. Chỉ đang tải — clamd nạp ~8 triệu chữ ký vào bộ nhớ mất 30–60 giây. Hãy đợi và kiểm tra: systemctl is-active clamav-daemon (trạng thái activating → vẫn đang tải).

Chẩn đoán: sudo journalctl -u clamav-daemon -n 30 --no-pager.
Trên dashboard hiện “Số tệp đã quét: 0” / “Lần quét cuối: —”? Daemon clamd chỉ giữ chữ ký trong bộ nhớ, bản thân nó không quét theo lịch. Bảng điều khiển hiển thị kết quả quét theo lịch, nên cần một cron để quét và ghi log. Trình tự động cài đặt sẽ đặt wrapper /usr/local/bin/clamav-scan.sh và cron lúc 01:30 — sau lần chạy đầu tiên, “Số tệp đã quét” và “Lần quét cuối” sẽ được điền. Chạy ngay không cần đợi lịch: sudo /usr/local/bin/clamav-scan.sh.

18. Cài đặt Linux Malware Detect (maldet)

Linux Malware Detect (LMD) — trình quét phần mềm độc hại chuyên về các mối đe dọa web: PHP-shell, backdoor web, trình tải mã độc. Dùng engine ClamAV và bổ sung thêm chữ ký riêng.

wget https://www.rfxn.com/downloads/maldetect-current.tar.gz tar -xzf maldetect-current.tar.gz dir=$(ls -d maldetect-*/ | head -1) && cd "$dir" && sudo bash install.sh && cd ~ rm -rf maldetect-* maldetect-current.tar.gz # Cập nhật chữ ký: sudo maldet -u # Quét /var/www: sudo maldet -a /var/www
LMD và ClamAV phối hợp rất tốt với nhau. Báo cáo gần nhất: maldet --report.
Khi cài đặt có thể xuất hiện dòng update-rc.d: error: unable to read /etc/init.d/maldet — dòng này vô hại. maldet không dùng init.d, việc cập nhật chữ ký và quét được chạy qua /etc/cron.daily/maldet. Nếu bên dưới thấy installation completed thì mọi thứ đã cài xong.
Trên trang, LMD hiện “Chưa cài đặt” dù đã cài? maldet không được cài qua apt mà nằm ở /usr/local/maldetect, và khi bật open_basedir thì sự tồn tại của nó được kiểm tra qua shell — xem mục “Trang trống dù dữ liệu đã có trên máy chủ”.

19. Cài đặt Suricata

Hệ thống phát hiện xâm nhập mạng: phân tích lưu lượng ở cấp độ gói tin và biết hàng nghìn chữ ký tấn công. Bổ sung cho ModSecurity (ModSecurity hoạt động ở cấp HTTP, còn Suricata ở cấp TCP/IP).

sudo add-apt-repository ppa:oisf/suricata-stable sudo apt update && sudo apt install suricata # Tải các quy tắc mới nhất: sudo suricata-update sudo systemctl enable --now suricata
Suricata “Đang chạy” nhưng bảng điều khiển không hiển thị cảnh báo / số sự kiện là 0? Suricata ghi /var/log/suricata/eve.json dưới quyền root với chế độ 750 cho thư mục, nên máy chủ web (www-data) không đọc được. Hãy mở quyền truy cập thư mục — các tệp bên trong vẫn được bảo vệ:
sudo chmod o+rx /var/log/suricata
Cài đặt tự động sẽ tự làm việc này — không cần thao tác thủ công.

20. Cài đặt Falco

Chặn bắt các lời gọi hệ thống qua eBPF/kernel module và phát hiện bất thường theo thời gian thực: shell từ nginx, tiến trình web đọc /etc/passwd, ghi vào /bin, v.v.

curl -fsSL https://falco.org/repo/falcosecurity-packages.asc > /tmp/falco.asc gpg --dearmor < /tmp/falco.asc | sudo tee /usr/share/keyrings/falco-archive-keyring.gpg > /dev/null sudo chmod 644 /usr/share/keyrings/falco-archive-keyring.gpg && rm /tmp/falco.asc echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" \ | sudo tee /etc/apt/sources.list.d/falcosecurity.list sudo apt update && sudo apt install falco sudo systemctl enable --now falco
Monitor đọc sự kiện Falco qua journalctl -u falco (không cần sudo — thông qua nhóm systemd-journal). Hãy đảm bảo www-data thuộc nhóm này — xem “Cấu hình sudo” (mục 2) ở trang cài đặt thủ công.
“0 sự kiện trong 24 giờ” là bình thường, không phải lỗi. Falco hoạt động theo sự kiện: nó im lặng khi mọi thứ ổn, và chỉ ghi sự kiện khi có bất thường (shell từ tiến trình web, đọc /etc/passwd, ghi vào thư mục hệ thống). Không có sự kiện nghiêm trọng nào trong một ngày trên máy chủ yên tĩnh là trạng thái khỏe mạnh.
Với dashboard, xuất ra file đáng tin cậy hơn. Đọc qua journalctl đòi hỏi quyền truy cập nhật ký; để dashboard thấy sự kiện ổn định, quá trình tự cài đặt bật file_output cho Falco → /var/log/falco/falco.log và đặt UMask=0022 cho dịch vụ (log được web server đọc). Trên bản cài đặt mới, không cần cấu hình thủ công điều này.

21. Cài đặt ModSecurity (WAF)

ModSecurity — tường lửa web (WAF) cho Apache hoặc Nginx. Chặn các cuộc tấn công ở tầng ứng dụng: SQL injection, XSS, vượt đường dẫn, trình quét.

# Apache: sudo apt install libapache2-mod-security2 sudo a2enmod security2 # Bộ quy tắc OWASP Core Rule Set: sudo apt install modsecurity-crs # BẮT BUỘC: không có file này thì bộ máy quy tắc bị tắt sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf sudo sed -i 's/^SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf sudo apache2ctl configtest && sudo systemctl reload apache2 # Kiểm tra: phải trả về 403 curl -s -o /dev/null -w '%{http_code}\n' "https://monitor.example.com/?id=1%20UNION%20SELECT%201,2--"
Chỉ cài gói thôi thì chưa bảo vệ được gì. Apache nạp cấu hình bằng dòng IncludeOptional /etc/modsecurity/*.conf, còn gói chỉ đặt modsecurity.conf-recommended — file này không khớp mẫu *.conf. Nếu không sao chép nó thành modsecurity.conf, SecRuleEngine vẫn ở trạng thái Off: module đã nạp, quy tắc CRS đã nạp, nhưng lưu lượng không được kiểm tra và không tạo audit log. Chế độ trung gian DetectionOnly chỉ ghi sự kiện vào log mà không chặn yêu cầu — dashboard hiển thị nó màu vàng.

Quyền truy cập audit log của dashboard. Log /var/log/apache2/modsec_audit.log thuộc quyền root (quyền 640), người dùng web không đọc được. Dashboard lấy dữ liệu qua một lớp bọc — hãy tạo nó:

sudo tee /usr/local/bin/monitor-modsec >/dev/null <<'EOF' #!/bin/sh echo "ENGINE=$(grep -hE '^SecRuleEngine[[:space:]]+' /etc/modsecurity/*.conf 2>/dev/null | tail -1 | awk '{print $2}')" echo "---LOG---" tail -n 3000 /var/log/apache2/modsec_audit.log 2>/dev/null echo "---RULES---" for f in /etc/modsecurity/crs/rules/*.conf /usr/share/modsecurity-crs/rules/*.conf /etc/modsecurity/custom-rules.conf; do [ -f "$f" ] && { echo "===FILE:$(basename "$f")==="; cat "$f"; } done EOF sudo chown root:root /usr/local/bin/monitor-modsec sudo chmod 755 /usr/local/bin/monitor-modsec # trong /etc/sudoers.d/monitor (người dùng = người mà PHP-FPM chạy dưới quyền đó): # www-data ALL=(ALL) NOPASSWD: /usr/local/bin/monitor-modsec
Lớp bọc lấy chỉ thị SecRuleEngine cuối cùng không có thụt lề: các dòng có thụt lề nằm bên trong khối <LocationMatch>/<Directory> (ví dụ tắt WAF cho phpMyAdmin) và không quyết định chế độ toàn cục.
Người dùng trong sudoers phải trùng với người dùng của FPM pool: trên Apache/Debian thông thường đó là www-data, còn trong HestiaCP pool của site chạy dưới quyền chủ site (ví dụ admin) — hãy kiểm tra grep -E '^user' /etc/php/*/fpm/pool.d/monitor.example.com.conf.
Nếu site đứng sau proxy Nginx (HestiaCP), Apache thấy client chính là proxy — dashboard lấy IP thật của kẻ tấn công từ header X-Forwarded-For. Chỉ những giao dịch có quy tắc kích hoạt mới được đưa vào thống kê: chỉ thị SecAuditLogRelevantStatus ghi vào audit log mọi phản hồi 4xx/5xx, nên các lỗi 403/500 thông thường cũng lọt vào đó — dashboard không tính chúng là sự kiện WAF.
Khối ---RULES--- cần cho mục “Tất cả quy tắc đang hoạt động” — dashboard hiển thị không chỉ những quy tắc đã kích hoạt mà toàn bộ quy tắc CRS đã nạp + quy tắc tùy chỉnh. Ba đường dẫn trong vòng lặp for f in … là các vị trí điển hình cho quy tắc CRS và phần bổ sung cục bộ; nếu bố trí của bạn khác (gói đặt file vào thư mục riêng, hoặc quy tắc tùy chỉnh không nằm ở /etc/modsecurity/custom-rules.conf), hãy tìm đường dẫn thật bằng lệnh sudo grep -rl 'IncludeOptional\|^Include ' /etc/apache2/mods-enabled/security2.conf /etc/apache2/conf-enabled/*.conf 2>/dev/null rồi thay chúng vào danh sách. Nếu lớp bọc cũ (không có phần này) — mục đó chỉ hiển thị cảnh báo “không khả dụng”, phần còn lại của trang vẫn hoạt động như trước.

22. Cài đặt Auditd

Auditd (Linux Audit Daemon) ghi lại các lệnh gọi hệ thống ở cấp nhân: đăng nhập và đăng xuất, lệnh sudo, các lần xác thực thất bại, thay đổi tệp. Monitor hiển thị lượt đăng nhập, các lần thất bại và lệnh sudo trong ngày hôm nay.

sudo apt install auditd audispd-plugins sudo systemctl enable --now auditd # Kiểm tra trạng thái và sự kiện: sudo systemctl status auditd sudo ausearch -m USER_LOGIN -ts today
Monitor đọc sự kiện qua ausearch (/usr/sbin/ausearch) và khi cần thì từ /var/log/audit/audit.log bằng lệnh tail. Cả hai phải có trong sudoers.

23. Cài đặt Monit

Theo dõi các dịch vụ (nginx, php-fpm, mysql, v.v.) và tự khởi động lại khi chúng ngừng chạy. Có thể gửi cảnh báo qua email.

sudo apt install monit sudo systemctl enable --now monit # Tệp cấu hình: sudo nano /etc/monit/monitrc ls /etc/monit/conf.d/
Monitor lấy danh sách dịch vụ qua monit status. Trong /etc/monit/monitrc phải bật giao diện HTTP (khối set httpd với allow localhost), nếu không monit status sẽ báo lỗi.
Trên dashboard hiện “0 dịch vụ đang được theo dõi”? Có hai nguyên nhân. (1) Giao diện HTTP đang tắt — trong monitrc dòng set httpd bị chú thích (mặc định ở dạng # set httpd port 2812 …). Hãy bỏ chú thích khối đó và cho phép localhost. (2) Bản thân httpd được bật không theo dõi gì cả — Monit chỉ đếm những gì được mô tả trong các stanza check; thiếu chúng thì danh sách vẫn trống dù giao diện đang chạy. Cấu hình tối thiểu hoạt động được:
# /etc/monit/conf.d/00-httpd — giao diện HTTP cho localhost: set httpd port 2812 use address localhost allow localhost # ví dụ các stanza check (những gì cần theo dõi): check process sshd with pidfile /run/sshd.pid start program = "/usr/bin/systemctl start ssh" stop program = "/usr/bin/systemctl stop ssh" check filesystem rootfs with path / if space usage > 90% then alert
sudo monit -t # kiểm tra cú pháp (Control file syntax OK) sudo systemctl reload monit sudo monit status
Trình cài đặt tự động đặt sẵn conf.d với httpd trên cổng 2812 và một bộ kiểm tra — trên bản cài mới không cần cấu hình thủ công.
Dịch vụ ở trạng thái “Có lỗi”? Monitor chỉ hiển thị trạng thái và cố ý không khởi động lại dịch vụ từ bảng điều khiển web (điều đó sẽ là thực thi lệnh root từ xa ngay trong panel bảo mật). Chẩn đoán và khởi động lại — qua SSH bằng Monit:
sudo monit status <service> # nguyên nhân lỗi sudo monit restart <service> # khởi động lại qua Monit # nếu Monit không dựng được dịch vụ — xem unit riêng của nó: sudo systemctl status <unit> --no-pager sudo journalctl -u <unit> -n 50 --no-pager

24. Cài đặt PSAD (phát hiện quét cổng)

PSAD phân tích nhật ký iptables và phát hiện việc quét cổng cùng các cuộc tấn công mạng, gán cho mỗi nguồn một mức độ nguy hiểm (1–5). Bổ trợ cho fail2ban và Suricata.

sudo apt install psad # PSAD đọc log iptables — cần bật ghi log (UFW tự làm việc này). # Với iptables thuần, hãy thêm quy tắc LOG vào chuỗi INPUT/FORWARD. sudo psad --sig-update sudo systemctl enable --now psad
Monitor đọc dữ liệu qua psad --Status (cần có trong sudoers). Nếu không bật ghi log iptables, trang sẽ trống — điều này là bình thường khi chưa có lần quét nào.

25. AppArmor / SELinux (kiểm soát truy cập)

Mandatory Access Control giới hạn những tệp và tài nguyên mà một chương trình có thể truy cập, ngay cả khi nó đã bị xâm nhập. Trên Ubuntu/Debian, AppArmor được dùng mặc định (thường đã được cài sẵn và đang hoạt động).

# AppArmor (Ubuntu/Debian): sudo apt install apparmor apparmor-utils sudo systemctl enable --now apparmor sudo aa-status # kiểm tra các profile
Monitor đọc trạng thái qua aa-status (cần có trong sudoers). Nó hiển thị số profile ở chế độ enforce/complain và các tiến trình không có profile.

“Số profile đã nạp” nhiều hơn enforce + complain là điều bình thường. Trong AppArmor 4.x (Ubuntu 24.04 trở lên) có thêm chế độ unconfined: profile được nạp vào nhân nhưng không giới hạn gì cả. Ubuntu gắn nhãn này cho hàng chục profile của các chương trình dùng user namespaces (trình duyệt, ứng dụng torrent và tương tự). Khi có những profile như vậy, thẻ “Số profile đã nạp” chuyển sang màu hổ phách và hiển thị số lượng của chúng — ví dụ unconfined: 90 trong tổng số 120 profile đã nạp và 26 ở chế độ enforce. Chỉ những profile ở enforce mới thực sự bảo vệ; trên Ubuntu 22.04 (AppArmor 3.x) không có chế độ này và các con số luôn khớp nhau.

sudo aa-status | grep -E "profiles are" # phân tách theo chế độ sudo aa-enforce /etc/apparmor.d/profile-name # chuyển profile sang enforce
Chỉ nên chuyển sang enforce những profile mà Ubuntu cố ý để ở unconfined khi bạn thực sự hiểu rõ: chúng bị tắt không phải do lỗi, mà vì nếu không thì chính các chương trình đó sẽ hỏng. Còn các profile ở complain là chuyện khác: quy tắc ở đó đã được viết sẵn và chỉ là chưa được áp dụng mà thôi.

26. Cài đặt debsums (kiểm tra tính toàn vẹn gói)

debsums kiểm tra xem tệp của các gói đã cài có khớp với giá trị băm từ kho lưu trữ không — phát hiện các tệp nhị phân hệ thống bị thay thế (bổ sung cho AIDE). Quét đầy đủ mất 1–2 phút nên chạy qua cron, còn dashboard đọc kết quả từ data/debsums/debsums.log và tự phân loại (chỉ tệp nhị phân và thư viện là quan trọng).

Tác vụ nằm trong cron của root (sudo crontab -e). Trình bao bọc debsums-scan.sh có sẵn được đặt vào /usr/local/bin/ (chmod +x; xem bảng tóm tắt các tác vụ cron) và tự ghi báo cáo vào data/debsums/ của dashboard.

sudo apt install debsums # Dòng cron (hằng ngày lúc 4:30): 30 4 * * * /usr/local/bin/debsums-scan.sh >> /path/to/monitor/logs/cron.log 2>&1

Trình bao bọc debsums-scan.sh tự tìm data/ của dashboard — không cần khai báo đường dẫn.

Thay đổi trong /etc/ (tệp cấu hình) và /usr/share/ (tài nguyên) trên máy chủ thường là bình thường — dashboard đánh dấu chúng bằng màu riêng. Đáng lo là thay đổi ở tệp nhị phân và thư viện (/bin, /sbin, /usr/lib v.v.) — thẻ “Tệp nhị phân / thư viện” hiển thị đúng những thay đổi này.

27. Cấu hình báo cáo Lynis

Lynis chạy thủ công hoặc theo cron. Báo cáo phải được lưu vào thư mục data/lynis/ của dự án — monitor đọc tệp lynis-report.dat.

# Chạy một lần (thay bằng đường dẫn tới thư mục gốc của bảng điều khiển): sudo lynis audit system --report-file /path/to/monitor/data/lynis/lynis-report.dat # Kiểm tra hằng ngày — dòng cron (trình bao lynis-scan.sh sẵn có trong /usr/local/bin/, xem bản tóm tắt): 0 3 * * * /usr/local/bin/lynis-scan.sh >> /path/to/monitor/logs/cron.log 2>&1
Sau lần chạy đầu tiên, trang “Audit Lynis” sẽ hiển thị ngay hardening index, các cảnh báo và khuyến nghị.
Nút “Chạy kiểm tra” trên trang Lynis. Nút này chạy lynis-scan.sh ở chế độ nền ngay từ bảng điều khiển (không cần đợi cron): hiển thị “Đang quét…” và tự cập nhật báo cáo khi hoàn tất. Để làm được điều này, người dùng web cần một dòng sudoers để chạy script — trình cài đặt tự động thêm nó vào /etc/sudoers.d/monitor. Nếu bảng điều khiển được cài thủ công/trước đây, hãy thêm dòng đó với cùng người dùng đã ghi trong tệp:
u=$(sudo awk '/NOPASSWD/ && !/lynis-scan/ {print $1; exit}' /etc/sudoers.d/monitor) [ -n "$u" ] && echo "$u ALL=(ALL) NOPASSWD: /usr/local/bin/lynis-scan.sh" | sudo tee -a /etc/sudoers.d/monitor sudo visudo -c && sudo chmod 440 /etc/sudoers.d/monitor

28. Cấu hình báo cáo Logwatch

Logwatch phải lưu báo cáo hằng ngày vào thư mục data/logwatch/ của dự án ở định dạng .txt. Monitor hiển thị báo cáo mới nhất và kho lưu trữ.

# Hằng ngày (6:00) — dòng cron (trình bao logwatch_daily.sh có sẵn trong /usr/local/bin/, xem phần tóm tắt): 0 6 * * * /usr/local/bin/logwatch_daily.sh >> /path/to/monitor/logs/cron.log 2>&1

Mô-đun dashboard

29. Giám sát mạng (tích hợp sẵn)

Giám sát mạng không cần cài đặt — đây là một trang tích hợp sẵn trong bảng điều khiển. Nó hiển thị trạng thái mạng của máy chủ từ các nguồn cục bộ:

  • giao diện và lưu lượng — từ /proc/net/dev;
  • trạng thái liên kết (UP/DOWN) và IP — qua ip;
  • kết nối và cổng đang lắng nghe — qua ss;
  • sự kiện mạng của nhân trong 24 giờ — qua journalctl -k.

Ba nguồn đầu hoạt động không cần sudo, nên giao diện, lưu lượng, kết nối và cổng hiển thị ngay lập tức. Khối “Sự kiện nhân” dùng journalctl -k — được đọc qua nhóm systemd-journal (“Cấu hình sudo”, mục 2), không cần sudo. Kiểm tra rằng người dùng web truy cập được mọi thứ:

# Kiểm tra dưới danh nghĩa www-data (PHP chạy bằng người dùng này): sudo -u www-data bash -c 'cat /proc/net/dev' sudo -u www-data bash -c 'ip -o link show' sudo -u www-data bash -c 'ss -s' sudo -u www-data journalctl -k --no-pager -n 5
Khối “Sự kiện mạng của nhân” hiển thị các sự kiện của ngăn xếp mạng nhân (thay đổi liên kết up/down, lỗi sóng mang, “network unreachable”). Các bản ghi tường lửa UFW BLOCK không xuất hiện ở đây — chúng nằm ở trang “Tường lửa UFW” và “Bản đồ tấn công”. Khối trống với dấu tích xanh = suốt ngày qua không có sự cố mạng nào.

30. Đĩa và SMART

Trang tích hợp hiển thị ba nội dung:

  • Hệ thống tệp — mức đầy của phân vùng (df); thanh chuyển đỏ khi ≥90%;
  • Ổ lưu trữ — danh sách đĩa (lsblk), chỉ đĩa thật (loop/snap được ẩn);
  • Tình trạng (SMART) — trạng thái và thuộc tính của đĩa (smartctl).

Dung lượng và danh sách thiết bị hoạt động ngay, không cần thiết lập. SMART cần gói smartmontools. Tiến trình web không truy cập trực tiếp được vào thiết bị đĩa, nên SMART được thu thập bằng cron vào tệp data/disk/smart.txt để dashboard đọc.

Tác vụ nằm trong cron của root (sudo crontab -e). Trình bao smart-scan.sh có sẵn được đặt vào /usr/local/bin/ (chmod +x; xem tổng hợp tác vụ cron) và tự ghi vào thư mục data/disk/ của dashboard.

sudo apt install smartmontools # Dòng cron (mỗi 30 phút): */30 * * * * /usr/local/bin/smart-scan.sh >> /path/to/monitor/logs/cron.log 2>&1

Trình bao smart-scan.sh tự tìm thư mục data/ của dashboard — không cần khai báo đường dẫn. Bên trong, lsblk -e7,11 loại trừ loop/cdrom.

Trên đĩa ảo (QEMU/KVM và tương tự) thường chỉ có trạng thái chung “tình trạng: OK”, còn nhiệt độ, giờ hoạt động và số sector đã phân bổ lại có thể trống — điều này là bình thường. Trên máy chủ vật lý sẽ hiển thị đầy đủ mọi thuộc tính.

31. Hiệu năng (CPU/RAM/Mạng/Đĩa)

Trang này hiển thị lịch sử tải của máy chủ trong 24 giờ gần nhất — Load Average, mức chiếm dụng CPU và thời gian chờ I/O, RAM/Swap, lưu lượng mạng (nhận/gửi), I/O đĩa (đọc/ghi), mức lấp đầy đĩa và inode, số file descriptor đang mở và kết nối MySQL, cùng số kết nối TCP và tiến trình hiện tại. Khung 24 giờ là mặc định: các nút phía trên chuyển sang 7 ngày hoặc 30 ngày.

Dữ liệu do cron/collect_metrics.php thu thập — cứ mỗi 5 phút ghi một ảnh chụp “thô” của các bộ đếm (/proc/loadavg, /proc/meminfo, /proc/stat, /proc/net/dev, /proc/diskstats, df/df -i, /proc/sys/fs/file-nr, SHOW GLOBAL STATUS LIKE 'Threads_connected') vào bảng CSDL system_metrics; phần trăm và tốc độ được trang tự tính từ chênh lệch giữa hai ảnh chụp liền kề (mức lấp đầy đĩa/inode/descriptor/kết nối MySQL là giá trị tức thời, không tính lại). Không cần sudo — các nguồn được đọc mà không cần quyền root.

Hai độ sâu lưu trữ. Các ảnh chụp “thô” năm phút sống được một ngày (cộng thêm một giờ dự phòng để điểm cũ nhất trên biểu đồ vẫn có điểm trước đó phục vụ tính toán). Mỗi giờ đã kết thúc được gộp thành một hàng; chuỗi theo giờ này được giữ 30 ngày và chính nó là thứ mà các chế độ xem “7 ngày” và “30 ngày” đọc. Nhờ vậy một tháng lịch sử chỉ tốn khoảng 720 hàng thay vì khoảng 8600.

Ở các khoảng 7 và 30 ngày, cái được hiển thị là giá trị trung bình theo giờ chứ không phải từng lần đo — một đỉnh kéo dài năm phút sẽ bị làm mượt ở tỷ lệ đó, và trang có nhắc điều này. Với Load Average, hàng theo giờ giữ cả giá trị trung bình lẫn đỉnh của giờ đó, nên biểu đồ hiện hai đường. Ngay sau khi cập nhật, các khoảng dài còn trống (“chưa có lịch sử cho khoảng này”) — chuỗi theo giờ bắt đầu tích lũy từ thời điểm đó.
# Dòng cron (mỗi 5 phút): */5 * * * * /usr/local/bin/collect-metrics-all.sh >> /path/to/monitor/logs/cron.log 2>&1

Trình bao bọc collect-metrics-all.sh (xem tóm tắt các tác vụ cron) tự tìm mọi phiên bản dashboard đã cài trên máy chủ và chạy cron/collect_metrics.php của từng cái dưới danh nghĩa chủ sở hữu trang web.

Cho đến khi trình thu thập chạy được ít nhất hai lần (khoảng 10 phút đầu sau khi cài đặt), trang sẽ hiển thị “đang thu thập dữ liệu” — biểu đồ cần tối thiểu một cặp điểm liền kề để tính tốc độ và phần trăm.

Tiến trình nặng nhất. Dưới các biểu đồ có hai bảng liệt kê năm tiến trình ngốn CPU nhiều nhất:

  • “Tiến trình ở lần đo gần nhất” — thứ đang làm nặng máy chủ vào lúc ảnh chụp cuối (thời điểm ghi ở tiêu đề thẻ);
  • “Tiến trình vào lúc cao điểm trong ngày” — cũng năm tiến trình đó, nhưng lấy từ lần đo có Load Average cao nhất trong 24 giờ qua. Đây chính là câu trả lời cho câu hỏi “đêm qua là cái gì vậy”, khi đỉnh trên biểu đồ đã qua từ lâu.

Cả hai bảng luôn mô tả 24 giờ gần nhất, ngay cả khi biểu đồ hiển thị cả tháng: ảnh chụp tiến trình chỉ có ở các hàng năm phút, mà những hàng đó không được giữ quá một ngày.

Các tiến trình của chính phép đo (ps, tập lệnh PHP thu thập và chuỗi cron/sudo phía trên nó) bị loại khỏi danh sách — nếu không chúng sẽ đứng đầu mọi ảnh chụp: ps ngay lúc khởi động cho thấy gần 100 % CPU. Còn các tác vụ cron khác chạy cùng phút thì vẫn xuất hiện, và đó chính là điều bảng này cần cho thấy.

Cảnh báo về tải (mục “Cài đặt” → “Cảnh báo về tải”) — khi ngưỡng CPU/RAM/đĩa/inode bị vượt, panel gửi thông báo qua tất cả các kênh đang bật (Telegram, Email, webhook, push — cùng những kênh của báo cáo hằng ngày; không cần bật riêng gì cho cảnh báo), rồi gửi thêm một thông báo nữa khi chỉ số trở lại bình thường. Trong lúc ngưỡng vẫn bị vượt, panel không gửi dồn dập: thông báo tiếp theo chỉ đến sau chu kỳ “đã trở lại bình thường → lại vượt ngưỡng”.

Các ngưỡng được kiểm tra bởi chính collect_metrics.php mỗi lần chạy (mỗi 5 phút) — không cần cron riêng. Trạng thái “đã thông báo / chưa” được lưu trong data/alerts_state.json, còn các ngưỡng nằm trong cài đặt của dashboard.

32. Sự kiện bảo mật (cảm biến 5 phút một lần)

Báo cáo hằng ngày trả lời câu hỏi “máy chủ đang thế nào”. Cảm biến trả lời một câu hỏi khác — “vừa có chuyện gì xảy ra”. Cứ 5 phút, nó hỏi các nguồn nhanh và chỉ ghi lại những gì đã thay đổi kể từ lần kiểm tra trước; trang “Sự kiện bảo mật” trong menu hiển thị danh sách các phát hiện.

Các kiểm tra chậm — Lynis, debsums, cập nhật gói, hạn của chứng chỉ SSL — cảm biến không đụng đến: dù sao con số của chúng cũng chỉ thay đổi mỗi ngày một lần và vẫn thuộc phần việc của báo cáo hằng ngày.

Nó nhận ra những gì — chín nguồn:

  • Fail2ban — các lệnh chặn mới (danh sách địa chỉ bị chặn được so sánh, nên việc bỏ chặn không phải là sự kiện, còn một lệnh chặn đang có hiệu lực chỉ được báo đúng một lần);
  • CrowdSec — các quyết định mới;
  • Falco — sự kiện mức Emergency, Alert và Critical;
  • Suricata — cảnh báo mới; dấu [Priority: 1] nâng phát hiện lên mức nghiêm trọng;
  • ClamAV — các dòng FOUND mới trong nhật ký quét;
  • AIDE — thay đổi tệp theo lần kiểm tra gần nhất (tức là không thường xuyên hơn nhịp mà nó tự chạy theo lịch riêng);
  • SSH — đăng nhập thành công dưới quyền root (nghiêm trọng) và đăng nhập từ một địa chỉ chưa từng gặp (cảnh báo);
  • Dịch vụ bảo vệ — UFW, Fail2ban, CrowdSec, Suricata, Falco: một thông báo khi dịch vụ dừng và một thông báo khi nó chạy lại;
  • Cổng — một cổng vừa trở nên truy cập được từ bên ngoài; nếu phía sau là cơ sở dữ liệu hay bộ nhớ đệm thì đó là mức nghiêm trọng.

Công cụ chưa được cài, hoặc có nhật ký mà panel không đọc được, đơn giản là im lặng — đó không phải lỗi và cũng không phải sự kiện.

Lần kiểm tra đầu tiên không gửi gì cả. Mỗi nguồn giữ một dấu “đã đọc đến đâu” (tệp data/secwatch_state.json), và lần chạy đầu chỉ ghi dấu đó. Nếu không, vừa bật theo dõi là bạn sẽ nhận thông báo về mọi thứ mà nhật ký đã tích lại suốt nhiều tháng.

Cấu hình — “Cài đặt” → “Theo dõi sự kiện”:

  • Bật theo dõi — chừng nào còn tắt thì tác vụ cron chạy không tải và danh sách không đầy lên;
  • Thông báo từ mức — “Cảnh báo” hoặc “Nghiêm trọng”. Phát hiện dưới ngưỡng vẫn vào danh sách nhưng không vào thông báo;
  • Khoảng nghỉ, phút — khoảng cách tối thiểu giữa hai thông báo, mặc định là 15;
  • Giờ yên lặng — một khung theo dạng 23-7, trong đó cảnh báo không được gửi đi.
Phát hiện nghiêm trọng được gửi ngay — cả khoảng nghỉ lẫn giờ yên lặng đều không giữ chúng lại: cảm biến 5 phút sinh ra chính là vì chúng. Dù vậy vẫn có cầu chì: không quá 6 thông báo mỗi giờ, phòng khi một nguồn nào đó bắt đầu “nhấp nháy”.

Thông báo đi theo đúng các kênh của báo cáo hằng ngày (Telegram, Email, webhook, push) — không cần bật riêng gì thêm. Mọi phát hiện đều được ghi vào danh sách bất kể thông báo có được gửi hay không, và được giữ trong 90 ngày.

Danh sách sự kiện. Bộ lọc: khoảng thời gian (24 giờ, 7, 30 hoặc 90 ngày), mức độ quan trọng và nguồn; hiển thị 500 bản ghi gần nhất. Nút “Kiểm tra ngay” chạy đúng lượt kiểm tra như cron — tiện ngay sau khi cấu hình, để khỏi phải chờ năm phút; nút này cũng nêu lý do khi không có thông báo nào được gửi (“dưới ngưỡng quan trọng”, “giờ yên lặng”, “chưa cấu hình kênh thông báo nào”). Bộ đếm màu đỏ cạnh mục menu là số sự kiện nghiêm trọng trong 24 giờ qua.

Thẻ địa chỉ. Nhấp vào một dòng trong danh sách sự kiện hoặc trong khối Địa chỉ liên quan sẽ mở ra mọi điều đã biết về một địa chỉ: trình tự thời gian các sự kiện của nó, quốc gia và ngay dòng đầu tiên là kết luận. Dòng trong danh sách mở thẻ khi sự kiện chỉ liên quan đến đúng một địa chỉ; nếu có nhiều địa chỉ thì chính các địa chỉ bên trong dòng là liên kết. Có bốn kết luận:

  • Có thể đã truy cập — đã có lần đăng nhập thành công từ địa chỉ này và chính địa chỉ đó cũng bị các lớp bảo vệ khác phát hiện, hoặc ngay sau đó AIDE, ClamAV đã kích hoạt hay một cổng mới được mở. Đây là trường hợp duy nhất đáng kiểm tra ngay;
  • Đăng nhập từ địa chỉ này — chỉ có một lần đăng nhập, không có gì khác. Thường đó là chủ sở hữu từ một nơi mới, vì vậy thẻ không được tô đỏ;
  • Đã chặn — địa chỉ đã bị bắt và chặn: lớp bảo vệ đã làm việc của nó, không cần làm gì;
  • Cần kiểm tra — có phát hiện nhưng không có chặn. Nếu địa chỉ vẫn quay lại, hãy tự chặn nó.

Dưới trình tự thời gian là khối Điều gì đã xảy ra trên máy chủ cùng lúc đó: những sự kiện hoàn toàn không có địa chỉ. AIDE không biết ai đã sửa tệp, ClamAV không biết ai mang tệp nhiễm vào. Chỉ hiển thị những sự kiện rơi vào khoảng hoạt động của địa chỉ và sáu giờ sau đó. Chính lần đăng nhập cùng với những gì xảy ra tiếp theo mới phân biệt một nỗ lực với quyền truy cập thực sự.

Mối liên hệ được dựng theo địa chỉ. Kẻ tấn công đổi địa chỉ sẽ cắt đứt mối liên hệ đó. Thẻ cho thấy những gì các lớp bảo vệ đã nhìn thấy, và không nhận trách nhiệm phân biệt một vụ xâm nhập với chính lần đăng nhập của bạn từ một nơi mới.

Lịch chạy. Tác vụ được đưa vào crontab của root (sudo crontab -e), lệch 2 phút so với việc thu thập số liệu để hai bên không khởi động cùng lúc:

# 5 phút một lần — kiểm tra sự kiện bảo mật 2-57/5 * * * * /usr/local/bin/security-watch-all.sh >> /path/to/monitor/logs/cron.log 2>&1
Chỉ chạy qua wrapper, đừng bao giờ gọi thẳng php cron/security_watch.php. Wrapper tìm tất cả bản cài của panel trên máy chủ và chạy phần kiểm tra dưới danh nghĩa chủ sở hữu trang — với đúng những quyền sudo mà panel dùng từ web. Gọi thẳng từ crontab của root sẽ tạo tệp trạng thái data/secwatch_state.json thuộc sở hữu root, và nút “Kiểm tra ngay” trong panel sẽ ngừng hoạt động.
Nếu máy chủ được chuẩn bị bằng cài đặt tự động thì tác vụ đã có sẵn — chỉ còn bật theo dõi trong cài đặt.

33. Máy chủ web: yêu cầu và mã phản hồi

Trang “Máy chủ web” phân tích nhật ký truy cập và cho thấy đã có bao nhiêu yêu cầu, bao nhiêu phần trong đó kết thúc bằng lỗi và ai đã gửi chúng. ModSecurity trả lời câu hỏi “cái gì đã bị chặn”, còn trang này trả lời “rốt cuộc thì cái gì đã đến được trang web”.

Những gì hiển thị: số yêu cầu, RPS, tỷ lệ lỗi, bộ đếm 5xx riêng và lượng dữ liệu đã gửi đi; biểu đồ yêu cầu theo giờ; phân bố các mã 2xx/3xx/4xx/5xx; địa chỉ hay gặp, IP hay gặp, lỗi hay gặp (mã cộng đường dẫn) và các máy khách (User-Agent). Khoảng thời gian: 24 giờ hoặc 7 ngày.

Nguồn là nhật ký truy cập ở định dạng combined, nên cả Nginx lẫn Apache đều phù hợp và không cần thêm mô-đun nào. Panel tìm nhật ký trong số các tệp tiêu chuẩn:

  • /var/log/nginx/access.log
  • /var/log/apache2/access.log
  • /var/log/apache2/other_vhosts_access.log
  • /var/log/httpd/access_log

Ngoài ra, panel còn kiểm tra các tệp *access*.log và *requests*.log trong /var/log/nginx/, /var/log/apache2/ và /var/log/httpd/. Nhờ vậy cũng tìm được nhật ký mà một trang ghi vào tệp riêng bằng chỉ thị CustomLog — ví dụ arciveo-monitor_requests.log do trình cài đặt Arcivéo tạo ra.

Chỉ một nhật ký được đọc: khi Nginx đứng trước Apache, cộng hai nhật ký lại sẽ đếm mỗi yêu cầu hai lần. Panel lấy nhật ký đầu tiên trong danh sách trên có được ghi trong một ngày gần nhất (các tệp tìm theo mẫu tên được thử sau đó, mới nhất trước); nếu suốt một ngày không tệp nào được ghi, panel lấy tệp không rỗng mới nhất. Vì thế, nhật ký bị logrotate làm rỗng hoặc còn sót lại từ một máy chủ web đã tắt không còn che mất nhật ký đang hoạt động. Phần cuối của nhật ký được đọc (3 MB) và kết quả được lưu đệm 5 phút — vì thế trên một trang có nhiều lưu lượng, khoảng thời gian đã chọn có thể không được phủ hết; phạm vi thực tế được ghi phía trên biểu đồ.

“Bảng điều khiển không đọc được nhật ký của máy chủ web”. Nguyên nhân hầu như luôn là quyền: các nhật ký và thư mục chứa chúng thuộc nhóm adm, còn người dùng web lại không thuộc nhóm này. Trình cài đặt Arcivéo thêm người dùng đó vào nhóm; sau khi cài đặt thủ công, một wrapper nhỏ của root chỉ được phép làm đúng một việc — in nhật ký ra — sẽ giải quyết vấn đề (trang ModSecurity cũng hoạt động như vậy). Wrapper chọn nhật ký theo cùng quy tắc với panel. Lệnh hiển thị ngay trên trang đó; đây cũng chính là lệnh ấy. Hãy dán toàn bộ — đây là một lệnh duy nhất:
sudo tee /usr/local/bin/monitor-weblog >/dev/null <<'EOF' && sudo chmod 755 /usr/local/bin/monitor-weblog && echo 'www-data ALL=(root) NOPASSWD: /usr/local/bin/monitor-weblog' | sudo tee /etc/sudoers.d/monitor-weblog >/dev/null && sudo visudo -c #!/bin/sh L="/var/log/nginx/access.log /var/log/apache2/access.log /var/log/apache2/other_vhosts_access.log /var/log/httpd/access_log $(ls -t /var/log/nginx/*access*.log /var/log/apache2/*access*.log /var/log/apache2/*requests*.log /var/log/httpd/*access_log 2>/dev/null)" for f in $L; do [ -r "$f" ] && [ -n "$(find "$f" -size +0 -mmin -1440 2>/dev/null)" ] && exec tail -c 3000000 "$f"; done for f in $(ls -t $L 2>/dev/null); do [ -r "$f" ] && [ -s "$f" ] && exec tail -c 3000000 "$f"; done EOF

Người dùng web có thể khác www-data — hãy kiểm tra pool PHP-FPM của trang chạy dưới người dùng nào (ps -o user= -C php-fpm) và điền tên đó vào dòng sudoers. Nếu tên nhật ký không chứa access lẫn requests, hãy thêm đường dẫn của nó vào đầu dòng L=… bên trong wrapper.

“Không có bản ghi nào cho khoảng đã chọn” — nhật ký đã được đọc nhưng không tìm thấy dòng phù hợp: hoặc trang thực sự không được mở, hoặc nhật ký vừa xoay vòng (logrotate) và bắt đầu lại từ đầu.

Các lần thử khai thác lỗ hổng đã biết. Bảng điều khiển đối chiếu các yêu cầu trong nhật ký với chữ ký của khoảng hai mươi mã khai thác phổ biến: PHP-CGI, duyệt thư mục trái phép trong Apache, PHPUnit, Log4Shell, lỗ hổng của router, camera và cổng VPN. Với mỗi lần thử, bảng cho biết nó có thể thành công trên máy chủ này không, dựa trên dữ kiện: phiên bản Apache và PHP đã cài, có Java hay không và mã phản hồi của máy chủ với yêu cầu tệp. Chỉ những gì thật sự có thể thành công mới được đánh dấu đỏ.

Gia cố máy chủ web. Bảng điều khiển gửi yêu cầu tới từng trang trên máy chủ qua 127.0.0.1 và kiểm tra những gì khách truy cập nhận được: phiên bản máy chủ web và PHP có lộ ra không, phương thức TRACE có bật không và có các header bảo vệ Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy và Content-Security-Policy không. Danh sách trang lấy từ địa chỉ dùng để mở bảng điều khiển và từ các tệp máy chủ ảo, nếu bảng điều khiển đọc được. Kết quả cập nhật mỗi 10 phút.

Nếu thiếu thứ gì, bên dưới bảng sẽ hiện một lệnh duy nhất. Lệnh tạo tệp riêng zz-arciveo-hardening.conf và chỉ nạp lại máy chủ web sau khi kiểm tra cấu hình đạt; các header trang đã tự gửi không bị lặp lại. CSP không được đề xuất dưới dạng một dòng: chính sách phải soạn riêng cho từng trang, nếu không trang sẽ không tải được script, style và phông chữ của chính nó. Hoàn tác thay đổi trên Apache:

sudo a2disconf zz-arciveo-hardening && sudo systemctl reload apache2
Chữ ký trên trang lỗi (ServerSignature) không bị đánh dấu khi phiên bản máy chủ web đã được ẩn: không có phiên bản, nó chỉ hiện tên máy chủ và tên host.

34. Bản đồ tấn công (GeoIP)

Trang “Bản đồ tấn công” xác định quốc gia theo IP bằng lệnh geoiplookup. Nếu thiếu gói GeoIP thì quốc gia sẽ không được xác định và các điểm sẽ không hiện trên bản đồ:

sudo apt install geoip-bin geoip-database # Kiểm tra: geoiplookup 8.8.8.8
Không cần sudo — cơ sở dữ liệu /usr/share/GeoIP/GeoIP.dat ai cũng đọc được, kết quả được lưu cache trong tmp/geoip_cache.json. Bản đồ (Leaflet + ô bản đồ OpenStreetMap) được tải trong trình duyệt — cần có internet trên máy tính đang mở dashboard.

35. Phơi lộ ra ngoài, cập nhật và tự động cập nhật

Hai thẻ tích hợp sẵn trên dashboard cho thấy không phải công cụ đang “bật/tắt”, mà mức độ an toàn thực sự của máy chủ. Không cần cài đặt, đọc cục bộ mà không cần sudo.

Phơi lộ ra ngoài — số dịch vụ lắng nghe trên mọi giao diện (0.0.0.0/[::]) và truy cập được từ bên ngoài. Tô đỏ nếu CSDL hoặc cache lộ ra ngoài (MySQL, PostgreSQL, Redis, MongoDB, Memcached, Elasticsearch) — đây là lỗ hổng trực tiếp (−10 điểm An toàn). Nguồn: ss -tuln.

Nếu thẻ đỏ — hãy đóng CSDL với thế giới bên ngoài: gắn vào 127.0.0.1 (bind-address trong cấu hình MySQL/PostgreSQL, bind 127.0.0.1 trong Redis) hoặc đóng cổng trong UFW.
“Cổng mở” ≠ “truy cập được từ bên ngoài”. Dịch vụ lắng nghe trên 127.0.0.1 (loopback) chỉ chính máy chủ nhìn thấy — từ bên ngoài không thể truy cập, ngay cả khi cổng đã “mở”. Vì vậy Postfix trên cổng 25 gắn với loopback là an toàn: tự động cấu hình đặt inet_interfaces = loopback-only (cộng thêm smtpd_banner trung tính — khắc phục cảnh báo Lynis MAIL-8818 về việc lộ phiên bản). Thẻ “Phơi lộ ra ngoài” chỉ tính là lộ ra ngoài những gì lắng nghe trên 0.0.0.0/[::]; các dịch vụ loopback không được tính.
Lynis MAIL-8818 thủ công (nếu bạn tự cài mail): trong /etc/postfix/main.cf hãy đặt smtpd_banner = $myhostname ESMTP (không có phiên bản và HĐH) và inet_interfaces = loopback-only, rồi sudo systemctl restart postfix.

Cập nhật bảo mật — bao nhiêu bản vá bảo mật đang chờ cài đặt và có cần khởi động lại sau khi cập nhật nhân hay không (−5 điểm An toàn khi có bản vá). Nguồn: /usr/lib/update-notifier/apt-check, tệp /var/run/reboot-required. Danh sách chi tiết — ở trang “Cập nhật bảo mật”.

# Cài đặt bản cập nhật: sudo apt update && sudo apt upgrade # Kiểm tra dịch vụ nào lắng nghe ra ngoài: ss -tuln | grep -E '0\.0\.0\.0|\[::\]'
Thẻ cập nhật hoạt động trên Ubuntu/Debian (update-notifier-common). Nếu không có apt-check — monitor tính số bản vá qua apt-get -s upgrade.

Tự động cập nhật bảo mật (unattended-upgrades) — ở trang “Cập nhật bảo mật” có một thẻ riêng cho thấy việc tự động cài bản vá bảo mật có được bật hay không và lần cuối nó chạy khi nào. Không cần sudo — trạng thái được đọc qua apt-config dump.

sudo apt install unattended-upgrades sudo dpkg-reconfigure -plow unattended-upgrades # bật # Kiểm tra đã bật chưa: apt-config dump | grep Unattended-Upgrade

Bảo trì

36. Sao lưu dữ liệu

Sao lưu là biện pháp bảo hiểm quan trọng nhất: mất dữ liệu còn đáng sợ hơn bất kỳ vụ xâm nhập nào. Bạn cần hai thứ — bản sao lưu máy chủ/website và riêng một bản sao lưu cơ sở dữ liệu của dashboard (nơi lưu người dùng, khóa WebAuthn, thiết lập, giấy phép).

Cách A — HestiaCP: tab Backup ở người dùng → nút tạo bản sao lưu (hoặc theo lịch trong thiết lập máy chủ). Bản sao lưu bao gồm website và cơ sở dữ liệu của chúng.

Cách B — thủ công (cron): dump cơ sở dữ liệu + nén thư mục data/ của dashboard:

# cron của root (sudo crontab -e) — sao lưu hằng ngày lúc 2:30 (thay tên/đường dẫn của bạn): 30 2 * * * mysqldump -u root MY_DB | gzip > /var/backups/monitor-db-$(date +\%F).sql.gz 40 2 * * * tar czf /var/backups/monitor-data-$(date +\%F).tar.gz -C /path/to/monitor data # Xóa các bản lưu cũ hơn 14 ngày: 0 3 * * * find /var/backups -name 'monitor-*' -mtime +14 -delete
Sao lưu trên cùng máy chủ giúp phòng lỗi, nhưng không cứu được khi mất máy chủ. Hãy sao chép các bản lưu ra lưu trữ bên ngoài (máy chủ khác, S3, rclone lên đám mây). Kiểm tra rằng việc khôi phục thực sự hoạt động.

37. Cập nhật và di chuyển panel

Cập nhật lên phiên bản mới. Trước tiên hãy sao lưu. Sau đó tải lại các tệp mã, giữ nguyên dữ liệu của bạn:

  • ghi đè (mã): public/, includes/, assets/, cron/, database/, cùng với các tệp gốc .htaccess (front-controller — không được để định tuyến từ phiên bản cũ), manifest.json, sw.js;
  • không đụng tới: config.php (dữ liệu CSDL), data/ (báo cáo), logs/, tmp/ (phiên và cache).
# Sau khi tải lên — xóa cache PHP (nếu bật opcache): sudo systemctl reload php*-fpm
FileZilla báo SSH_FX_PERMISSION_DENIED — Permission denied. Tệp của bảng điều khiển thuộc về www-data (chúng được đặt như vậy khi cài đặt), trong khi ứng dụng SFTP kết nối bằng người dùng của bạn, vốn không có quyền ghi. Giao toàn bộ bảng điều khiển cho www-data “cho nó chạy” chính là điều dẫn đến lỗi này; dưới đây là ba cách, cách nào cũng giải quyết được vấn đề.
# Phương án A (khuyến nghị) — tách chủ sở hữu: mã là của bạn, thư mục làm việc thuộc máy chủ web. # Máy chủ web hoàn toàn không nhận quyền ghi vào MÃ của bảng điều khiển: sudo chown -R deploy:www-data /path/to/monitor sudo chown -R www-data:www-data /path/to/monitor/data /path/to/monitor/tmp /path/to/monitor/logs sudo find /path/to/monitor -type d -exec chmod 755 {} \; sudo find /path/to/monitor -type f -exec chmod 644 {} \; sudo chmod 750 /path/to/monitor/data /path/to/monitor/tmp /path/to/monitor/logs sudo chmod 640 /path/to/monitor/config.php # Phương án B — ACL phủ lên các chủ sở hữu hiện tại (không thay đổi gì): sudo apt install -y acl sudo setfacl -R -m u:deploy:rwX /path/to/monitor sudo setfacl -R -d -m u:deploy:rwX /path/to/monitor # Phương án C — qua nhóm www-data. Đơn giản hơn, nhưng quyền ghi vào tệp của # bảng điều khiển cũng thuộc về máy chủ web (lỗ hổng PHP có thể thay mã): sudo usermod -aG www-data deploy sudo find /path/to/monitor -type d -exec chmod 2775 {} \; sudo find /path/to/monitor -type f -exec chmod 664 {} \; sudo chmod 640 /path/to/monitor/config.php sudo chmod 2750 /path/to/monitor/data /path/to/monitor/tmp /path/to/monitor/logs
Vì sao phương án A an toàn. Bảng điều khiển chỉ ghi vào ba thư mục — data/ (báo cáo), tmp/ (phiên và cache), logs/; chúng vẫn thuộc về www-data. Phần còn lại là mã, và máy chủ web chỉ cần đọc chúng, điều mà nhóm www-data với quyền 644 đã cho phép. Lợi ích phụ: khi có lỗ hổng trong PHP, các tệp của bảng điều khiển không thể bị ghi đè. Trên các bảng điều khiển hosting (HestiaCP và tương tự) không cần phương án A: ở đó tệp website vốn đã thuộc tài khoản mà bạn dùng để đăng nhập qua SFTP, còn máy chủ web đọc chúng theo nhóm.
Cạm bẫy của phương án B: mọi lệnh chmod tiếp theo lên các tệp sẽ đặt lại mặt nạ ACL, và quyền truy cập lặng lẽ biến mất. Nếu sau khi “dọn dẹp lại quyền” việc tải lên lại vấp phải Permission denied — hãy chạy lại cả hai lệnh setfacl.
Bit 2 trong phương án C chính là setgid: tệp tải lên qua SFTP vẫn nằm trong nhóm www-data, nếu không bảng điều khiển sẽ không ghi đè được. Sau phương án C, hãy kết nối lại trong FileZilla — nhóm mới chỉ có hiệu lực khi đăng nhập mới. Kiểm tra: id deploy (phải xuất hiện nhóm www-data) và ls -ld /path/to/monitor (drwxrwsr-x — chữ s nghĩa là setgid đã được đặt).

Di chuyển sang máy chủ khác:

  1. Trên máy chủ mới, dựng website + HTTPS (xem trang cài đặt thủ công).
  2. Sao chép tất cả tệp của panel cùng với config.php, data/.
  3. Di chuyển CSDL: mysqldump trên máy cũ → nhập vào máy mới; sửa dữ liệu CSDL trong config.php.
  4. Lặp lại trên máy chủ mới: sudoers, thành viên nhóm adm, tác vụ cron.
  5. Giấy phép gắn với tên miền — nếu tên miền không đổi, khóa sẽ tiếp tục hoạt động.

38. Trang thứ hai trên máy chủ này (thêm một tên miền)

Bảng điều khiển đã chạy và trên cùng máy chủ cần thêm một trang nữa — với tên miền riêng bên cạnh trình giám sát. Sau khi thiết lập tự động, máy chủ là một Apache + PHP-FPM thông thường, nên trang web cần ba thứ: thư mục riêng, vhost riêng và chứng chỉ riêng. Bảng điều khiển thì không đụng tới: nó theo dõi toàn bộ máy chủ, không cần bản thứ hai cho tên miền thứ hai.

Phần này dành cho máy chủ không có bảng quản trị hosting. Nếu đã cài HestiaCP hoặc bảng tương tự, hãy thêm tên miền trong đó chứ không làm thủ công. Còn nếu bạn định chạy nhiều trang (kèm cả email), hãy cài bảng quản trị trước khi thiết lập tự động: kịch bản sẽ nhận ra nó và chuyển sang chế độ bổ sung, trong khi bảng quản trị cài sau sẽ dựng lại cấu hình Apache, tường lửa và email.
Cần thay gì trong các lệnh. site2.example.com là địa chỉ của trang mới. Đó có thể là một tên miền riêng (mycompany.com) hoặc một tên miền phụ (shop.mycompany.com) — với Apache thì không khác nhau, chỉ khác ở bản ghi DNS (bước 1) và việc cấp chứng chỉ (bước 5). site2 chỉ là tên ngắn của trang: thư mục /var/www/site2 và tệp cấu hình site2.conf được đặt theo tên đó. Nó không liên quan gì đến tên miền, Apache chỉ lấy địa chỉ từ ServerName; tên có thể là bất kỳ — ví dụ với shop.mycompany.com thì dùng shop là tiện: thư mục /var/www/shop, cấu hình shop.conf.

1. DNS. Trong bảng DNS tại nhà đăng ký tên miền, hãy tạo bản ghi A trỏ tới IP của máy chủ này:

# Tên miền phụ (shop.mycompany.com): Loại: A Tên: shop Giá trị: 203.0.113.10 TTL: 3600 # Tên miền riêng (mycompany.com) — hai bản ghi, để địa chỉ có www cũng hoạt động: Loại: A Tên: @ Giá trị: 203.0.113.10 TTL: 3600 Loại: A Tên: www Giá trị: 203.0.113.10 TTL: 3600

Vài phút sau hãy kiểm tra: dig +short site2.example.com phải trả về IP của máy chủ.

2. Thư mục của trang.

sudo mkdir -p /var/www/site2 sudo chown -R www-data:www-data /var/www/site2

3. vhost. Dán khối lệnh vào terminal nguyên vẹn — đường dẫn tới socket PHP-FPM được tự xác định:

PHPSOCK=$(ls -1 /run/php/php*-fpm.sock 2>/dev/null | head -1) # tự động nhận diện socket PHP-FPM sudo tee /etc/apache2/sites-available/site2.conf > /dev/null <<'EOF' <VirtualHost *:80> ServerName site2.example.com # Tên miền riêng: bỏ dấu # ở dòng dưới để trang cũng mở được với www. # Với tên miền phụ thì không cần dòng này. #ServerAlias www.site2.example.com DocumentRoot /var/www/site2 <Directory /var/www/site2> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory> <FilesMatch \.php$> SetHandler "proxy:unix:__PHPSOCK__|fcgi://localhost" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/site2_error.log CustomLog ${APACHE_LOG_DIR}/site2_access.log combined </VirtualHost> EOF sudo sed -i "s#__PHPSOCK__#${PHPSOCK}#" /etc/apache2/sites-available/site2.conf

4. Bật trang và kiểm tra cấu hình trước khi nạp lại:

sudo a2ensite site2.conf sudo apache2ctl configtest # kết quả mong đợi: Syntax OK sudo systemctl reload apache2

5. SSL. certbot đã được thiết lập tự động cài sẵn; đến lúc này DNS phải trỏ về máy chủ:

# Tên miền phụ — một địa chỉ: sudo certbot --apache -d site2.example.com # Tên miền riêng — kèm luôn www (cả hai bản ghi A ở bước 1 phải trỏ về máy chủ # và www phải có trong ServerAlias, nếu không certbot không xác minh được địa chỉ thứ hai): sudo certbot --apache -d site2.example.com -d www.site2.example.com

Tên miền thứ ba và các tên miền tiếp theo được thêm y hệt — lặp lại các bước 1–5 với tên miền và thư mục khác.

Ai trả lời các yêu cầu “lạ”. Nếu Host không khớp với bất kỳ ServerName nào (truy cập bằng IP hoặc tên miền của người khác trỏ về máy chủ của bạn), Apache sẽ phục vụ trang được bật đầu tiên theo thứ tự bảng chữ cái trong /etc/apache2/sites-enabled/. Trang mặc định 000-default bị thiết lập tự động vô hiệu hóa, nên vai trò “trực” đó thuộc về vhost của bảng điều khiển (arciveo-monitor.conf) — điều này không ảnh hưởng tới các trang mở bằng tên riêng của chúng. Nếu muốn mở trang khác qua IP, hãy đặt tên tệp của nó để đứng trước theo bảng chữ cái (ví dụ 000-site2.conf).
Việc bảo vệ áp dụng cho toàn máy chủ: UFW, Fail2ban, CrowdSec, ModSecurity và Suricata đã chạy sẵn và tự động che chắn trang mới, không phải cấu hình riêng. Cổng 80/443 cũng đã mở. Trang có nhật ký riêng: site2_error.log và site2_access.log trong /var/log/apache2/.
Đừng đặt thư mục của trang mới bên trong thư mục của bảng điều khiển — thư mục gốc web của bảng điều khiển do .htaccess riêng điều phối (front controller), và một trang lồng bên trong sẽ trả về thứ khác với mong đợi. Hãy để các trang nằm cạnh nhau: /var/www/monitor, /var/www/site2.

39. Khôi phục quyền truy cập (mất khóa, mật khẩu, chặn IP)

Nếu bạn không thể đăng nhập — mọi thứ đều sửa được trực tiếp trong CSDL từ máy chủ. Mở CSDL (tên lấy từ config.php):

sudo mysql MY_DB

Mất khóa WebAuthn (không qua được yếu tố thứ hai) — tắt 2FA, đăng nhập bằng mật khẩu, đăng ký khóa mới:

UPDATE users SET webauthn_enabled = 0;

Quên mật khẩu — đặt hash mới (tạo hash trên máy chủ rồi thay vào):

# Tạo hash cho mật khẩu mới: php -r "echo password_hash('NEW_PASSWORD', PASSWORD_BCRYPT), \"\n\";" # Trong CSDL (dán hash vừa tạo vào): # UPDATE users SET password = '$2y$10$...' WHERE username = 'admin';

Tự chặn mình bằng bộ lọc IP — tắt giới hạn:

UPDATE settings SET value = '0' WHERE name = 'ip_restriction_enabled';
Bạn luôn có quyền truy cập CSDL: sudo mysql trên máy chủ, hoặc phpMyAdmin / mục CSDL trong bảng điều khiển hosting. Sau khi khôi phục, hãy bật lại WebAuthn và bộ lọc IP.

40. Tất cả tác vụ cron ở một nơi

Tổng hợp các tác vụ nằm trong root-cron của máy chủ (thêm qua sudo crontab -e). Chỉ giữ lại các dòng của những công cụ bạn dùng; chỉnh đường dẫn cho khớp máy chủ của bạn.

# Cron máy chủ của monitor (root) — nhập qua: sudo crontab -e # 01:30 — quét ClamAV theo các đường dẫn nguy hiểm (web, home, temp) → thẻ “Tệp đã kiểm tra” và “Lần quét gần nhất” 30 1 * * * /usr/local/bin/clamav-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 02:00 — kiểm tra tính toàn vẹn tệp bằng AIDE (cần --config rõ ràng) 0 2 * * * /usr/bin/aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1 # khi khởi động — khôi phục quyền /var/lib/aide (tệp tmpfiles của gói # aide-common.conf đặt lại về 0700, khiến dashboard không còn thấy cơ sở dữ liệu) @reboot chmod 755 /var/lib/aide # 03:00 — kiểm toán an ninh Lynis 0 3 * * * /usr/local/bin/lynis-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 04:00 — cập nhật danh sách chặn ipsum (level 1) 0 4 * * * /usr/local/bin/load-ipsum.sh >> /path/to/monitor/logs/cron.log 2>&1 # tập ipsum khi khởi động được nạp bởi dịch vụ ipsum-load.service (TRƯỚC firewall, nếu không # UFW sẽ không thấy tập trong before.rules) — không phải cron. Ở đây chỉ có việc làm mới hằng ngày bên trên. # 06:00 — báo cáo Logwatch 0 6 * * * /usr/local/bin/logwatch_daily.sh >> /path/to/monitor/logs/cron.log 2>&1 # mỗi 30 phút — kiểm tra ổ đĩa SMART */30 * * * * /usr/local/bin/smart-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 04:30 — tính toàn vẹn gói debsums 30 4 * * * /usr/local/bin/debsums-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 08:00 — báo cáo theo lịch qua Email và Telegram 0 8 * * * /usr/local/bin/daily-report-all.sh >> /path/to/monitor/logs/cron.log 2>&1 # mỗi giờ — làm mới danh sách gói (cho thẻ “Cập nhật bảo mật”) 0 * * * * /usr/bin/apt-get update -qq >/dev/null 2>&1 # mỗi 5 phút — chụp trạng thái tài nguyên (CPU/RAM/mạng/đĩa) cho trang “Hiệu năng” */5 * * * * /usr/local/bin/collect-metrics-all.sh >> /path/to/monitor/logs/cron.log 2>&1 # 5 phút một lần, lệch so với ảnh chụp ở trên — cảm biến sự kiện bảo mật: # lệnh chặn mới, cảnh báo IDS, dịch vụ bảo vệ đã dừng, cổng vừa mở. # Khi việc theo dõi chưa được bật trong cài đặt, tác vụ này không làm gì cả. 2-57/5 * * * * /usr/local/bin/security-watch-all.sh >> /path/to/monitor/logs/cron.log 2>&1 # 15 phút một lần — bản tóm tắt máy chủ gửi về tài khoản cá nhân, để nhiều máy chủ # hiện trong một danh sách. Khi bản tóm tắt chưa được bật, tác vụ này không làm gì cả. 3,18,33,48 * * * * /usr/local/bin/fleet-push-all.sh >> /path/to/monitor/logs/cron.log 2>&1
Chi tiết từng mục xem trong các phần tương ứng. Các tác vụ sao lưu (phần trước) được thêm vào cùng cron này. Sau khi chỉnh, hãy kiểm tra: sudo crontab -l và xem dịch vụ cron còn hoạt động không.
Giờ cron = múi giờ máy chủ, không phải TIMEZONE trong config.php. Hằng số TIMEZONE chỉ ảnh hưởng đến PHP (cách dashboard hiển thị ngày), nhưng cron-daemon chạy tác vụ theo giờ hệ thống của OS. Nếu múi giờ máy chủ không khớp với bạn, báo cáo “08:00” sẽ đến sai giờ. Ví dụ: máy chủ đặt ở múi giờ khác (Asia/Dhaka, UTC+6), còn bạn ở TP. Hồ Chí Minh (UTC+7) → báo cáo “08:00” sẽ đến lúc 09:00 giờ của bạn. Hãy kiểm tra và nếu cần, chỉnh múi giờ hệ thống về giờ của bạn:
# Kiểm tra múi giờ hiện tại của máy chủ: timedatectl # Đặt múi giờ của bạn (ví dụ) và khởi động lại cron: sudo timedatectl set-timezone Asia/Ho_Chi_Minh sudo systemctl restart cron
Sau đó dòng 0 8 * * * sẽ chạy lúc 08:00 giờ địa phương. Nếu không, bạn sẽ phải tự dịch chuyển cron, nhưng khi chuyển giờ mùa đông/mùa hè độ lệch lại sai — nên chỉnh múi giờ hệ thống là đúng hơn.
Các script bao bọc sẵn có. Bản làm việc của chúng và mẫu crontab (crontab.txt) nằm trong thư mục system/ cạnh dự án, bên ngoài public_html. Đây không phải phần của trang web — không cần tải chúng lên web-root; hãy đặt trên máy chủ theo các đường dẫn hệ thống (như trong crontab ở trên):
  • lynis-scan.sh → /usr/local/bin/ (chmod +x) — chạy lynis audit system, trong lúc quét đặt cờ /tmp/lynis-running và sao chép lynis-report.dat vào data/lynis/ của dashboard;
  • logwatch_daily.sh → /usr/local/bin/ (chmod +x) — tạo báo cáo Logwatch hằng ngày (sshd, fail2ban, sudo, postfix) vào data/logwatch/;
  • smart-scan.sh → /usr/local/bin/ (chmod +x) — lấy trạng thái ổ đĩa (smartctl) vào data/disk/;
  • debsums-scan.sh → /usr/local/bin/ (chmod +x) — kiểm tra tính toàn vẹn gói (debsums) vào data/debsums/;
  • clamav-scan.sh → /usr/local/bin/ (chmod +x) — quét diệt virus ClamAV theo các đường dẫn nguy hiểm (web, home, temp); ghi tổng hợp vào /var/log/clamav/scan.log, nơi trang ClamAV đọc dữ liệu (dòng 01:30 trong crontab ở trên);
  • load-ipsum.sh → /usr/local/bin/ (chmod +x) — cập nhật tập ipset ipsum (level 1) tại chỗ, không phá vỡ các quy tắc tường lửa đang hoạt động (dòng 04:00 trong crontab ở trên);
  • daily-report-all.sh → /usr/local/bin/ (chmod +x) — chạy báo cáo cron/daily_report.php của dashboard (dòng 08:00 trong crontab ở trên);
  • daily_report.php — đã có sẵn trong dashboard (cron/daily_report.php), chạy qua daily-report-all.sh, không cần cài riêng;
  • collect-metrics-all.sh → /usr/local/bin/ (chmod +x) — chạy cron/collect_metrics.php của dashboard (trang “Hiệu năng”, dòng */5 trong crontab ở trên); collect_metrics.php đã có sẵn trong dashboard, không cần cài riêng;
  • security-watch-all.sh → /usr/local/bin/ (chmod +x) — chạy cron/security_watch.php của panel dưới danh nghĩa chủ sở hữu trang (trang “Sự kiện bảo mật”, dòng 2-57/5 trong crontab ở trên); security_watch.php đã nằm sẵn trong panel, không cần cài riêng;
  • fleet-push-all.sh → /usr/local/bin/ (chmod +x) — gửi bản tóm tắt máy chủ về tài khoản cá nhân thông qua cron/fleet_push.php của panel (dòng 3,18,33,48 ở trên);
  • crontab.txt (system/cron/) — mẫu tác vụ; nhập các dòng cần thiết qua sudo crontab -e.
Đường dẫn tới script trong crontab phải khớp với nơi bạn đã đặt nó.
Cách đặt script vào /usr/local/bin/. Không thể ghi trực tiếp từ FileZilla vào đó — thư mục thuộc về root, và SFTP-client sẽ nhận SSH_FX_PERMISSION_DENIED. Trình tự như sau: trước tiên tải tệp lên /tmp (ai cũng ghi được vào đó), rồi chuyển nó vào đúng chỗ bằng một lệnh:
# trong FileZilla: ở ô “Máy chủ ở xa” nhập /tmp rồi tải script lên đó, # rồi qua SSH (install đặt luôn chủ sở hữu và quyền, không cần chown/chmod): sudo install -o root -g root -m 755 /tmp/lynis-scan.sh /usr/local/bin/lynis-scan.sh rm -f /tmp/lynis-scan.sh # kiểm tra: tệp đã ở đúng chỗ, quyền rwxr-xr-x, cú pháp còn nguyên bash -n /usr/local/bin/lynis-scan.sh && ls -l /usr/local/bin/lynis-scan.sh
Đừng nhầm thư mục: cần /tmp ở gốc máy chủ — không phải /var/tmp và cũng không phải tmp/ bên trong chính dashboard (thư mục sau thuộc về www-data và không cho người dùng của bạn truy cập). Trong cây thư mục của FileZilla, /tmp là nhánh cấp cao nhất, cạnh var, không nằm bên trong nó.
Đã cài máy chủ bằng cấu hình tự động? Các script bao bọc này và các tác vụ cron của chúng đã được cài sẵn bởi script (trong /usr/local/bin/, log — /var/log/arciveo-cron.log) — không cần làm gì thủ công.
Nơi các script tìm dashboard. Các script bao bọc là trung lập về tên miền: chúng tìm các bản cài dashboard bằng cách duyệt /home/*/web/*/public_html và /var/www/*, rồi đặt báo cáo vào data/ của chúng. Nếu dashboard nằm ở đường dẫn khác — hãy thêm nó vào dòng for app in … bên trong các script, nếu không báo cáo Lynis/SMART/debsums/Logwatch sẽ không vào được dashboard.
cron.log và quyền truy cập. Tệp logs/cron.log được tạo đầu tiên bởi root-cron — nó sẽ thuộc về root, và tab “Nhật ký cron” trong dashboard sẽ không đọc cũng như xóa được nó. Hãy tạo tệp trước bằng danh nghĩa web-user (chủ sở hữu thư mục trang web; trên HestiaCP đây là tài khoản, vd. admin) — khi đó root-cron chỉ ghi thêm mà không đổi chủ sở hữu:
# tạo trước bằng web-user (trước khi thêm các dòng cron): sudo -u OWNER touch /path/to/monitor/logs/cron.log # nếu cron.log đã do root-cron tạo — giao lại cho web-user: sudo chown OWNER:OWNER /path/to/monitor/logs/cron.log sudo chmod 644 /path/to/monitor/logs/cron.log
Xem chủ sở hữu thư mục: stat -c %U /path/to/monitor.
Quản lý từ dashboard. Trong phần “Hệ thống” có trang “Crontab” — có thể xem và thêm tác vụ mà không cần SSH. Dashboard chỉ chỉnh những tác vụ được thêm qua chính nó (một khối riêng trong root-crontab, đánh dấu bằng các chú thích nội bộ); tất cả những gì đã có trong crontab (danh sách ở trên) được hiển thị ở đó dưới dạng danh sách chỉ đọc “Tác vụ khác của máy chủ” với nút “Sao chép vào trình soạn thảo” — nút này chỉ chuyển lịch/lệnh vào biểu mẫu thêm, không đụng đến dòng gốc. Để “chuyển” một tác vụ đã có sang cho dashboard quản lý — hãy sao chép nó vào trình soạn thảo, lưu lại, rồi xóa dòng cũ thủ công (sudo crontab -e), nếu không nó sẽ chạy hai lần.
Thiết lập một lần trên máy chủ. Trang cần một script bao bọc có đặc quyền — không phải sudo crontab trần (như vậy sẽ là leo thang trực tiếp lên root cho bất kỳ ai chiếm được phiên dashboard), mà là một script hẹp với hai lệnh (list/set), chỉ đụng đến khối riêng của nó nằm giữa các chú thích nội bộ. Cài một lần:
sudo install -m 0755 -o root -g root /dev/stdin /usr/local/sbin/arciveo-cron <<'ARCIVEO_CRON_EOF' #!/bin/bash set -euo pipefail BEGIN='# >>> ARCIVEO-CRON-MANAGED (edited from the panel; do not edit by hand) >>>' END='# <<< ARCIVEO-CRON-MANAGED <<<' cmd=${1:-} case "$cmd" in list) [ "$#" -eq 1 ] || { echo "usage: ${0##*/} list" >&2; exit 2; } crontab -l -u root 2>/dev/null || true ;; set) [ "$#" -eq 1 ] || { echo "usage: ${0##*/} set (body on stdin)" >&2; exit 2; } body=$(cat) if grep -qF "$BEGIN" <<<"$body" || grep -qF "$END" <<<"$body"; then echo "invalid body: markers not allowed" >&2; exit 2 fi current=$(crontab -l -u root 2>/dev/null || true) tmp=$(mktemp); trap 'rm -f "$tmp"' EXIT { if grep -qF "$BEGIN" <<<"$current"; then awk -v b="$BEGIN" '{print} $0==b{exit}' <<<"$current" else [ -n "$current" ] && printf '%s\n' "$current" echo "$BEGIN" fi printf '%s\n' "$body" echo "$END" if grep -qF "$END" <<<"$current"; then awk -v e="$END" 'f{print} $0==e{f=1}' <<<"$current" fi } > "$tmp" crontab -u root "$tmp" ;; *) echo "usage: ${0##*/} <list|set>" >&2; exit 2 ;; esac ARCIVEO_CRON_EOF echo "www-data ALL=(ALL) NOPASSWD: /usr/local/sbin/arciveo-cron" | sudo tee -a /etc/sudoers.d/monitor sudo visudo -c
Web-user có thể khác www-data — hãy kiểm tra pool PHP-FPM của trang web chạy dưới người dùng nào (ps -o user= -C php-fpm), rồi thay vào dòng sudoers.
Tệp mới được tải lên với chủ sở hữu sai — trang trả về “Access denied.”. Nếu tệp public/crontab_monitor.php được tải lên qua FTP/SFTP dưới người dùng hệ thống khác (ví dụ root) so với các tệp còn lại của trang web, web-server sẽ không đọc được nó. Hãy đối chiếu chủ sở hữu và quyền với tệp lân cận rồi chỉnh cho khớp:
ls -la public/crontab_monitor.php public/ssl_monitor.php sudo chown OWNER:OWNER public/crontab_monitor.php sudo chmod 644 public/crontab_monitor.php

Chẩn đoán

41. Công cụ đã cài nhưng vẫn báo “Chưa cài”

Monitor xác định sự hiện diện của công cụ thông qua dpkg-query — cơ sở dữ liệu gói APT. Nếu công cụ không được cài qua apt (thủ công, từ snap hoặc từ mã nguồn), dpkg sẽ không thấy nó.

# Kiểm tra qua dpkg: dpkg -l fail2ban | grep '^ii' dpkg -l auditd | grep '^ii' # Tìm đường dẫn tới tệp thực thi: which ufw fail2ban-client auditctl # Kiểm tra sudo từ www-data: sudo -u www-data sudo fail2ban-client status sudo -u www-data sudo ufw status verbose

42. Khắc phục sự cố (500, không có dữ liệu)

Lỗi 500 — hãy kiểm tra log của PHP, nginx và của chính monitor:

tail -50 /var/log/nginx/error.log tail -50 /var/log/php*-fpm.log # Log của monitor: tail -50 logs/monitor_$(date +%Y-%m-%d).log # Quyền trên thư mục: ls -la data/ tmp/ logs/
Dashboard chạy trên hosting panel (HestiaCP, ISPmanager, cPanel)? Ở đó PHP không chạy dưới www-data mà dưới tài khoản người dùng (ví dụ admin — chủ sở hữu thư mục website). Mọi quy tắc sudo và thành viên nhóm (adm, systemd-journal) đều phải khai báo cho người dùng này, nếu không các mô-đun sẽ hiện “Không hoạt động / 0” dù dịch vụ đang chạy. Xác định người dùng PHP thực tế: ps -o user= -C php-fpm | sort -u hoặc chủ sở hữu thư mục website stat -c '%U' /path/to/monitor. Sau đó trong tất cả lệnh bên dưới hãy thay nó vào chỗ www-data. Bản cài đặt tự động tự nhận diện người dùng web và khai báo sudoers cho nó.

Dữ liệu không hiển thị — hầu như luôn do chưa cấp quyền sudo. Hãy kiểm tra từng lệnh cụ thể với tư cách người dùng web (thay www-data bằng người dùng của bạn). Cờ -n = không cần mật khẩu, giống như PHP — nếu nó hỏi mật khẩu nghĩa là chưa có quy tắc trong sudoers:

sudo -u www-data sudo -n fail2ban-client status sudo -u www-data sudo -n ufw status verbose sudo -u www-data sudo -n ipset list -t ipsum sudo -u www-data sudo -n /usr/sbin/ausearch -m USER_LOGIN -ts today sudo -u www-data sudo -n /usr/sbin/aa-status sudo -u www-data sudo -n /usr/sbin/psad --Status sudo -u www-data journalctl -u falco --no-pager -n 5
Mô-đun hiện “Không hoạt động” / “0” dù công cụ vẫn chạy (ví dụ sudo aa-status trong terminal có hiện profile, nhưng trang “AppArmor” lại “Không hoạt động”). Nguyên nhân: người dùng web không có quyền sudo cho lệnh của chính mô-đun đó. Hãy thử lệnh đó từ danh sách trên: nếu bị hỏi mật khẩu — thêm dòng còn thiếu vào /etc/sudoers.d/monitor (“Cấu hình sudo”). Các lệnh “mới” hay gặp: /usr/sbin/aa-status (MAC), /usr/sbin/psad --Status (PSAD).
Nếu một trang cụ thể (Falco, ModSecurity, Auditd, cổng đang mở của UFW) trống rỗng — hãy đối chiếu với danh sách trong phần về sudo: có thể chưa cho phép apache2ctl, ausearch, aa-status hoặc ss, hoặc người dùng web không thuộc nhóm adm/systemd-journal (từ đó đọc log fail2ban/auth/modsec và journalctl — Falco cùng các sự kiện kernel).

43. Trang trống mặc dù dữ liệu đã có trên máy chủ

Triệu chứng: trên máy chủ có dữ liệu (thấy được qua shell), nhưng trang lại hiển thị “không có dữ liệu” hoặc trạng thái sai — ví dụ AIDE báo “Chưa khởi tạo” dù cơ sở dữ liệu đã được tạo.

Nguyên nhân là open_basedir: nhiều bảng điều khiển và hosting giới hạn pool PHP-FPM trong thư mục của domain, nên các hàm PHP file_exists(), file_get_contents(), filemtime() trên các đường dẫn hệ thống (/var/lib/aide, /var/log, /proc…) bị chặn. Monitor khắc phục bằng cách đọc các đường dẫn đó qua lệnh hệ thống chuẩn (cat, test, stat).

# Tệp có thấy được qua shell không (cách monitor đọc): sudo -u www-data bash -lc 'test -e /var/lib/aide/aide.db && echo VISIBLE || echo NO' # Giá trị open_basedir hiện tại của pool domain: grep -ri open_basedir /etc/php/*/fpm/pool.d/ 2>/dev/null
Nếu shell “thấy” tệp (VISIBLE) mà trang thì không — đó là open_basedir. Giải pháp đúng là đọc bằng lệnh hệ thống (đã áp dụng cho AIDE và Giám sát mạng). Không cần mở rộng open_basedir ra /var, /proc và như vậy kém an toàn hơn.

44. Trang SSL không hoạt động

Monitor kiểm tra chứng chỉ bằng cách kết nối trực tiếp đến các domain qua cổng 443. Nếu domain không truy cập được từ chính máy chủ hoặc cổng bị firewall chặn — việc kiểm tra sẽ thất bại.

# Kiểm tra chứng chỉ thủ công: echo | openssl s_client -connect monitor.example.com:443 2>/dev/null \ | openssl x509 -noout -dates # Kiểm tra khả năng truy cập: curl -I https://monitor.example.com
Monitor tự động lấy domain từ các cấu hình của Nginx (/etc/nginx/sites-enabled/, /etc/nginx/conf.d/) và Apache (/etc/apache2/sites-enabled/) cùng với host hiện tại từ HTTP_HOST.
Tự động phát hiện subdomain. Các subdomain được phát hiện tự động từ nhật ký công khai Certificate Transparency và kiểm tra qua mạng — kể cả khi chúng đặt trên máy chủ khác. Không cần thêm gì thủ công.

45. Chỉ thấy được một trong số nhiều CSDL

Monitor kết nối tới MySQL bằng người dùng khai báo trong config.php, người này chỉ có quyền truy cập cơ sở dữ liệu của mình. MySQL chỉ hiển thị trong information_schema các cơ sở dữ liệu mà người dùng có quyền — nên những cơ sở còn lại không hiện ra.

Để monitor thấy được tất cả CSDL, hãy cấp cho người dùng này quyền chỉ đọc (một lần bằng root; thay bằng tên người dùng trong config.php):

sudo mysql -u root GRANT SELECT, PROCESS, SHOW DATABASES ON *.* TO 'DB_USER'@'localhost'; FLUSH PRIVILEGES; EXIT;
SELECT ON *.* chỉ cấp quyền đọc — không thể sửa, xóa hay tạo bất kỳ thứ gì, hoàn toàn an toàn cho việc giám sát.
Nếu không có GRANT này, dashboard chỉ thấy cơ sở dữ liệu của mình — đây không phải lỗi mà là giới hạn quyền. Dashboard không dùng sudo mysql: danh sách cơ sở dữ liệu được lấy qua kết nối PDO riêng của nó.

46. PostgreSQL không hiển thị trên trang “Cơ sở dữ liệu”

PostgreSQL yêu cầu quyền truy cập ở cấp người dùng postgres, mà người dùng web của dashboard không có. Việc mở lệnh sudo psql rộng từ PHP là không an toàn — thay vào đó dashboard gọi một lớp bọc hẹp không tham số, chỉ in ra phiên bản, số kết nối và danh sách cơ sở dữ liệu kèm dung lượng. Hãy tạo nó:

sudo tee /usr/local/bin/monitor-pgstat >/dev/null <<'EOF' #!/bin/sh # Arciveo Security Monitor - read-only PostgreSQL version, connections and per-database size sudo -u postgres psql -tAc "SELECT version();" | grep -oE 'PostgreSQL [0-9.]+' echo "---" sudo -u postgres psql -tAc "SELECT count(*) FROM pg_stat_activity;" echo "---" sudo -u postgres psql -tAc "SELECT datname || '|' || pg_size_pretty(pg_database_size(datname)) FROM pg_database WHERE datistemplate = false;" EOF sudo chown root:root /usr/local/bin/monitor-pgstat sudo chmod 755 /usr/local/bin/monitor-pgstat # trong /etc/sudoers.d/monitor (người dùng = người mà PHP-FPM chạy dưới quyền): # www-data ALL=(ALL) NOPASSWD: /usr/local/bin/monitor-pgstat
Nếu không dùng PostgreSQL — hãy bỏ dòng monitor-pgstat khỏi sudoers (bước 13 của phần cài đặt thủ công) và không cần tạo script: thẻ PostgreSQL sẽ chỉ đơn giản là không hoạt động.

47. Cảnh báo được kích hoạt — cần làm gì

Bảng điều khiển cho thấy điều gì đang xảy ra; dưới đây là cần làm gì trong các tình huống điển hình. Nguyên tắc chung: đừng hoảng loạn, đối chiếu với hoạt động hợp lệ (thao tác của bạn, cập nhật, sao lưu), và xử lý theo mức độ nghiêm trọng.

  • Bản đồ tấn công / nhiều lượt chặn của fail2ban — đây là chuyện bình thường với mọi máy chủ trên internet (bot liên tục dò SSH/web). Điều quan trọng là các lượt chặn có hoạt động. Hãy đảm bảo đăng nhập SSH chỉ bằng khóa (đã tắt mật khẩu), và IP của bạn nằm trong ignoreip.
  • ModSecurity đã chặn các yêu cầu — WAF đang đẩy lùi các cuộc tấn công vào trang web, đó là nhiệm vụ của nó. Nếu lưu lượng hợp lệ của bạn bị chặn (báo động giả) — hãy tìm rule id trong chi tiết và thêm ngoại lệ vào cấu hình CRS.
  • AIDE: các tệp đã bị thay đổi — đối chiếu danh sách với những gì bạn đã làm (cập nhật gói, sửa cấu hình — là bình thường). Việc thay đổi các tệp nhị phân hệ thống mà bạn không đụng tới là lý do để cảnh giác. Sau các thay đổi hợp lệ, hãy cập nhật cơ sở dữ liệu AIDE.
  • debsums: tệp nhị phân/thư viện bị thay đổi (ngoài /etc, ngoài /usr/share) — có khả năng bị đánh tráo. Hãy đối chiếu gói: debsums PACKAGE_NAME, nếu nghi ngờ hãy cài đặt lại nó (apt install --reinstall).
  • ClamAV / maldet: phát hiện mối đe dọa — hãy kiểm tra tệp trong khu cách ly, đừng mở nó. Nếu đó là web-shell trong thư mục trang web — hãy cô lập máy chủ và tìm điểm xâm nhập (plugin có lỗ hổng, rò rỉ quyền truy cập).
  • Falco: sự kiện nghiêm trọng (khởi chạy shell trong container, truy cập các tệp nhạy cảm) — hãy phân tích sự kiện: tiến trình của ai, thứ gì đã khởi chạy. Thường đó là hoạt động quản trị hợp lệ.
  • Phơi nhiễm bên ngoài: CSDL/cache hiện màu đỏ — hãy đóng ngay: gắn dịch vụ vào 127.0.0.1 hoặc đóng cổng trong UFW. Đây là một lỗ hổng thực sự.
  • SSL sắp hết hạn / đã hết hạn — hãy gia hạn chứng chỉ (Let's Encrypt tự gia hạn; nếu không — hãy kiểm tra certbot renew hoặc thiết lập trong bảng điều khiển).
  • Có bản cập nhật bảo mật đang chờ — hãy cài đặt: sudo apt update && sudo apt upgrade; sau khi cập nhật nhân hãy khởi động lại máy chủ.
Dấu hiệu bị xâm nhập thực sự (tiến trình/người dùng lạ, tệp nhị phân bị thay đổi, spam gửi đi, tác vụ cron lạ): hãy ngắt máy chủ khỏi truy cập bên ngoài, tạo bản sao lưu để phân tích và, nếu dữ liệu là trọng yếu, hãy dựng máy chủ sạch từ một bản sao lưu đáng tin cậy — việc dọn sạch rootkit một cách chắc chắn là rất khó.