Bỏ qua tới nội dung
Máy chủ & .htaccess

Bài viết WordPress trả về lỗi 404 nhưng trang chủ vẫn chạy bình thường

Nếu trang chủ tải bình thường nhưng mọi bài viết và trang đơn lẻ đều trả về lỗi 404, thì các rule rewrite URL mà WordPress dựa vào đã bị thiếu hoặc hỏng trong file của bạn

Đã đăng

Nếu trang chủ tải bình thường nhưng mọi bài viết và trang đơn lẻ đều trả về lỗi 404, thì các rule rewrite URL mà WordPress dựa vào đã bị thiếu hoặc hỏng trong file .htaccess của bạn. Cách khắc phục nhanh nhất: vào Settings → Permalinks trong wp-admin và bấm Save Changes mà không thay đổi gì cả. Thao tác đó buộc WordPress tạo lại khối rewrite. Nếu lỗi 404 vẫn còn, thì hoặc là file .htaccess không ghi được bởi web server, hoặc Apache được cấu hình để bỏ qua nó, hoặc bạn đang chạy Nginx (vốn không hề dùng .htaccess).

Vì sao trang chủ chạy được mà bài viết thì không

Trang chủ chạy được vì một request tới / được phục vụ trực tiếp từ index.php ở thư mục gốc. Không cần rewrite gì hết.

Một request tới /2026/my-post/ thì khác. Đường dẫn đó không phải là một thư mục thật trên ổ đĩa. Để WordPress trả lời được nó, Apache phải định tuyến request nội bộ về index.php, rồi WordPress đọc REQUEST_URI, đối chiếu nó với các rule rewrite được lưu trong option rewrite_rules trong database, giải các query var (WP::parse_request()), và tải đúng bài viết.

Chỗ bàn giao từ Apache sang index.php chính là phần hay hỏng. Nó phụ thuộc hoàn toàn vào khối này trong .htaccess:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

Hai dòng RewriteCond nói rằng: nếu đường dẫn được yêu cầu không phải là một file thật (!-f) và không phải là một thư mục thật (!-d), thì gửi nó tới index.php. Bỏ khối đó đi thì Apache sẽ đi tìm một thư mục /2026/my-post/ theo đúng nghĩa đen, không thấy, và trả về lỗi 404 của chính nó — trước khi WordPress kịp nhìn thấy request.

Đây là phần mà hầu hết các hướng dẫn bỏ qua. Khi bạn bấm Save ở màn hình Permalinks, WordPress gọi flush_rewrite_rules(). Trên Apache với một file .htaccess ghi được, thao tác đó kích hoạt save_mod_rewrite_rules(), hàm này tạo lại khối phía trên qua WP_Rewrite::mod_rewrite_rules() và ghi ngược lại bằng insert_with_markers().

insert_with_markers() chỉ đụng vào các dòng nằm giữa # BEGIN WordPress# END WordPress. Mọi thứ bên ngoài các marker đó được giữ nguyên. Mọi thứ bên trong bị bỏ đi và ghi lại. Các phiên bản WordPress mới hơn thậm chí còn in luôn cảnh báo ngay trong file:

Any changes to the directives between these markers will be overwritten.

Vậy nên câu “WordPress ghi đè file .htaccess của tôi” là đúng nhưng phải hiểu cho hẹp: nó chỉ ghi lại khối được đánh dấu của riêng nó, và chỉ khi file ghi được. Nếu file không ghi được, thao tác lưu âm thầm không làm gì với ổ đĩa cả. WordPress hiện một thông báo màu vàng trên màn hình Permalinks bảo bạn dán các rule vào thủ công, và nếu bạn bỏ lỡ thông báo đó thì khối rewrite của bạn vẫn thiếu và mọi bài viết cứ tiếp tục trả về 404.

Cách khắc phục, theo thứ tự

1. Lưu lại permalink. Settings → Permalinks → Save Changes. Không cần sửa gì cả. Thao tác này chạy lại flush và ghi lại khối. Nó xử lý được phần lớn các trường hợp, kể cả các rule cũ còn sót lại sau khi migrate.

