.htaccess Generator for WordPress

WordPress Protection Guide

JA

.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 .htpasswd hashes. bcrypt hash generation requires an external library, and SHA1 is insufficient for security. Use the htpasswd command 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, .phar or .phtml files never appear in uploads/ . 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.

← Back to Generator