“Cannot modify header information - headers already sent by” in WordPress: find the cause and fix it

By CompatNav · Published · Last reviewed · 4 min read

Short answer

WordPress or a plugin tried to send a header (a redirect, a cookie, a login) after something was already printed on the page. Headers must come first, so PHP refuses and warns you. The part that matters is “output started at”: the file and line that printed too early. It’s usually one of two things: a blank line or space before <?php or after ?> in a file, often functions.php or wp-config.php; or a PHP message shown on screen, very often a deprecation notice after a PHP upgrade. Remove the blank line, or stop showing PHP messages on the page and log them instead.

How to read the message

Output on PHP 8.5.11

Warning: 1Cannot modify header information - headers already sent by (2output started at /var/www/html/wp-content/plugins/contact-box/contact-box.php:7) in /var/www/html/wp-content/plugins/contact-box/contact-box.php 3on line 4
Form sent
  1. A header (here a redirect) came too late.
  2. The culprit: the file and line that printed something first.
  3. Where the header was sent. In WordPress this is often wp-includes/pluggable.php: not the cause.

php.net explains the rule: “Remember that header() must be called before any actual output is sent, either by normal HTML tags, blank lines in a file, or from PHP.” (php.net) WordPress sends headers for redirects, cookies and logins, so this shows up as a login that loops, a form that doesn’t take you to the next page, or settings that don’t save.

Cause 1: a blank line outside the PHP code

This is the example above: a plugin file ends its PHP code with ?>, then a blank line, then more PHP. That blank line is printed to the visitor, and the redirect after it fails, on every PHP version:

wp-content/plugins/contact-box/contact-box.php

<?php
function contact_box_redirect() {
    // WordPress's wp_redirect() sends the same header
    header('Location: /thank-you/');
}
?>

<?php
contact_box_redirect();
echo "Form sent\n";

Output on PHP 7.4.33

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/wp-content/plugins/contact-box/contact-box.php:7) in /var/www/html/wp-content/plugins/contact-box/contact-box.php on line 4
Form sent

Output on PHP 8.5.11

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/wp-content/plugins/contact-box/contact-box.php:7) in /var/www/html/wp-content/plugins/contact-box/contact-box.php on line 4
Form sent

Look at the file and line after “output started at”. A blank line or space before <?php at the top, or after ?> at the end, is the cause. The same goes for an invisible byte-order mark (BOM) that some text editors add at the very start of a file.

Cause 2: a PHP message printed on the page

Anything printed counts, including a PHP message. After a PHP upgrade, plugins often trigger new deprecation notices or warnings. If those are displayed, the first one is “output”, and every header after it fails. Here the redirect fails on PHP 8.5 but not on 8.1, only because 8.5 prints a deprecation notice first:

wp-content/plugins/contact-box/contact-box.php

<?php
class Contact_Form {
    public function __construct() {
        $this->sent = true;
    }
}

$form = new Contact_Form();
// After sending, the plugin sends the visitor to a thank-you page
// (WordPress's wp_redirect() does this with the same header):
header('Location: /thank-you/');
echo "Form sent\n";

Output on PHP 8.1.34

Form sent

Output on PHP 8.5.11

Deprecated: Creation of dynamic property Contact_Form::$sent is deprecated in /var/www/html/wp-content/plugins/contact-box/contact-box.php on line 4

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/wp-content/plugins/contact-box/contact-box.php:4) in /var/www/html/wp-content/plugins/contact-box/contact-box.php on line 11
Form sent

In this case “output started at” points to the line of the notice. WordPress itself says debug display isn’t for live sites: its debug tools “are meant for local testing and staging installs” (developer.wordpress.org).

Fix it

  1. Read “output started at” and open that file at that line (by FTP or your host’s file manager).
  2. A blank line or space outside <?php … ?>? Delete it. In your own functions.php or wp-config.php, the closing ?> at the very end can simply be removed.
  3. A PHP message at that line? Log PHP messages instead of displaying them: the WordPress debug log has the lines for wp-config.php, with WP_DEBUG_DISPLAY set to false. If messages still show with WP_DEBUG off, your host displays them: ask it to turn display_errors off. Once nothing is printed, the headers work again.
  4. Then deal with the message itself: update the plugin or theme it names, or send it to its developer.

If the file belongs to a plugin or theme, don’t edit it yourself: an update would undo the change. Update it, or ask its developer.

Before your next PHP upgrade

Cause 2 is predictable: the deprecation notices a PHP upgrade brings are in the code before you switch. CompatNav, a free plugin on wordpress.org, lists them per plugin and theme for the PHP version you choose (8.0 to 8.5), labelled “Deprecation notice”, so you can update first and switch with fewer surprises. For what each kind of message does to a page: deprecation notice or fatal error.

Key takeaways

  • Ignore the file at the end of the message (often pluggable.php): the culprit is the one after “output started at”.
  • Cause 1: a blank line or space outside <?php … ?>, or an invisible BOM at the start of a file.
  • Cause 2: a PHP warning or deprecation notice printed on the page, typical after a PHP upgrade with WP_DEBUG on.
  • Symptoms: a login that loops, a form that doesn’t redirect, settings that don’t save.
  • Fix: remove the stray output, and log PHP messages instead of displaying them.

Frequently asked questions

The message says pluggable.php. Is WordPress broken?

No. wp-includes/pluggable.php is where WordPress sends redirects and cookies, so it’s where PHP notices the problem. The file that printed too early is the one after “output started at”.

Why did it start after a PHP upgrade?

A newer PHP version adds deprecation notices and turns some notices into warnings. If your site shows PHP messages on screen, the first message printed counts as output, and every header after it fails.

Can I just turn warnings off?

Turning off the display of PHP messages stops them from counting as output, and the headers work again. The messages still deserve a look in the log.

Check your own site before you upgrade

CompatNav is a free WordPress plugin. It reads the code of your plugins and themes on your own server and tells you, in plain words, what will break and what will only show notices on the PHP version you choose. It never changes your code. It can’t see problems that only appear while code runs with real data, so a quick check of your site after the upgrade still matters.

Get CompatNav on wordpress.org How it works

Sources

About the code examples: each output is the real output of the code shown, run with the official PHP builds, without a php.ini, with all errors reported and displayed. Only the file path was replaced by a neutral server path.