Make WordPress Core

Opened 5 months ago

Last modified 8 weeks ago

#64256 new enhancement

Add HTTP 500 status code for WordPress critical error messages

Reported by: swissky's profile swissky Owned by:
Milestone: 7.1 Priority: normal
Severity: normal Version:
Component: Bootstrap/Load Keywords: has-patch changes-requested has-test-info has-screenshots
Focuses: Cc:

Description

Summary

Ensure HTTP 500 status code is always returned for WordPress critical error messages

Component

Bootstrap/Load (or Site Health)

Description

Currently, when WordPress displays the critical error message "There has been a critical error on this website.", it does not consistently return an HTTP status code. While the documentation mentions that fatal errors "typically" return HTTP 500, there is no guarantee that monitoring systems can reliably detect these errors.

Problem

Monitoring tools cannot reliably detect WordPress critical errors because:

  • No HTTP status code is guaranteed to be returned
  • The response may appear as HTTP 200 (success) to monitoring systems
  • Critical errors go undetected until manual inspection
  • Existing documentation mentions "typically 500" but doesn't guarantee monitoring-friendly behavior

Related Tickets

Related to ticket #44458 (WSOD - White Screen of Death) which addresses error handling improvements.

Proposed Solution

Ensure that WordPress always sets HTTP status code 500 when displaying critical error messages, regardless of whether a custom php-error.php template exists or not. This ensures:

  • Monitoring systems can automatically detect critical errors
  • HTTP status codes correctly reflect the error state
  • Standard HTTP error handling works as expected
  • Consistent behavior across all WordPress installations

Implementation Options

Option 1: Always set HTTP 500

Modify _default_wp_die_handler() in wp-includes/functions.php to always set HTTP status code 500 when displaying critical error messages, even if headers were already sent.

Option 2: Filter/Feature Flag

Add an optional filter force_500_on_fatal_error that allows forcing HTTP status code 500 for fatal errors, ensuring backward compatibility while enabling monitoring-friendly behavior.

Option 3: Ensure php-error.php sets status code

Document and ensure that custom php-error.php templates should set HTTP status codes, and provide a default implementation that always sets 500.

Current Behavior

  • class-wp-fatal-error-handler.php sets 'response' => 500 in args (line 212)
  • _default_wp_die_handler() calls status_header($parsed_args['response']) (line 3884)
  • However, this only works if !did_action('admin_head') and headers haven't been sent
  • Custom php-error.php templates may not set status codes

Testing

Tested scenarios:

  • cURL to verify HTTP status code: curl -I https://example.com/test-fatal-error.php
  • Browser developer tools to check response headers
  • Monitoring systems to verify error detection
  • Cases where headers were already sent

Backward Compatibility

This change should be backward compatible:

  • Only affects HTTP status codes, not error messages
  • Doesn't change existing error handling logic
  • Can be implemented as opt-in via filter if needed

Use Case

Monitoring systems rely on HTTP status codes to detect server errors. Currently, WordPress critical errors may not return HTTP 500, causing:

  • False negatives in monitoring alerts
  • Delayed detection of critical issues
  • Inconsistent error reporting

References

  • WordPress documentation mentions fatal errors "typically" return HTTP 500
  • Related to ticket #44458 (WSOD improvements)
  • Monitoring systems require reliable HTTP status codes for error detection

Attachments (3)

wp-includes-functions.php.patch (1.2 KB) - added by swissky 5 months ago.
64256.patch (1.3 KB) - added by swissky 5 months ago.
Patch Update
Capture d’écran 2026-02-03 à 15.34.37.png (274.8 KB) - added by jsmansart 2 months ago.

Download all attachments as: .zip

Change History (15)

#1 @SergeyBiryukov
5 months ago

Hi there, welcome back to WordPress Trac! Thanks for the ticket.

Looking at the patch, checking if the message contains either critical error or There has been a critical error seems redundant, as the first one should suffice. But more importantly, how would this work for messages translated into other languages?

#2 @swissky
5 months ago

Thank you for the feedback! You're absolutely right on both points. I've updated the patch to address these issues.

Changes Made

  1. Removed Redundant String Checks: The patch now uses a single, language-independent method instead of checking multiple strings.
  1. Language-Independent Solution: Instead of string matching (which fails with translations), the patch now:
  • Checks for error code 'internal_server_error' - This is the code used by WordPress fatal error handler (see class-wp-fatal-error-handler.php line 238)
  • Also checks $parsed_args['code'] - In case the code is set there instead
  • Ensures HTTP 500 if response is already 500 - Covers cases where the fatal error handler already set it

