Nếu bạn đang quản lý một hay nhiều site WordPress trên VPS, câu hỏi không phải là “site tôi có bị hack không?” mà là “Site nào bị hack?” hay “tôi sẽ phát hiện ra khi nào?”
Wordfence plugin trên Dashboard rất quen thuộc, nhưng ít người biết rằng nó có một điểm yếu nghiêm trọng: nó chạy bên trong WordPress. Nếu malware đã xâm nhập vào hệ thống, chúng hoàn toàn có thể can thiệp vào kết quả scan, tự loại mình ra khỏi danh sách — và bạn vẫn thấy báo cáo “No threats found” trong khi site đang bị nhiễm nặng.
Hay trên 1 VPS mà bạn chạy nhiều site, rồi mỗi site bạn lại cài Wordfence Plugin thì sẽ rất nặng cho VPS, rất tốn tài nguyên, quản lý mất thời gian
Để duy trì blog nên mình có làm aff cho 1 số bên hosting. Nhưng dù aff mình cũng chọn 1 số nhà cung cấp uy tín về chất lượng và support nên các bạn cứ yên tâm nhé.
Nếu có mua hosting mà có trong list dưới đây các bạn click vào link trước khi mua để ủng hộ mình nhé. Mình cảm ơn nhiều
- Azdigi: Giá rẻ thì dùng gói Pro Gold Hosting còn chất lượng hơn thì em khuyên dùng Business Hosting. Có điều kiện thì lên VPS nhé
- Tino hosting
- iNet
- Nước ngoài thì Vultr
Giải pháp? Wordfence CLI — công cụ dòng lệnh chạy thẳng trên server, hoàn toàn độc lập với WordPress, không có malware nào can thiệp được. Quét 1 phát được tất cả site trên VPS

Vấn đề với các cách scan thông thường
Trước khi đi vào cài đặt, hãy hiểu tại sao các phương pháp thông thường không đủ tin cậy:
- Wordfence Plugin: Chạy trong WordPress → malware có thể bypass kết quả scan
- cPanel Virus Scanner: Signature database cũ, nhiều false negative
- Online scanner (Sucuri, VirusTotal…): Chỉ scan HTML output bên ngoài, không thấy được file PHP độc hại phía server
Wordfence CLI khắc phục tất cả điểm yếu trên vì nó chạy trực tiếp trên filesystem của server, không qua WordPress, không qua trình duyệt.
Tại sao nên dùng Wordfence CLI?
- Scan toàn bộ filesystem, kể cả file ngoài thư mục WordPress
- Malware không thể tự che giấu khỏi CLI
- Có thể tự động hóa qua cronjob — scan hàng đêm mà không cần làm gì thủ công
- Export kết quả ra CSV để lưu lịch sử theo dõi
- Miễn phí, open-source, dùng cùng signature database với Wordfence plugin
Hướng dẫn cài đặt trên VPS Ubuntu/Debian
Yêu cầu: Ubuntu 20.04+ hoặc Debian 11+, Python 3.8+, quyền root hoặc sudo.
Bước 1 — Cài system dependencies (quan trọng, không bỏ qua)
Đây là bước hay bị bỏ qua nhất và cũng là nguyên nhân gây ra lỗi phổ biến nhất. Wordfence CLI cần thư viện PCRE native của hệ thống để xử lý pattern matching.
apt-get update
apt-get install -y libpcre2-dev libpcre3-dev python3-full pipx
Bước 2 — Thiết lập pipx
Từ Python 3.12, Ubuntu/Debian block việc dùng pip3 install trực tiếp vào system (theo PEP 668). Dùng pipx thay thế — nó tạo virtual environment riêng biệt cho mỗi tool CLI, sạch sẽ và không gây conflict.
pipx ensurepath
source ~/.bashrc
Bước 3 — Cài Wordfence CLI
pipx install wordfence
Bước 4 — Kiểm tra
wordfence --help
Hiện ra logo Wordfence + danh sách lệnh là thành công.
Lần đầu chạy scan hệ thống sẽ hỏi cấu hình như đồng ý điều khoản sử dụng, license… thì bạn cứ đồng ý hết là được nhé. Hoặc cấu hình chủ động bằng
wordfence configure
Cách sử dụng — Scan malware
Scan một site cụ thể:
wordfence malware-scan /var/www/yourdomain.com/htdocs
Scan nhiều site cùng lúc:
wordfence malware-scan /var/www/site1.com/htdocs /var/www/site2.com/htdocs
hoặc
wordfence malware-scan /var/www/*/htdocs
Scan toàn bộ /var/www:
wordfence malware-scan /var/www
Export kết quả ra CSV:
wordfence malware-scan \
--output-format csv \
--output-path /root/scan-result.csv \
/var/www/yourdomain.com/htdocs
hoặc file .txt
wordfence malware-scan \
--output-format line-delimited \
--output-path /root/scan-result.txt \
/var/www/yourdomain.com/htdocs
Cách chạy tự động và báo kết quả ra tele
Để chạy tự động, bạn hãy làm tiếp bước dưới đây để
- Chạy tự động scan hàng ngày
- Nếu có file nghi ngờ mã độc sẽ tự động gửi về tele
- Mỗi ngày scan 20 site tránh scan quá nhiều gây nặng vps
Bước 1: Tạo file wordfence-scan.sh để viết script tự động scan và gửi kết quả qua tele
nano /root/wordfence-scan.sh
Nội dung file
#!/bin/bash
# ==========================================
# WORDFENCE ROTATING SCAN — 20 site/ngày
# ==========================================
WEB_ROOT="/var/www" #Thay lại cho đúng với VPS của bạn
BOT_TOKEN="YOUR_BOT_TOKEN"
CHAT_ID="YOUR_CHAT_ID"
LOG_DIR="/root/scans"
RESULT_FILE="${LOG_DIR}/scan-result.txt"
STATE_FILE="/root/scans/.scan_index" # Lưu vị trí đang scan
SITES_PER_DAY=20 # Số site scan mỗi ngày
DELAY_BETWEEN_SITES=60 # Giây nghỉ giữa các site
HOSTNAME=$(hostname)
mkdir -p "$LOG_DIR"
# Xoá trắng file kết quả cho lần quét mới
> "$RESULT_FILE"
# --- HÀM GỬI TELEGRAM ---
send_telegram() {
local message="$1"
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
--data-urlencode "chat_id=${CHAT_ID}" \
--data-urlencode "text=${message}" \
--data-urlencode "parse_mode=HTML" \
> /dev/null
}
# --- HÀM SCAN 1 SITE ---
scan_site() {
local SITE_PATH="$1"
local SITE_NAME=$(basename $(dirname "$SITE_PATH"))
local TEMP_FILE=$(mktemp)
local START_TIME=$(date +%s)
echo "[$(date '+%H:%M:%S')] ▶ $SITE_NAME"
/root/.local/bin/wordfence malware-scan \
--output-format line-delimited \
--output-path "$TEMP_FILE" \
--quiet \
"$SITE_PATH"
local DURATION=$(( $(date +%s) - START_TIME ))
local COUNT=0
[ -s "$TEMP_FILE" ] && COUNT=$(wc -l < "$TEMP_FILE")
if [ "$COUNT" -gt 0 ]; then
# Ghi vào file kết quả chung
echo "=== ${SITE_NAME} — ${COUNT} file — $(date '+%d/%m/%Y %H:%M:%S') ===" >> "$RESULT_FILE"
cat "$TEMP_FILE" >> "$RESULT_FILE"
echo "" >> "$RESULT_FILE"
local FILE_LIST=$(head -20 "$TEMP_FILE")
send_telegram "🚨 <b>CẢNH BÁO MALWARE</b>
🖥 Server: <b>${HOSTNAME}</b>
🌐 Site: <b>${SITE_NAME}</b>
📁 Phát hiện: <b>${COUNT} file đáng nghi</b>
⏱ Scan: ${DURATION}s
<code>${FILE_LIST}</code>"
fi
rm -f "$TEMP_FILE"
LAST_SCAN_COUNT=$COUNT
echo "[$(date '+%H:%M:%S')] ✔ ${SITE_NAME} — ${COUNT} threats — ${DURATION}s"
}
# ==========================================
# DISCOVER TẤT CẢ SITE
# ==========================================
ALL_SITES=()
while IFS= read -r -d '' config; do
ALL_SITES+=("$(dirname "$config")")
done < <(find "$WEB_ROOT" -maxdepth 4 -name "wp-config.php" -print0 2>/dev/null)
TOTAL_SITES=${#ALL_SITES[@]}
if [ "$TOTAL_SITES" -eq 0 ]; then
echo "Không tìm thấy site nào."
exit 1
fi
# ==========================================
# ĐỌC INDEX HIỆN TẠI — xoay vòng
# ==========================================
START_INDEX=0
if [ -f "$STATE_FILE" ]; then
START_INDEX=$(cat "$STATE_FILE")
# Reset về 0 nếu đã quét hết
if [ "$START_INDEX" -ge "$TOTAL_SITES" ]; then
START_INDEX=0
echo "🔄 Đã quét hết tất cả site — bắt đầu lại từ đầu"
fi
fi
END_INDEX=$(( START_INDEX + SITES_PER_DAY ))
[ "$END_INDEX" -gt "$TOTAL_SITES" ] && END_INDEX="$TOTAL_SITES"
BATCH_COUNT=$(( END_INDEX - START_INDEX ))
# Tính ngày hoàn thành 1 vòng
DAYS_LEFT=$(( (TOTAL_SITES - END_INDEX + SITES_PER_DAY - 1) / SITES_PER_DAY ))
echo "=========================================="
echo "Tổng site: $TOTAL_SITES"
echo "Hôm nay scan: site $((START_INDEX+1)) → $END_INDEX ($BATCH_COUNT site)"
echo "Còn lại: ~$DAYS_LEFT ngày để hoàn thành 1 vòng"
echo "=========================================="
# Gửi thông báo bắt đầu
send_telegram "🔍 <b>Wordfence Rotating Scan</b>
🖥 Server: <b>${HOSTNAME}</b>
📦 Hôm nay: site <b>${START_INDEX+1} → ${END_INDEX}</b> / ${TOTAL_SITES}
🗓 Còn ~${DAYS_LEFT} ngày để hoàn thành 1 vòng
🕐 Bắt đầu: $(date '+%d/%m/%Y %H:%M')"
# ==========================================
# SCAN BATCH HÔM NAY
# ==========================================
CURRENT=0
TOTAL_THREATS=0
INFECTED_SITES=""
LAST_SCAN_COUNT=0
for (( i=START_INDEX; i<END_INDEX; i++ )); do
CURRENT=$(( CURRENT + 1 ))
SITE_PATH="${ALL_SITES[$i]}"
SITE_NAME=$(basename $(dirname "$SITE_PATH"))
echo ""
echo "[$CURRENT/$BATCH_COUNT] (site $(( i+1 ))/$TOTAL_SITES)"
scan_site "$SITE_PATH"
if [ "$LAST_SCAN_COUNT" -gt 0 ]; then
TOTAL_THREATS=$(( TOTAL_THREATS + LAST_SCAN_COUNT ))
INFECTED_SITES="${INFECTED_SITES}\n⚠️ ${SITE_NAME}: ${LAST_SCAN_COUNT} file"
fi
# Nghỉ giữa các site (trừ site cuối)
if [ "$CURRENT" -lt "$BATCH_COUNT" ]; then
echo "[$(date '+%H:%M:%S')] Nghỉ ${DELAY_BETWEEN_SITES}s..."
sleep "$DELAY_BETWEEN_SITES"
fi
done
# ==========================================
# LƯU INDEX CHO NGÀY MAI
# ==========================================
echo "$END_INDEX" > "$STATE_FILE"
# ==========================================
# BÁO CÁO TỔNG KẾT
# ==========================================
if [ "$TOTAL_THREATS" -gt 0 ]; then
send_telegram "📊 <b>KẾT QUẢ HÔM NAY</b>
🖥 Server: <b>${HOSTNAME}</b>
📦 Đã scan: <b>${BATCH_COUNT} site</b> (${START_INDEX+1}→${END_INDEX}/${TOTAL_SITES})
🚨 Site bị nhiễm: $(echo -e "$INFECTED_SITES")
🔢 Tổng file nghi: <b>${TOTAL_THREATS}</b>
🕐 Hoàn thành: $(date '+%d/%m/%Y %H:%M')"
else
send_telegram "✅ <b>SCAN XONG — SẠCH</b>
🖥 Server: <b>${HOSTNAME}</b>
📦 Đã scan: <b>${BATCH_COUNT} site</b> (${START_INDEX+1}→${END_INDEX}/${TOTAL_SITES})
🕐 Hoàn thành: $(date '+%d/%m/%Y %H:%M')"
fi
Bước 2: Cấp quyền và test
chmod +x /root/wordfence-scan.sh
/root/wordfence-scan.sh
Bước 3: Sau khi test okie thì hãy thêm vào cronjob để scan tự động nhé
crontab -e
0 2 * * * /root/wordfence-scan.sh >> /root/scans/cron.log 2>&1
Hoàn thành rồi đó. Chúc các bạn bảo vệ tốt cho site của mình!
- Bình luận



