On most hosts the right way to protect wp-admin/install.php is a rule in Apache or nginx, or a pre WordPress firewall plugin like Wordfence (Extended Protection) or NinjaFirewall. But on shared nginx hosting you sometimes have no access to the server config, and core updates restore the file even if you delete or replace it. This snippet takes a different approach: a must use plugin that makes install.php harmless from inside PHP, without touching the file itself.
Why a must-use plugin?
Must use plugins live in wp-content/mu-plugins/ and load automatically on every request, before normal plugins and before any frontend code runs. They cannot be deactivated through the admin interface, and WordPress core updates do not touch them. That makes them the right place for small pieces of "always on" code.
The snippet below intercepts any request to install.php during the plugin loading phase, sends a 403, and exits before WordPress would normally render the setup wizard.
The snippet
Create a file at wp-content/mu-plugins/disable-install-php.php (create the mu-plugins folder if it does not exist) with the following contents:
<?php
/**
* Plugin Name: Disable install.php
* Description: Returns a 403 for any request to wp-admin/install.php.
*/
if (!defined('ABSPATH')) {
exit;
}
add_action('muplugins_loaded', function () {
$requestUri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : ';
$path = parse_url($requestUri, PHP_URL_PATH);
if (is_string($path) && preg_match('#/wp-admin/install\.php$#', $path)) {
status_header(403);
nocache_headers();
header('Content-Type: text/plain; charset=utf-8');
echo 'Forbidden';
exit;
}
});How it works
- muplugins_loaded fires before regular plugins and before WordPress hands control to
install.php. That is early enough to block the request cleanly. - parse_url + regex matches the exact path. We intentionally do not check just for the substring
install.phpso we do not accidentally block legitimate admin URLs that happen to contain that string as a query parameter. - status_header(403) + nocache_headers() makes sure the response is treated as forbidden and not cached by upstream proxies, CDNs or browser caches.
Why this is a fallback, not the main line of defense
This runs inside PHP, so it only works while WordPress itself still boots. In the exact scenario where install.php becomes dangerous, a broken or empty database, WordPress may fail before this code runs, and the file gets served directly. For that reason, a webserver level rule or a pre WordPress firewall (Wordfence Extended Protection, NinjaFirewall, BitFire, Shield, MalCare) is always preferable.
Use this snippet when neither a webserver rule nor a pre WordPress firewall plugin is genuinely an option: locked down shared hosting where auto_prepend_file is not permitted, no support contact, no way to get a rule added. It is better than nothing, and in most real world setups (where the database is fine 99.9% of the time) it does block casual scanners and automated exploit attempts that probe the endpoint from the outside.
Verify
Visit https://yourdomain.com/wp-admin/install.php in a private window. You should see a plain 403 Forbidden response. If you get the "Already Installed" page instead, check that the file is actually in wp-content/mu-plugins/ (not in the regular plugins/ folder) and that the file permissions allow PHP to read it.