---
title: "\"Cannot modify header information - headers already sent\""
url: https://wpministry.co/errors/headers-already-sent/
description: "PHP was asked to send a header, such as a redirect or the login cookie, after output began. Fix the file named after \"output started at\", not the second one."
lang: en-US
---

Skip to content

# How to fix "Cannot modify header information - headers already sent" in WordPress

PHP was asked to send a header, such as a redirect or the login cookie, after part of the page had already gone out. The message names two files. The first, after "output started at", is where the stray output came from, and it is the one to fix.

By

WP Ministry

Published

October 7, 2026

Tested on

WordPress 7.1.4, PHP 8.3.36

## In short

- Nothing is lost. A redirect or a cookie could not be sent, so a login or a page that sends you on did not finish.
- The message names two files. Fix the first, after "output started at". The second is only where PHP noticed.
- The usual cause is a blank line or a space before the opening PHP tag or after a closing one, in wp-config.php or functions.php.
- A PHP warning printed on the page is output too. Send errors to a log and the headers go out again.
- A login screen that says "Cookies are blocked due to unexpected output" is the same fault with the warning hidden.
- Some servers hold output back before sending it, which hides the mistake. It can first show after a move to a new host.

PHP sends a page in two parts: the headers first, then the page itself. Headers carry what a browser acts on before it shows anything, such as a redirect to another address or the cookie that keeps you logged in. PHP's manual says a header "must be called before any actual output is sent", and it counts a blank line in a file as output. This warning means WordPress asked for a header after something, often one blank line, had already gone out.

Nothing has been lost. What failed is the redirect or the cookie, so a login does not hold, or a page that should send you on stays where it is. Remove the stray output and both work again.

## Read the message first

The message names two files, and only the first needs fixing.

text

```
Warning: Cannot modify header information - headers already sent by (output started at /home/example/public_html/wp-config.php:104) in /home/example/public_html/wp-includes/pluggable.php on line 1542
```

- **After "output started at" is the file that sent something too early,** then a colon and the line it came from. Here that is line 104 of `wp-config.php`. This is the file to fix.
- **The second path is only where PHP noticed.** `wp-includes/pluggable.php` holds the WordPress functions that send redirects and set the login cookies, which is why it is named so often. Leave it alone.

| The first path is | What it usually means | Fix |
| --- | --- | --- |
| `wp-config.php` or a theme's `functions.php`, on line 1 or at the very end | A blank line or a space outside the PHP tags. If line 1 looks clean, a byte order mark. | First, then fourth |
| The file and line of another warning or notice printed just above this one | That message is the output | Second |
| A file in a plugin's folder | Code that prints as soon as it is loaded | Third |
| A file inside `wp-admin` or `wp-includes` | A core file that was changed | Fifth |

A server that keeps PHP's errors off the page shows no warning. The login screen says "Error: Cookies are blocked due to unexpected output." in its place, or the page stays blank. How to turn on WordPress debug mode (https://wpministry.co/blog/enable-wordpress-debug-mode/) shows how to send errors to a log, where the same line is written. Paste it into the WordPress error message and debug.log decoder (https://wpministry.co/tools/wordpress-error-decoder/) to see which plugin or theme it points at. It is read in your browser, and nothing is sent.

## Where it goes wrong

A page request passes through each of these in turn. This one comes from **PHP, the language WordPress runs on**.

1. Browser
2. DNS
3. HTTPS
4. CDN or firewall
5. Web server
6. PHP (this error comes from here)
7. WordPress
8. Database and files

## What causes it

- ### A blank line or a space sits outside the PHP tags of a file
  Common
  Anything before the opening PHP tag or after a closing one is sent to the browser as part of the page. It is usually in wp-config.php or a theme's functions.php, and usually arrived with an edit.
  Fix: Remove what is outside the PHP tags
- ### A PHP warning or notice is printed on the page
  Common
  When PHP's errors are shown on the page, each one is output. One raised early, by any plugin or theme, goes out before the headers do.
  Fix: Keep PHP's errors off the page
- ### A plugin prints something as soon as it is loaded
  Sometimes
  Code that prints the moment its file is read, where it should wait until the page is being built, puts its text ahead of the headers.
  Fix: Switch off the plugin the first path names
- ### The file was saved with a byte order mark
  Sometimes
  Some text editors put three bytes that do not show at the start of a file they save as UTF-8. PHP sends them like anything else in front of the opening tag.
  Fix: Remove a byte order mark from the file
- ### One of WordPress's own files was changed
  Rare
  The first path is inside wp-admin or wp-includes. Those files are the same on every site running that version, so one that sends output early has been edited or damaged.
  Fix: Replace WordPress's own files with fresh copies

## How to fix it

### Remove what is outside the PHP tags

- Easy
- Back up first
- About 10 minutes
- Steps tested on WordPress 7.1.4
1. #### Step 1: Open the file the first path names
   Use SFTP or your host's file manager, and a plain text editor, not a word processor.
2. #### Step 2: At the top, make the opening tag the first thing in the file
   If the message gives line 1, delete every blank line and space in front of `<?php`.
3. #### Step 3: At the end, delete the closing tag and everything after it
   If the message gives a line at the end of the file, the output comes after a closing `?>` there. A file that ends in PHP does not need that tag, and with it gone nothing can follow it.
4. #### Step 4: Save, upload and reload
   The warning is gone, and the login or the redirect works.

With SSH access to a Linux server, run these from the folder that holds `wp-config.php`, with the path of your file in place of `wp-config.php`. The first shows the top of the file with its hidden characters. Each `$` marks the end of a line.

bash

```
head -n 3 wp-config.php | cat -A
```

A clean file starts `<?php$`. A `$` alone is a blank line. Keep a copy of the file, then delete the blank lines above the first line that has anything on it, and the spaces in front of that line:

bash

```
cp wp-config.php ~/wp-config-before.php
sed -i '/[^[:space:]]/,$!d' wp-config.php
sed -i '1s/^[[:space:]]*//' wp-config.php
```

For the end of the file, print from a few lines above the line in the message:

bash

```
sed -n '100,$p' wp-config.php | cat -A
```

If the closing `?>` is on line 103, delete from that line to the end, then have PHP check the file:

bash

```
cp wp-config.php ~/wp-config-before.php
sed -i '103,$d' wp-config.php
php -l wp-config.php
```

It answers "No syntax errors detected" when PHP can read the file.

**To undo it:** Upload the copy of the file you downloaded before you changed it.

### Keep PHP's errors off the page

- Easy
- Back up first
- About 10 minutes
- Steps tested on WordPress 7.1.4

When the page shows another warning or notice above this one, and "output started at" names the file and line that message does, the message itself is the output. The fault it reports may be small. Printing it is what breaks the login.

