.htaccess Placement (Overview of the 3 Locations)
public_html/
├── .htaccess ← Root config (rewrite, headers, cache, etc.)
├── wp-admin/
│ └── .htaccess ← Admin screen-specific config
└── wp-content/
└── uploads/
└── .htaccess ← Uploads directory-specific config
Why use separate files instead of <Directory> ?
The
<Directory>
directive is exclusive to Apache's main configuration file (
httpd.conf
) and cannot be used inside
.htaccess
. To apply settings per directory, place a separate
.htaccess
file in that directory.
wp-admin/.htaccess — Basic Authentication for the Admin Screen
# wp-admin/.htaccess
# Exclude admin-ajax.php (required for frontend Ajax)
<Files admin-ajax.php>
<IfModule mod_authz_core.c>
Require all granted
</IfModule>
<IfModule !mod_authz_core.c>
Satisfy Any
Order allow,deny
Allow from all
Deny from none
</IfModule>
</Files>
# Allow upgrade.php only from server-internal IP (for WordPress auto-updates)
<Files upgrade.php>
<IfModule mod_authz_core.c>
<RequireAny>
Require ip 127.0.0.1 ::1
Require valid-user
</RequireAny>
</IfModule>
</Files>
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /home/username/.htpasswd
Require valid-user
The Role of Basic Authentication
This adds an extra authentication barrier to the WordPress admin screen (
/wp-admin/
). Because authentication is enforced at the server level before WordPress's own login processing runs, it is an effective countermeasure against brute-force attacks (password guessing).
Why Exclude admin-ajax.php
admin-ajax.php
is used for frontend Ajax requests (form submissions, pop-ups, add-to-cart, etc.). If Basic Auth is applied to the entire admin directory, Ajax requests from non-logged-in visitors will fail, so this file must be excluded from the authentication.
Why Exclude upgrade.php by Server-Internal IP
WordPress auto-updates work by having the server itself (
127.0.0.1
/
::1
) access
upgrade.php
internally. If Basic Auth is in place, auto-updates will fail, so access from the loopback IP address is permitted without authentication. Note that the example above assumes Apache 2.4+ (
mod_authz_core
); for Apache 2.2, an equivalent allow directive must be added separately.
Basic Authentication Directives
| Directive | Meaning |
|---|---|
AuthType Basic |
Specifies Basic authentication as the auth method |
AuthName |
Message displayed in the authentication dialog |
AuthUserFile |
Full path to the password file (
.htpasswd
)
|
Require valid-user |
Allows access only to successfully authenticated users |
Creating the .htpasswd File
Generate the password file with the
htpasswd
command. Place this file outside the document root so it cannot be accessed directly from the web.
# Create new file (-c option)
htpasswd -c /home/username/.htpasswd username
# Add a user to an existing file
htpasswd /home/username/.htpasswd username
.htaccess Generator does not auto-generate passwords or
.htpasswdhashes. bcrypt hash generation requires an external library, and SHA1 is insufficient for security. Use thehtpasswdcommand to create a secure password file.
Root .htaccess — Protecting wp-includes / wp-admin/includes
Rules to block direct access to WordPress core files. Place these inside the
mod_rewrite
block in the root
.htaccess
.
# Block direct access to wp-admin/includes/
RewriteCond %{REQUEST_URI} ^/wp-admin/includes(?:/|$) [NC]
RewriteRule .* - [F,L]
# Block direct access to wp-includes/*.php
RewriteCond %{REQUEST_URI} ^/wp-includes/[^/]+\.php$ [NC]
RewriteRule .* - [F,L]
Why Block All Requests to wp-admin/includes/
wp-admin/includes/
stores internal admin processing files that never need to be accessed directly from a browser. All requests to this directory should be blocked.
Why Block Only *.php in wp-includes/
wp-includes/
also contains frontend assets such as CSS, JS, and images, so blocking the entire directory would break the site. Only PHP files are blocked while access to assets is permitted.
The pattern
[^/]+\.php$
matches only PHP files directly under the directory, so it does not interfere with legitimate PHP files in subdirectories such as
wp-includes/js/tinymce/wp-tinymce.php
.
Difference from Directory Listing ( -d Check)
Directory listing is already covered by
Options -Indexes
. These rules protect against direct PHP file access, which is a more impactful attack vector.
wp-content/uploads/.htaccess — Preventing PHP Execution
# wp-content/uploads/.htaccess
<FilesMatch "\.(?:php|phar|phtml)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
</FilesMatch>
Purpose
This configuration prevents PHP files from being uploaded to and executed from the uploads directory (
/wp-content/uploads/
).
Attackers exploit vulnerable plugins or themes to upload
.php
files and then execute them via a browser to hijack the server (remote code execution). This file acts as a defense line against that attack.
The Role of <FilesMatch>
This is the regex version of
<Files>
. Unlike
<Files>
, which matches a single filename,
<FilesMatch>
can match multiple extensions at once using a regular expression pattern.
| Blocked Extension | Reason |
|---|---|
.php |
Standard PHP script files |
.phar |
PHP Archive — an executable archive containing PHP code |
.phtml |
Legacy PHP extension; may be executed as PHP depending on Apache config |
In normal WordPress operation,
.pharor.phtmlfiles never appear inuploads/. Attackers try to bypass upload restrictions by using extensions other than.php, so all of them should be blocked together.
Why Use the Apache 2.2 / 2.4 Compatible Pattern
While the root
.htaccess
is placed intentionally by an admin, the
uploads/
.htaccess
is sometimes auto-generated by plugins (WP Super Cache, EWWW Image Optimizer, etc.). Ensuring compatibility across a wide range of Apache versions guarantees the file works reliably in any environment.
<IfModule mod_authz_core.c>
# Apache 2.4+ syntax
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
# Apache 2.2 and earlier (fallback)
Order deny,allow
Deny from all
</IfModule>
Why Not Use php_flag engine off
php_flag engine off
is only valid in
mod_php
environments (PHP running as an Apache module). It has no effect in PHP-FPM (FastCGI) environments used by major shared hosting providers such as XServer and ConoHa WING.
FilesMatch
+
Require all denied
is processed at the Apache access control layer, so it works reliably in any environment regardless of the PHP execution method.