2. Kiểm tra xem .htaccess có tồn tại và ghi được không. Nó nằm cùng thư mục với wp-config.php. Nó là một dotfile, nên hãy bật “show hidden files” trong client FTP của bạn hoặc dùng:

ls -la /path/to/wordpress/.htaccess

Nếu nó bị thiếu, hãy tạo mới và dán khối đúng vào. Nếu bản cài của bạn nằm trong một thư mục con, thì RewriteBase và đích rewrite cuối cùng phải khớp với đường dẫn đó — làm sai chỗ này là nguyên nhân phổ biến nhất khiến một khối dán tay vẫn bị 404. Công cụ tạo .htaccess của chúng tôi dựng ra chính xác khối cho đường dẫn cài đặt của bạn để bạn khỏi phải đoán.

3. Xác nhận Apache được phép đọc .htaccess. Nếu virtual host có AllowOverride None, Apache bỏ qua hoàn toàn file này bất kể nội dung của nó là gì. Bạn cần AllowOverride All (hoặc ít nhất là FileInfo) trên thư mục WordPress. Cái này nằm trong cấu hình server, không phải .htaccess, nên nó đòi hỏi quyền truy cập server:

<Directory /var/www/html>
    AllowOverride All
</Directory>

4. Xác nhận mod_rewrite đã được bật. Trên Debian/Ubuntu:

sudo a2enmod rewrite
sudo systemctl restart apache2

5. Trên Nginx, không có .htaccess. Các rule rewrite nằm trong server block thay vì đó:

location / {
    try_files $uri $uri/ /index.php?$args;
}

Chẩn đoán nhanh: lỗi 404 này là của ai?

Nhìn vào trang 404. Nếu đó là một trang lỗi trơ trụi không có style của Apache hoặc của host, thì Apache chưa bao giờ chạm tới WordPress — đó là vấn đề .htaccess hoặc cấu hình server (bước 2–4). Nếu đó là trang 404 có style của theme bạn, thì WordPress đang nhận được request nhưng không đối chiếu được — các rule trong database đã cũ, và lưu lại permalink (bước 1) là cách khắc phục. Chỉ riêng việc phân biệt được điểm này thôi đã giúp phần lớn mọi người tiết kiệm cả tiếng đồng hồ mò mẫm.

Những điều không nên làm

Đừng vội tắt hết plugin. Triệu chứng thuần túy kiểu “bài viết 404, trang chủ chạy” là vấn đề rewrite, không phải xung đột plugin. Tắt plugin là câu trả lời theo phản xạ trên các diễn đàn và nó gần như không bao giờ động tới chuyện này.

Đừng chmod 777 file .htaccess của bạn. WordPress cần file ghi được bởi web server user, đây không phải là một với world-writable. 644 với đúng chủ sở hữu là ổn và an toàn; 777 là một lỗ hổng bảo mật mà đằng nào cũng không sửa được lỗi định tuyến.

Đừng dán rule rewrite tùy chỉnh vào bên trong các marker # BEGIN WordPress / # END WordPress. Chúng sẽ bị xóa sạch lần tới khi ai đó lưu permalink. Hãy đặt các directive tùy chỉnh phía trên dòng # BEGIN WordPress.

Đừng cài lại WordPress. Các file lõi vẫn ổn. Đây chỉ là một khối văn bản bị thiếu, không phải bản cài bị hỏng.

Đừng đổ lỗi cho theme. Định tuyến permalink là phần lõi của WordPress. Một theme không thể gây ra nó và cũng không thể sửa nó.

Vẫn bí?

Nếu bạn đã lưu lại permalink, đã xác nhận khối rewrite có mặt và ghi được, và đã đặt AllowOverride All, thì thủ phạm còn lại là một plugin bảo mật hoặc một rule ở tầng host đang tạo lại hoặc khóa .htaccess sau lưng bạn. Hãy kiểm tra timestamp sửa đổi của file ngay sau khi lưu permalink — nếu nó không thay đổi, thì có gì đó đang chặn thao tác ghi. Tạo lại một khối sạch bằng công cụ tạo .htaccess, dán vào, và test lại bằng một lần hard refresh.