WP-Cron là cơ chế lập lịch tác vụ mặc định của WordPress, được dùng cho mọi thứ từ kiểm tra cập nhật, xoá spam comment cho đến gửi email hàng loạt. Tuy nhiên, hầu hết developer khi mới tìm hiểu đều hiểu nhầm WP-Cron giống với Cron của hệ điều hành. Thực tế, WP-Cron chỉ là một cơ chế giả lập chạy khi website có request — và chính thiết kế này gây ra vô số vấn đề về hiệu năng. Bài viết này sẽ giải thích chi tiết cách WP-Cron hoạt động, vì sao nó làm chậm website, và cách chuyển sang System Cron thật một cách an toàn.
WP-Cron là gì và vì sao nó không phải Cron thật
Cron thật (System Cron / crontab) là tiến trình chạy nền của hệ điều hành, được kernel kích hoạt đúng theo lịch biểu — ví dụ mỗi 5 phút, mỗi ngày lúc 2 giờ sáng. Nó chạy bất kể website có ai truy cập hay không, và không phụ thuộc vào web server.
WP-Cron thì hoàn toàn khác. WordPress không có tiến trình nền riêng. Thay vào đó, mỗi khi có một request (người dùng truy cập, bot crawl, request AJAX…), WordPress kiểm tra option cron trong database. Nếu phát hiện có event đã quá hạn chạy, nó gửi một request HTTP nội bộ (qua wp_remote_post()) đến wp-cron.php để thực thi các tác vụ đó ngay trong lần request ấy.
Hệ quả trực tiếp: nếu website của bạn ít traffic hoặc bị cache toàn bộ ở tầng CDN (không request nào chạm tới PHP), thì WP-Cron gần như không bao giờ chạy. Email không được gửi, scheduled post không được publish đúng giờ, bản cập nhật bị trì hoãn.
Vì sao WP-Cron làm chậm website
Ngược lại, khi website có nhiều traffic, WP-Cron lại gây ra vấn đề hiệu năng theo 3 hướng:
- Request chặn nhau: Khi một request trigger
wp-cron.php, WordPress set lock_transient_doing_cronđể các request khác không chạy cron đồng thời. Nhưng nếu tác vụ chạy lâu (ví dụ gửi 500 email), request đó bị treo, vàwp_remote_post()chờ response — làm tăng đáng kể thời gian phản hồi cho chính request của người dùng đầu tiên kích hoạt. - Chi phí CPU lan toả: Mỗi request đều phải query database để kiểm tra option cron. Trên hosting dùng chung, 10 website trên cùng một server đều trigger cron riêng của mình, nhân lên lượng CPU tiêu tốn.
- Plugin nặng queue: WooCommerce, WP Mail SMTP, các plugin booking… đăng ký rất nhiều event. Khi nhiều event quá hạn cùng lúc, một request duy nhất phải xử lý cả đống tác vụ — dẫn đến timeout hoặc tác vụ bị bỏ lỡ.
Thực tế tại các dự án WordPress có WooCommerce, tôi từng thấy thời gian phản hồi trang tăng thêm 1-2 giây chỉ vì một event gửi email bị quá hạn và chạy ngay trong request đầu tiên sau đó. Đây là lý do vì sao việc hiểu rõ và kiểm soát WP-Cron là kỹ năng bắt buộc với bất kỳ ai vận hành WordPress ở mức production.
Kiểm tra WP-Cron đang chạy như thế nào
Trước khi thay đổi bất cứ điều gì, hãy kiểm tra trạng thái WP-Cron hiện tại bằng WP-CLI. Đây là bước an toàn, chỉ đọc, không thay đổi dữ liệu:
# Liệt kê tất cả cron event đang được lên lịch
wp cron event list
# Chỉ xem event đã quá hạn chạy (due now)
wp cron event list --due-now
# Kiểm tra xem WP-Cron có hoạt động không
wp cron test
# Xem chi tiết từng event (hook, lần chạy tới, interval)
wp cron event list --fields=hook,next_run,recurrence
Nếu lệnh wp cron test trả về Success: WP-Cron spawning is enabled, nghĩa là cơ chế mặc định đang bật. Lúc này bạn nên xem --due-now có nhiều event không — đó là dấu hiệu website đang “nợ” tác vụ vì không đủ traffic để kích hoạt cron.
Chuyển sang System Cron thật với DISABLE_WP_CRON
Cách chuẩn để tách WP-Cron khỏi request lifecycle là tắt cơ chế tự spawn và để cron của hệ điều hành gọi wp-cron.php theo lịch cố định. Trước tiên, thêm hằng số sau vào wp-config.php, đặt ngay trước dòng /* That's all, stop editing! */:
// wp-config.php - Tắt WP-Cron tự spawn
// Sau khi thêm, WP-Cron CHỈ chạy khi được gọi từ System Cron
define( 'DISABLE_WP_CRON', true );
Lưu ý quan trọng: ngay sau khi thêm dòng này mà chưa cấu hình crontab, mọi tác vụ định kỳ (kiểm tra cập nhật, gửi email, scheduled post) sẽ ngừng hoàn toàn. Hãy cấu hình crontab ở bước tiếp theo trước, hoặc thực hiện cả hai trong cùng một phiên làm việc.
Cấu hình crontab gọi wp-cron.php
Chạy crontab -e (user chạy web server, thường là runcloud hoặc www-data) và thêm dòng sau:
# Chạy WP-Cron mỗi 5 phút, không ghi log
*/5 * * * * curl -s -o /dev/null "https://chuyendev.com/wp-cron.php?doing_wp_cron"
# Nếu server chặn loopback HTTP, dùng wget --spider
# */5 * * * * wget -q -O /dev/null "https://chuyendev.com/wp-cron.php?doing_wp_cron"
Khoảng cách 5 phút là hợp lý cho hầu hết website: đủ thường xuyên để email và scheduled post chạy đúng giờ, nhưng không gây áp lực CPU. Với trang ít tác vụ, có thể dùng 15 phút.
Cách tốt hơn: dùng WP-CLI trong crontab
Gọi qua HTTP vẫn phụ thuộc vào web server và có thể bị chặn bởi firewall hoặc security plugin. Cách đáng tin cậy hơn là dùng WP-CLI trực tiếp — chạy PHP thuần, không qua HTTP:
# Chạy tất cả event đã đến hạn bằng WP-CLI (không qua HTTP)
*/5 * * * * cd /home/runcloud/webapps/cdev && wp cron event run --due-now --allow-root >> /var/log/wp-cron.log 2>&1
# Chạy riêng một event cụ thể (hữu ích cho tác vụ nặng)
# 0 3 * * * cd /home/runcloud/webapps/cdev && wp cron event run woocommerce_cleanup_sessions --allow-root
Flag --due-now chỉ chạy các event đã đến hạn, tránh chạy lại event chưa tới giờ. Redirect output vào log file giúp bạn truy vết khi có tác vụ lỗi. Nếu bạn mới bắt đầu làm quen với WP-CLI, bài viết hardening WordPress với WP-CLI cũng dùng chính kỹ thuật cron.daily để tự động hoá việc kiểm tra bảo mật — một ví dụ thực tế về việc kết hợp system cron vào quy trình vận hành.
Tạo lịch biểu tùy chỉnh trong code
Khi plugin hoặc theme cần chạy tác vụ định kỳ, bạn đăng ký event bằng wp_schedule_event(). Hàm này nhận 3 tham số: thời điểm chạy lần đầu (timestamp), khoảng lặp lại, và tên hook:
// Đăng ký event chạy mỗi giờ, lần đầu sau 5 phút nữa
if ( ! wp_next_scheduled( 'my_custom_hourly_task' ) ) {
wp_schedule_event( time() + 300, 'hourly', 'my_custom_hourly_task' );
}
// Hàm xử lý - gắn vào hook đã đăng ký
add_action( 'my_custom_hourly_task', 'run_my_custom_hourly_task' );
function run_my_custom_hourly_task() {
// Logic xử lý: gửi email, đồng bộ API, dọn dữ liệu...
}
WordPress chỉ có sẵn 4 interval: hourly, twicedaily, daily và weekly. Nếu cần lịch biểu riêng (mỗi 30 phút, mỗi 2 ngày), dùng filter cron_schedules để đăng ký interval mới:
// Đăng ký interval 30 phút
add_filter( 'cron_schedules', 'my_custom_cron_interval' );
function my_custom_cron_interval( $schedules ) {
$schedules['every_30_minutes'] = array(
'interval' => 30 * MINUTE_IN_SECONDS,
'display' => __( 'Mỗi 30 phút', 'textdomain' ),
);
return $schedules;
}
// Dùng interval vừa đăng ký
if ( ! wp_next_scheduled( 'my_sync_task' ) ) {
wp_schedule_event( time(), 'every_30_minutes', 'my_sync_task' );
}
Xây dựng script giám sát WP-Cron tự động
Một trong những vấn đề khó chịu nhất khi vận hành WordPress là cron chết âm thầm: website vẫn chạy bình thường nhưng email, backup, scheduled post đều ngừng. Để phát hiện sớm, hãy tạo một script giám sát chạy định kỳ và cảnh báo qua Telegram khi có bất thường:
#!/bin/bash
# /usr/local/bin/wp-cron-health.sh - Kiểm tra sức khỏe WP-Cron
WP_PATH="/home/runcloud/webapps/cdev"
TELEGRAM_BOT="123456789:YOUR_BOT_TOKEN"
TELEGRAM_CHAT="YOUR_CHAT_ID"
# Đếm số event quá hạn chưa chạy (nợ cron)
DUE_COUNT=$(wp --path="$WP_PATH" --allow-root cron event list --due-now --format=count 2>/dev/null)
# Lấy thời điểm event cũ nhất còn nợ
OLDEST=$(wp --path="$WP_PATH" --allow-root cron event list --due-now --fields=hook,next_run_relative --format=csv 2>/dev/null | tail -n +2 | head -5)
if [ "$DUE_COUNT" -gt 10 ]; then
MESSAGE="⚠️ WP-Cron nghẽn tại $(hostname): $DUE_COUNT event quá hạn
$OLDEST"
curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT/sendMessage" -d chat_id="$TELEGRAM_CHAT" -d text="$MESSAGE" -o /dev/null
fi
# Ghi log hằng ngày để đối chiếu sau
echo "$(date '+%F %T') due=$DUE_COUNT" >> /var/log/wp-cron-health.log
Đăng ký script vào crontab chạy mỗi 10 phút:
# Chạy script giám sát mỗi 10 phút
*/10 * * * * bash /usr/local/bin/wp-cron-health.sh
# Nếu muốn log riêng từng lần chạy, thêm redirect
# */10 * * * * bash /usr/local/bin/wp-cron-health.sh >> /var/log/wp-cron-health-run.log 2>&1
Ngưỡng cảnh báo DUE_COUNT -gt 10 có thể điều chỉnh tuỳ quy mô website. Ý tưởng cốt lõi: thay vì chờ user phàn nàn “email không gửi được”, hệ thống tự báo cho bạn trước khi vấn đề lan rộng.
Khi nào nên dùng Action Scheduler thay vì WP-Cron
WP-Cron phù hợp với tác vụ ngắn, chạy nhanh (dưới vài giây). Với tác vụ nặng — gửi 10.000 email, tạo ảnh thumbnail hàng loạt, đồng bộ hàng nghìn sản phẩm — WP-Cron thường timeout giữa chừng, và event bị đánh dấu là đã chạy dù chưa hoàn thành. Đây là lúc cần Action Scheduler (thư viện queue mặc định của WooCommerce): tác vụ được chia nhỏ, xếp hàng, chạy nền theo từng batch và tự retry khi lỗi.
Nguyên tắc lựa chọn đơn giản: dưới 5 giây thì WP-Cron, lâu hơn thì Action Scheduler hoặc queue riêng. Kết hợp cả hai: Action Scheduler vẫn dựa trên cơ chế WP-Cron để kích hoạt worker, nên việc chuyển sang System Cron ở trên cũng giúp queue chạy ổn định hơn rõ rệt.
Case study: chuyển WooCommerce sang System Cron
Trong quá trình triển khai dự án cửa hàng WooCommerce cho khách hàng tại Code Tốt, chúng tôi gặp tình trạng trang checkout chậm 2-3 giây vào khung giờ cao điểm. Kiểm tra log phát hiện hàng chục event của WooCommerce (cleanup sessions, stock sync, email queue) bị dồn lại và chạy ngay trong request đầu tiên sau đó — chính là request mở trang checkout của khách.
Giải pháp áp dụng đúng quy trình ở trên: tắt DISABLE_WP_CRON, cấu hình crontab gọi WP-CLI mỗi 5 phút, và thêm script giám sát Telegram. Kết quả: thời gian phản hồi trang checkout giảm 40%, không còn email gửi trễ, và đội vận hành nhận được cảnh báo sớm mỗi khi queue bị nghẽn. Kinh nghiệm này sau đó được Khôi Pro đúc kết thành quy trình chuẩn áp dụng cho mọi dự án WordPress tại Code Tốt.
Kết luận
WP-Cron tiện lợi nhưng không phải giải pháp đúng cho production. Việc chuyển sang System Cron chỉ mất 5 phút nhưng mang lại lợi ích dài hạn: request nhanh hơn, tác vụ chạy đúng giờ, và dễ dàng giám sát. Hãy bắt đầu bằng wp cron event list --due-now để xem website của bạn đang “nợ” bao nhiêu tác vụ.
Bài viết liên quan: