2026年11月13日

2026年11月13日

Protect the WordPress Admin with Basic Authentication (Apache/Nginx)

This article is a localized edition of the Japanese original. Read the Japanese original

Basic authentication is a mechanism in which the web server (Apache or Nginx) asks for a username and password before it returns a page. If you apply it to wp-login.php and /wp-admin/, requests without credentials are stopped with a 401 before they ever reach the WordPress login form. Because they are stopped before PHP runs, it also keeps brute-force attacks on the login screen from driving up server load.

This article walks through the setup and how to verify it, using Apache 2.4 and Nginx on Debian / Ubuntu as the example (as of November 2026; the configuration examples were tested on Apache 2.4.69 and the Nginx stable release).

What Basic authentication does and does not protect

Before you set it up, check how far its effect reaches.

Item How Basic authentication handles it
Login attempts against wp-login.php Stopped with a 401 if no credentials are sent
The admin screens under /wp-admin/ Same as above
xmlrpc.php (XML-RPC) Not covered. Disable or restrict it separately
REST API (/wp-json/) Not covered
Encryption of the connection None. Credentials are only Base64-encoded, so HTTPS is a prerequisite

xmlrpc.php is another entry point that accepts a username and password, so if you do not use it, also follow the steps to disable XML-RPC.

Basic authentication is also a poor fit for sites where registered members or shop customers log in through wp-login.php, because you would have to hand the Basic authentication credentials to all of them. In that case, consider limiting login attempts or restricting the login page by IP address instead.

Step 1: Create the password file (htpasswd)

Create the password file with the htpasswd command. Put it outside the document root (such as /var/www/html) so it cannot be read over the web.

# Install the htpasswd command
sudo apt install apache2-utils     # Debian / Ubuntu
# sudo dnf install httpd-tools     # RHEL / AlmaLinux / Rocky Linux

# Generate a hard-to-guess password (write down the output)
openssl rand -base64 24

# Create a new file and add the first user (-c recreates the file, so omit it from then on)
sudo htpasswd -c -B /etc/apache2/.htpasswd-wp wp_guard

# Add a second user
sudo htpasswd -B /etc/apache2/.htpasswd-wp second_user

# Remove a user
sudo htpasswd -D /etc/apache2/.htpasswd-wp second_user

# Owned by root, readable only by the web server's group
sudo chown root:www-data /etc/apache2/.htpasswd-wp
sudo chmod 640 /etc/apache2/.htpasswd-wp
  • -B stores the password with bcrypt. Without it, the password is stored with Apache's MD5 variant (apr1).
  • www-data is the user Apache and Nginx run as on Debian / Ubuntu. On RHEL-family systems, Apache runs as apache and Nginx as nginx.
  • For a file used by Nginx, create it with apr1, which the official Nginx documentation explicitly lists as a supported format, and it will be read (omit -B: sudo htpasswd -c /etc/nginx/.htpasswd-wp wp_guard). Whether bcrypt works depends on what the operating system's crypt() function supports.

Step 2: Configure Apache (.htaccess)

To configure authentication in .htaccess, the directory must allow AllowOverride AuthConfig (or All). If WordPress permalinks work, it is usually set to All.

Protect wp-login.php

Add the following to the .htaccess in the WordPress root (where wp-config.php is), above # BEGIN WordPress. WordPress rewrites everything between # BEGIN WordPress and # END WordPress, so do not put it there.

# Basic authentication: wp-login.php
<Files "wp-login.php">
    AuthType Basic
    AuthName "Restricted"
    AuthUserFile /etc/apache2/.htpasswd-wp
    Require valid-user
</Files>

Protect /wp-admin/ (excluding admin-ajax.php)

Create a new .htaccess inside the wp-admin directory.

AuthType Basic
AuthName "Restricted"
AuthUserFile /etc/apache2/.htpasswd-wp
Require valid-user

# admin-ajax.php is also called from the public side of the site, so exclude it
<Files "admin-ajax.php">
    Require all granted
</Files>

admin-ajax.php is a file that plugins and themes call from the browsers of visitors who are not logged in. If you put authentication on it, public-side features such as search, filtering and cart operations may stop working. In Apache 2.4, the Require inside <Files> is evaluated after the directory-wide Require valid-user and replaces it, so only this file passes without authentication.

The Satisfy Any and Allow from all found in older articles are Apache 2.2 syntax. Also, <Directory> cannot be used inside .htaccess. If you can edit the server configuration (the virtual host), putting the same content inside <Directory "/var/www/html/wp-admin"> has the same effect. In that case, test the configuration and then reload.

sudo apachectl configtest
sudo systemctl reload apache2

If you only changed .htaccess, no reload is needed. On shared hosting, write the full server path (a path starting with /) in AuthUserFile. If the control panel has a Basic authentication feature, using it is easier.

Step 3: Configure Nginx

Nginx does not read .htaccess, so add the configuration to the site's server { } block. Adjust the PHP handler (fastcgi_pass) to your environment (for example, unix:/run/php/php8.3-fpm.sock for php8.3-fpm on Ubuntu 24.04).

# Inside server { }, alongside the existing location ~ \.php$

# Basic authentication: wp-login.php
location = /wp-login.php {
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd-wp;

    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

# Basic authentication: everything under /wp-admin/
location ^~ /wp-admin/ {
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd-wp;

    # Let admin-ajax.php through without authentication
    location = /wp-admin/admin-ajax.php {
        auth_basic off;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    # Execute PHP inside /wp-admin/ (without this, PHP files are returned as-is)
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

When location ^~ /wp-admin/ matches, Nginx stops checking regular-expression location blocks (including the server-wide location ~ \.php$). That is why you need a nested location for PHP inside /wp-admin/. If you forget it, the PHP files of the admin screens may be returned as source code instead of being executed.

sudo nginx -t && sudo systemctl reload nginx

For the overall Nginx configuration, see also configuring Nginx to run WordPress.

Step 4: Verify that it works

Check the response status codes from the command line. If you pass only the username to -u, curl prompts for the password interactively, so it does not end up in your shell history.

# Without credentials: expect 401
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-login.php
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-admin/

# With credentials: expect 200
curl -s -o /dev/null -w "%{http_code}\n" -u wp_guard https://example.com/wp-login.php

# admin-ajax.php: anything but 401 is fine (a call without action returns 400)
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-admin/admin-ajax.php

Finally, log in to the admin screens in a browser, and confirm that public-side features that use admin-ajax.php, such as forms and search, still work.

Operational notes

  • Use the same AuthName (the auth_basic string): Browsers reuse credentials you have already entered for the same site and the same realm, so using the same name for wp-login.php and /wp-admin/ means you only have to enter them once.
  • admin-post.php: Some plugins use /wp-admin/admin-post.php as the submission target for forms filled in by visitors who are not logged in (the admin_post_nopriv_ hook). If a public-side form can no longer be submitted, exclude this file the same way as admin-ajax.php.
  • Password resets and new users: The link in password reset emails (wp-login.php?action=rp) is also behind Basic authentication. If people other than administrators log in, either give them the credentials or have an administrator change the password with WP-CLI's wp user update.
  • Where to put the file: If you put .htpasswd-wp inside the document root, it may be downloadable over the web depending on your configuration. Keep it outside.
  • Combine login protection with other measures: If the Basic authentication password leaks, the protection is gone. Keep using strong WordPress passwords, two-factor authentication and brute-force attack countermeasures.

Related articles

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