Why This Works

  1. Language-independent: Error code 'internal_server_error' is always the same, regardless of language
  2. Uses WordPress internals: The fatal error handler creates WP_Error with this exact code
  3. Covers all cases: Checks both WP_Error code and parsed_args code
  4. No redundancy: Single, clear check

The updated patch is attached.

@swissky
5 months ago

Patch Update

#4 @westonruter
5 months ago

  • Keywords has-patch changes-requested added
  • Milestone changed from Awaiting Review to 7.0

This makes sense to me.

Using this sample plugin code, for example:

<?php
add_action( 'init', function () {
        non_existing_function();
} );

I get a 200 OK response, unexpectedly, even though PHP is dumping out:

Fatal error: Uncaught Error: Call to undefined function non_existing_function()

@swissky Can you open a pull request to facilitate collaboration and automated checks?

#6 @whiteshadow01
5 months ago

Since the author hasn't replied in 2 weeks, I have opened a PR on github as requested by @westonruter

#7 @ozgursar
3 months ago

  • Keywords has-test-info has-screenshots added

Patch Testing Report

Patch Tested: https://github.com/WordPress/wordpress-develop/pull/10579/

Environment

  • WordPress: 7.0-alpha-61215-src
  • PHP: 8.2.29
  • Server: nginx/1.29.4
  • Database: mysqli (Server: 8.4.7 / Client: mysqlnd 8.2.29)
  • Browser: Opera
  • OS: macOS
  • Theme: Twenty Twenty-One 2.7
  • MU Plugins: None activated
  • Plugins:
    • Code Snippets 3.9.4
    • Test Reports 1.2.1

Steps taken

  1. Add the following code via Code Snippets or functions.php to "trigger" a fatal error
add_action( 'init', function () {
        non_existing_function();
} );
  1. View frontend and check document headers using Chrome Dev Tools
  2. Confirm 200 OK Status code
  3. Apply patch
  4. Refresh the homepage and check document headers again
  5. ❌ Patch is failing. It's still displaying the 200 OK status code.

Expected result

  • We are expecting to see HTTP 500 status code when an internal server error occurs

Screenshots/Screencast with results

Before:
https://i.imgur.com/80vz0Yw.png

After:
https://i.imgur.com/6Spv3JY.png

#8 @jsmansart
2 months ago

Reproduction Report

Description

This report does not validates whether the issue can be reproduced.

Environment

  • WordPress: 7.0-alpha-20260203.151637
  • PHP: 8.2.30
  • Server: Apache/2.4.66 (Unix) PHP/8.2.30
  • Database: mysqli (Server: 10.6.24-MariaDB / Client: mysqlnd 8.2.30)
  • Browser: Firefox 148.0
  • OS: macOS
  • Theme: Twenty Twenty-Five 1.4
  • MU Plugins: None activated
  • Plugins:
    • Test Reports 1.2.1

Actual Results

  1. ❌ Error condition doesn't occurs (not reproduced).

Setps taken

  1. Add the following code via Code Snippets or functions.php to "trigger" a fatal error
add_action( 'init', function () {
    non_existing_function();
} );
  1. View frontend and check document headers with inspector
  2. ❌ Status code is already 500

Expected Results

  1. ✅ We should get a status code 200.

Actual Results

  1. ❌ We get a status code 500.

Screeshots

https://core.trac.wordpress.org/raw-attachment/ticket/64256/Capture%20d%E2%80%99%C3%A9cran%202026-02-03%20%C3%A0%2015.34.37.png

Last edited 2 months ago by jsmansart (previous) (diff)

@mindctrl commented on PR #10579:


2 months ago
#9

@shivamdev-lgtm are you able to address the remaining review comments?

@whiteshadow01 commented on PR #10579:


2 months ago
#10

Yes I'll take it up tomorrow @mindctrl

@whiteshadow01 commented on PR #10579:


2 months ago
#11

@mindctrl I have fixed the rest of the comments. Please take a look

#12 @audrasjb
8 weeks ago

  • Milestone changed from 7.0 to 7.1

As we're getting close to beta 1, I'm moving this ticket to 7.1.

Note: See TracTickets for help on using tickets.