1. #### Step 1: Send errors to a log instead of the page
   In `wp-config.php`, replace the `WP_DEBUG` line with the three lines the debug mode guide (https://wpministry.co/blog/enable-wordpress-debug-mode/) gives, or run these. `WP_DEBUG_DISPLAY` set to `false` does nothing unless `WP_DEBUG` is `true`.
   bash Copy

   ```
   wp config set WP_DEBUG true --raw
   wp config set WP_DEBUG_LOG true --raw
   wp config set WP_DEBUG_DISPLAY false --raw
   ```

2. #### Step 2: Reload the page
   The headers are sent again. The message that was on the page is now in `wp-content/debug.log`.
3. #### Step 3: Deal with what the log names
   The path in the line names the plugin or theme. Update it, or send the line to its author. The debug mode guide covers turning the log off again and deleting it.

**To undo it:** Set WP_DEBUG back to false and remove the other two lines.

### Switch off the plugin the first path names

- Easy
- Low risk
- About 10 minutes
- Steps tested on WordPress 7.1.4

When the first path is in a plugin's folder, that plugin prints something the moment it is loaded. WordPress checks for this when a plugin is activated, and the Plugins screen then says the plugin generated "unexpected output during activation".

1. #### Step 1: Deactivate the plugin
   The folder after `wp-content/plugins/` in the path is the plugin's. With WP-CLI:
   Without it, use the Plugins screen. If you cannot log in, how to deactivate WordPress plugins when you are locked out of the dashboard (https://wpministry.co/blog/deactivate-wordpress-plugins-locked-out/) gives the ways to do it from outside, such as renaming the plugin's folder over SFTP.
   bash Copy

   ```
   wp plugin deactivate plugin-folder-name
   ```

2. #### Step 2: Reload the page
   If the warning is gone, you have found the cause. Update the plugin, or tell its author what the message said.

If the first path is a line of `functions.php` where you pasted a snippet, take the snippet out: the first fix on the syntax error page (https://wpministry.co/errors/syntax-error-unexpected/) shows how. Before you add it again, move whatever it prints into a function attached to a hook such as `wp_head`, so that it runs while the page is being built.

**To undo it:** Activate the plugin again.

### Remove a byte order mark from the file

- Takes care
- Back up first
- About 10 minutes
- Steps tested on WordPress 7.1.4

A byte order mark is three bytes, `EF BB BF`, that some editors put at the start of a file they save as UTF-8. It is not a character you can see, so line 1 looks clean. PHP sends it to the browser like anything else in front of `<?php`.

1. #### Step 1: Save the file again without the mark
   Open it in a code editor, save it with plain UTF-8 as the encoding and not one with BOM in its name, and upload it.
2. #### Step 2: Or look for the mark over SSH
   Print the file's first three bytes:
   `ef bb bf` is a byte order mark. A clean file gives `3c 3f 70`, the characters `<?p`.
   bash Copy

   ```
   head -c 3 wp-content/themes/your-theme/functions.php | od -An -tx1
   ```

3. #### Step 3: Strip it
   On a Linux server, this removes those three bytes from the start of line 1 and changes nothing else:
   Print the first three bytes again. They are now `3c 3f 70`.
   bash Copy

   ```
   sed -i '1s/^\xEF\xBB\xBF//' wp-content/themes/your-theme/functions.php
   ```

### Replace WordPress's own files with fresh copies

- Takes care
- Back up first
- About 20 minutes

When the first path is inside `wp-admin` or `wp-includes`, do not repair the file by hand. Replace it.

1. #### Step 1: See which files differ from the originals
   It compares the installed files with WordPress.org's checksums for your version and lists each one that does not match.
   bash Copy

   ```
   wp core verify-checksums
   ```

2. #### Step 2: Download the same version again
   `--force` overwrites the files that are there. `--skip-content` leaves out the default themes and plugins. Add `--locale=` with your site's language code if WordPress is not installed in US English.
   bash Copy

   ```
   wp core download --version=$(wp core version) --skip-content --force
   ```

3. #### Step 3: Without WP-CLI, replace the one file
   Download your version of WordPress from WordPress.org, unzip it, and upload the file the message names over the one on the server.

If nobody edited that file, see how to check whether your WordPress site has been hacked (https://wpministry.co/blog/check-wordpress-site-hacked/).

## When to get help

If the file the message names is clean at both ends, has no byte order mark, and the warning is still there, the output comes from code that runs inside that file, and finding it means reading the code. The same holds if the warning returns each time a plugin or theme updates.

## Common questions

Why did this start after I moved to a new host?

The stray output was probably there all along. PHP can hold a page back in a buffer before sending it, a setting named `output_buffering`, and while the page is held back a header can still be added. The setting is off by default, and the settings file PHP ships for live servers sets a buffer of 4096 bytes. A blank line that did no harm with the buffer on gives this warning with it off. Fix the file, and the site works on both.

Is it safe to delete the closing tag at the end of a PHP file?

Yes, when the file ends in PHP, as `wp-config.php` and `functions.php` do. PHP's manual and WordPress's coding standards both prefer leaving it out. PHP also swallows one line break straight after a closing `?>`, which is why a file can end with the tag for years and fail only when a second blank line is added.

The warning is gone and something is still wrong. What now?

If the site went down the moment you saved, with a line beginning "Parse error", the edit removed a character PHP needs: see the syntax error page linked above. If the login screen still comes back, with no message, see the login page that keeps refreshing (https://wpministry.co/errors/login-page-refreshing-and-redirecting/). If pages are blank, see the white screen of death (https://wpministry.co/errors/white-screen-of-death/).

## More on this subject

- WordPress errors and troubleshooting: https://wpministry.co/topics/wordpress-troubleshooting/
- Guide How to update the PHP version of a WordPress site: https://wpministry.co/blog/update-php-version-wordpress/
- Guide WordPress uptime monitoring: what a check sees and what it misses: https://wpministry.co/blog/wordpress-uptime-monitoring/
- Glossary term Database table prefix: https://wpministry.co/glossary/database-table-prefix/
- Glossary term debug.log: https://wpministry.co/glossary/debug-log/
- Glossary term Heartbeat API: https://wpministry.co/glossary/heartbeat-api/
- Guide How to change WordPress permalinks without breaking your old links: https://wpministry.co/blog/change-wordpress-permalinks-safely/

## Would you rather we fixed it?

Quick Fix is $49. One issue, one site, up to about an hour. No fix, no fee. 30-day warranty.

More about this work: One-time WordPress fix (https://wpministry.co/services/one-time-fix/)

May we measure how this site is used?

A yes notes the pages of this visit and loads Google Analytics. A no keeps and loads nothing. See the cookies page (https://wpministry.co/legal/cookies/).

If you say yes, we note the pages you look at during this visit and load Google Analytics. If you say no, nothing is kept and nothing is loaded. The site works the same either way. The cookies page (https://wpministry.co/legal/cookies/) has the details.

## Structured data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://wpministry.co/#organization",
      "name": "WP Ministry",
      "url": "https://wpministry.co/",
      "logo": "https://wpministry.co/brand/logo.png",
      "address": {
        "@type": "PostalAddress",
        "streetAddress": "1309 Coffeen Ave STE 1200",
        "addressLocality": "Sheridan",
        "addressRegion": "WY",
        "postalCode": "82801",
        "addressCountry": "US"
      },
      "sameAs": [
        "https://www.linkedin.com/company/wp-ministry/",
        "https://x.com/wpministryco",
        "https://www.instagram.com/wpministryco/",
        "https://www.facebook.com/wpministryco",
        "https://www.tiktok.com/@wpministryco",
        "https://www.pinterest.com/wpministryco/",
        "https://www.youtube.com/channel/UCOZRuAukk5QSTRPG6BaHl3Q"
      ]
    },
    {
      "@type": "WebSite",
      "@id": "https://wpministry.co/#website",
      "name": "WP Ministry",
      "url": "https://wpministry.co/",
      "inLanguage": "en-US",
      "publisher": {
        "@id": "https://wpministry.co/#organization"
      }
    },
    {
      "@type": "TechArticle",
      "@id": "https://wpministry.co/errors/headers-already-sent/#article",
      "headline": "How to fix \"Cannot modify header information - headers already sent\" in WordPress",
      "description": "PHP was asked to send a header, such as a redirect or the login cookie, after output began. Fix the file named after \"output started at\", not the second one.",
      "url": "https://wpministry.co/errors/headers-already-sent/",
      "mainEntityOfPage": "https://wpministry.co/errors/headers-already-sent/",
      "inLanguage": "en-US",
      "image": "https://wpministry.co/og/errors/headers-already-sent.png",
      "author": {
        "@id": "https://wpministry.co/#organization"
      },
      "publisher": {
        "@id": "https://wpministry.co/#organization"
      },
      "datePublished": "2026-10-07T00:00:00.000Z",
      "dateModified": "2026-10-07T00:00:00.000Z"
    },
    {
      "@type": "BreadcrumbList",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "name": "Home",
          "item": "https://wpministry.co/"
        },
        {
          "@type": "ListItem",
          "position": 2,
          "name": "Error fixes",
          "item": "https://wpministry.co/errors/"
        },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "How to fix \"Cannot modify header information - headers already sent\" in WordPress"
        }
      ]
    }
  ]
}
```