Troubleshooting

auto_prepend_file and auto_append_file

Reviewed on 6 min read debugging PHP

auto_prepend_file sets a file that PHP loads, through an operation equivalent to require, before the main file. auto_append_file does the same after the main file, except when execution ends with exit(). Both directives use include_path.

They are not equivalent to include, require or Composer autoloading: they are applied from the PHP configuration and can affect every execution covered by that configuration.

For example, the Wordfence firewall can use auto_prepend_file to filter requests before WordPress loads. Check its current value before changing it: replacing it with a diagnostic script may leave that protection unexecuted.

When facing a reinfection, start with the narrowest diagnostic source: the time and path shown in the security history, the timestamps and differences of the affected file, and the access and security logs within that interval.

A POST request close in time is only a candidate: it does not prove that the request modified the file, nor does it identify which plugin handled it. In WordPress, many components can receive requests through index.php, admin-ajax.php or the REST API.

Only if those sources do not let you follow the execution can auto_prepend_file be used temporarily to log specific metadata from future requests. Never log the full POST body.

Step by step

First create /home/CPANEL_USUARIO/private with 0700 permissions. Then, outside public_html, create the file /home/CPANEL_USUARIO/private/request-debug.php with this content, adjusting action, plugin and event to the only fields the investigation needs:

<?php

(function (): void {

$logFile = '/home/CPANEL_USUARIO/private/request-debug.log';

if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
    return;
}

clearstatcache(true, $logFile);
$logSize = is_file($logFile) ? filesize($logFile) : 0;

if (is_int($logSize) && $logSize >= 5 * 1024 * 1024) {
    return;
}

$selectedFields = [];

foreach (['action', 'plugin', 'event'] as $field) {
    if (isset($_POST[$field]) && is_scalar($_POST[$field])) {
        $selectedFields[$field] = substr((string) $_POST[$field], 0, 200);
    }
}

$entry = [
    'time' => date(DATE_ATOM),
    'method' => $_SERVER['REQUEST_METHOD'],
    'path' => parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH),
    'fields' => $selectedFields,
];

$encoded = json_encode(
    $entry,
    JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE
);

if (is_string($encoded)) {
    $previousMask = umask(0077);
    try {
        if (file_put_contents($logFile, $encoded.PHP_EOL, FILE_APPEND | LOCK_EX) !== false) {
            chmod($logFile, 0600);
        }
    } finally {
        umask($previousMask);
    }
}
})();

Replace CPANEL_USUARIO with your cPanel username.

This script saves only the path, the method and the selected fields, and checks a 5 MiB threshold before writing. It is not a strict limit: simultaneous requests or the last entry can exceed it. Check the size during the diagnostic window. Do not add password, authorisation, cookie, token, personal data or payment fields; if a legitimate value may contain sensitive information, redact it or replace it with a hash before logging it. The script and the log must belong to the cPanel user, and the log must keep 0600 permissions.

This log only provides metadata to correlate a candidate request. It does not prove that the request caused the modification and it is not tamper-proof evidence: an attacker with write access to the same account could alter or disable it.

Next, add this at the very top of the .htaccess file in public_html:

php_value auto_prepend_file '/home/CPANEL_USUARIO/private/request-debug.php'

Again, replace the cPanel username with yours. The directive will affect PHP executions covered by that .htaccess, usually including its subdirectories. Validate the script first with the same PHP version the website uses.

If a 500 error appears after adding php_value, remove the line immediately and check the error log. Only a message identifying the directive as invalid or not allowed confirms that the handler does not support setting it from .htaccess.

Before you start, define a short capture window and a maximum size. When you finish, immediately remove the auto_prepend_file line, delete request-debug.php and destroy the log once the evidence is no longer needed. While you keep it, limit access to the staff carrying out the analysis and apply an explicit retention period; do not keep it indefinitely.

This capture does not replace incident response. Even if it lets you link a request to a modification, you still need to confirm which code handled it, close the entry point, check for persistence, reinstall components from trusted sources, rotate the affected credentials and verify that no other files or users have been altered.

Keep the previous value of auto_prepend_file and restore it when you finish. If a firewall manages it, do not replace it without preparing a compatible integration on a test copy. Also check that the path itself and the selected fields do not contain personal identifiers; leave out any data you do not need. The example restores the permission mask after writing so that it does not change how the application creates its own files.

You may also find useful

Something wrong or out of date? Tell us and we will review it.