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
- Tested on
- WordPress 7.1.3, PHP 8.3.35
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.
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.phpholds 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 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 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.
- Browser
- DNS
- HTTPS
- CDN or firewall
- Web server
- PHP (this error comes from here)
- WordPress
- Database and files
What causes it
A blank line or a space sits outside the PHP tags of a file
CommonAnything 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.
A PHP warning or notice is printed on the page
CommonWhen 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.
A plugin prints something as soon as it is loaded
SometimesCode 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.
The file was saved with a byte order mark
SometimesSome 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.
One of WordPress's own files was changed
RareThe 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.
How to fix it
Remove what is outside the PHP tags
- Easy
- Back up first
- About 10 minutes
- Steps tested on WordPress 7.1.3
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.
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.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.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.
head -n 3 wp-config.php | cat -AA 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:
cp wp-config.php ~/wp-config-before.php
sed -i '/[^[:space:]]/,$!d' wp-config.php
sed -i '1s/^[[:space:]]*//' wp-config.phpFor the end of the file, print from a few lines above the line in the message:
sed -n '100,$p' wp-config.php | cat -AIf the closing ?> is on line 103, delete from that line to the end, then have PHP check the file:
cp wp-config.php ~/wp-config-before.php
sed -i '103,$d' wp-config.php
php -l wp-config.phpIt 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.3
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.
Step 1: Send errors to a log instead of the page
In
wp-config.php, replace theWP_DEBUGline with the three lines the debug mode guide gives, or run these.WP_DEBUG_DISPLAYset tofalsedoes nothing unlessWP_DEBUGistrue.bashwp config set WP_DEBUG true --raw wp config set WP_DEBUG_LOG true --raw wp config set WP_DEBUG_DISPLAY false --rawStep 2: Reload the page
The headers are sent again. The message that was on the page is now in
wp-content/debug.log.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.3
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".
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 gives the ways to do it from outside, such as renaming the plugin's folder over SFTP.
bashwp plugin deactivate plugin-folder-nameStep 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 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.3
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.
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.
Step 2: Or look for the mark over SSH
Print the file's first three bytes:
ef bb bfis a byte order mark. A clean file gives3c 3f 70, the characters<?p.bashhead -c 3 wp-content/themes/your-theme/functions.php | od -An -tx1Step 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.bashsed -i '1s/^\xEF\xBB\xBF//' wp-content/themes/your-theme/functions.php
To undo it: Upload the copy of the file you downloaded before you changed it.
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.
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.
bashwp core verify-checksumsStep 2: Download the same version again
--forceoverwrites the files that are there.--skip-contentleaves out the default themes and plugins. Add--locale=with your site's language code if WordPress is not installed in US English.bashwp core download --version=$(wp core version) --skip-content --forceStep 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.
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. If pages are blank, see the white screen of death.
- GuideLocked out of WordPress admin: find which lockout you have and get back in
- GuideSlow WordPress admin: how to find the cause and fix it
- ResourceWooCommerce checkout is down: a runbook
- GuideWordPress site not showing up on Google: what to check, in order
- Guideadmin-ajax.php high CPU usage in WordPress: how to find what is calling it
- GuideHow to change WordPress permalinks without breaking your old links

