D
P
0

WordPress & PHP

Adding a require_once to a Live functions.php Locked Me Out of wp-admin — and the Recovery Tool With It

June 20, 2026·3 min read
Adding a require_once to a Live functions.php Locked Me Out of wp-admin — and the Recovery Tool With It

This is one of the most embarrassing and most easily made deploy mistakes there is. I was tidying up the functions.php of a high-traffic WordPress site I maintain, splitting code into cleaner inc/*.php files. I added one line:

require_once get_template_directory() . '/inc/social-share.php';

I uploaded functions.php through WP File Manager (the plugin I deploy with). Then the site went completely down. wp-admin showed:

There has been a critical error on this website.

And here's the part that made me break out in a cold sweat: WP File Manager itself lives inside wp-admin. Because the fatal error fired on every request — admin included — I had lost the very tool I was going to use to fix it. The recovery channel went down with the site.

Root cause: functions.php loads on EVERY request

The cause was trivial: I uploaded a functions.php that require_once'd inc/social-share.phpbut I hadn't uploaded the new file yet. The require_once target didn't exist, so PHP threw a fatal error.

functions.php isn't an ordinary file. WordPress loads it on every request, including the admin dashboard. So a require_once pointing at a missing file will fatal the whole site, including the tool you were going to use to fix it. This isn't an error confined to the frontend; it touches everything.

Prevention #1: deploy order matters

The rule is simple but easily broken in a hurry: upload dependencies before their callers.

  • Upload the new inc/*.php files first.
  • Then upload the functions.php that requires them.

If you can deploy atomically (rsync, git deploy, a zip extracted in one go), do that so there's no window in which the new functions.php exists but its dependency doesn't.

Prevention #2: wrap includes defensively

When you deploy via a file manager — where uploads aren't atomic — never trust that a dependency file is already there. Wrap every include in file_exists():

if ( file_exists( $f = get_template_directory() . '/inc/social-share.php' ) ) {
    require_once $f;
}

With this, a missing file degrades gracefully — the social-share feature simply doesn't appear, but the site stays up and wp-admin stays reachable. Far better than a white "critical error" screen.

Recovery once you're already locked out

If the site is already down and wp-admin is unreachable, forget every plugin — you can't open a single one. You need file access from outside WordPress:

  1. Log in via SFTP or cPanel File Manager (these live outside WordPress, so they stay up).
  2. Do one of:
    • Upload the missing file (inc/social-share.php) — the fatal clears immediately; or
    • Comment out / delete the require_once line from functions.php so it stops looking for the missing file.
  3. Reload the site. Once the fatal is gone, wp-admin (and WP File Manager) come back.

A milder variant of the same class of bug

This bug has a cousin with a different symptom but an identical cause — reversed deploy order. I added a new function to an existing inc file, then uploaded the caller first, before the updated inc file:

Fatal error: Call to undefined function my_new_social_share()

The function doesn't exist yet because the file that defines it hasn't been uploaded. Same pattern exactly: caller precedes definition → fatal.

Deploy checklist for anything loaded on every request

  1. Upload dependencies before callers — new inc/*.php files first, functions.php last.
  2. Where possible, deploy atomically (git/rsync/zip) to eliminate the dangerous window.
  3. Wrap includes in file_exists() when deploying via a file manager — a missing file should degrade, not fatal.
  4. Don't rely on a tool that lives inside wp-admin as your only recovery channel — keep SFTP/cPanel access ready.
  5. When locked out: upload the missing file or remove the require line over SFTP — not through any plugin.

The lesson: for anything loaded on every request, deploy order matters. Upload dependencies before their callers, and guard your includes with file_exists() every time you deploy through a tool that lives inside the very site you're changing.