Select Page

How to Password Protect a Directory with .htaccess: Complete Setup Guide for Apache

by | Nov 2, 2026 | Uncategorized

You just pushed a new staging site to staging.yourdomain.com and the last thing you want is Google crawling it, a client sharing the URL too early, or a bot hammering an unfinished checkout page. The fastest, cheapest and most reliable lock on an Apache server is still HTTP Basic Authentication driven by a .htaccess file.

This guide walks through the real-world setup end to end: generating the .htpasswd file, writing the correct directives for Apache 2.4, protecting a single file instead of a whole folder, letting your own office IP skip the prompt, and debugging the two errors that waste everyone’s afternoon: the 500 Internal Server Error and the endless login prompt. We also cover what to do when your host blocks .htaccess overrides entirely.

What .htaccess password protection actually does

When you password protect a directory with .htaccess, Apache answers the first request with a 401 Unauthorized status and a WWW-Authenticate header. The browser reacts by showing its native username and password dialog. Credentials are then sent on every subsequent request, encoded in Base64, and checked against a password file called .htpasswd.

Two things follow from that, and they matter:

  • Base64 is encoding, not encryption. Always serve the protected area over HTTPS, otherwise credentials travel in readable form.
  • The 401 response blocks everything, including crawlers, uptime monitors, API calls and asset requests from other origins. That is exactly what you want on staging, but it is also the source of most of the bugs described later in this post.

When to use it, and when not to

Good fit Poor fit
Staging and preview environments Per-user accounts for a public product
Admin panels, phpMyAdmin, /backup folders Anything needing password reset, roles or 2FA
Internal documentation or client deliverables Sites behind Nginx with no Apache layer
Temporarily locking a single sensitive file Protecting endpoints that third-party services must call
password lock laptop

Step 1: Generate the .htpasswd file

The .htpasswd file holds one line per user, in the format username:hashedpassword. There are three realistic ways to create it.

Option A: the htpasswd command (recommended)

If you have SSH access, this is the cleanest route. Apache ships the htpasswd utility (package apache2-utils on Debian/Ubuntu, httpd-tools on RHEL/AlmaLinux).

# Create a new file and add the first user (-c creates, -B uses bcrypt)
htpasswd -c -B /home/youruser/secure/.htpasswd clientpreview

# Add a SECOND user to an existing file - never reuse -c here
htpasswd -B /home/youruser/secure/.htpasswd developer

# Change an existing password
htpasswd -B /home/youruser/secure/.htpasswd clientpreview

# Delete a user
htpasswd -D /home/youruser/secure/.htpasswd developer

Important: using -c on an existing file wipes every account in it. That single flag is responsible for more lockouts than any other mistake in this workflow. There’s a good explainer over at stackoverflow.com.

Option B: generate the hash without the utility

No SSH, or a minimal container image? You can build the line manually.

# OpenSSL (APR1-MD5 style, widely compatible with Apache)
openssl passwd -apr1 'YourStrongPassword'

# PHP one-liner producing a bcrypt hash
php -r 'echo password_hash("YourStrongPassword", PASSWORD_BCRYPT), PHP_EOL;'

Then paste the output into a plain text file:

clientpreview:$apr1$kR2v8s1a$Hn1Qz9/0xWw2nQJ7kPq6R.

Option C: web-based generators

They work, but you are handing your password to a third party. If you use one, treat the password as burned and change it immediately after the first login, or better, only use a generator for a throwaway staging credential.

Where to store .htpasswd

Put it outside the public web root whenever possible:

  • Good: /home/youruser/secure/.htpasswd
  • Acceptable: /home/youruser/public_html/.htpasswd (Apache blocks files starting with .ht by default, but that depends on server config)
  • Bad: inside a folder with directory listing enabled, or anywhere a backup plugin will zip it into a downloadable archive

Set permissions to 644 and make sure the file is readable by the Apache user. On shared hosting, your home directory path usually appears in cPanel under General Information or by running pwd in the file manager terminal.

Step 2: Write the .htaccess directives

Create (or edit) a file named exactly .htaccess inside the directory you want to lock. For a staging site that lives in /home/youruser/public_html/staging/, the file goes right there.

AuthType Basic
AuthName "Staging Area - Authorized Access Only"
AuthUserFile /home/youruser/secure/.htpasswd
Require valid-user

Line by line:

Directive What it does
AuthType Basic Selects HTTP Basic Authentication. Digest exists but is poorly supported and offers little real benefit over Basic plus HTTPS.
AuthName The realm text shown in the browser dialog. Must be wrapped in double quotes if it contains spaces.
AuthUserFile Absolute server path to the password file. Relative paths and URLs will not work.
Require valid-user Any account listed in the file is accepted. Use Require user clientpreview to restrict to one specific account.

Protection is inherited by every subdirectory, so one file at the root of your staging folder covers the whole tree.

Finding the correct absolute path

If you are not sure what path to put in AuthUserFile, drop this temporary PHP file in the directory and open it in a browser:

<?php echo __DIR__; ?>

Delete it as soon as you have the value. Typical shapes you will see:

  • cPanel: /home/username/public_html/staging
  • Plesk: /var/www/vhosts/domain.com/httpdocs/staging
  • Generic VPS: /var/www/domain.com/public/staging
password lock laptop

Step 3: Test it properly

  1. Open the protected URL in a private browsing window. Browsers cache Basic Auth credentials aggressively, so a normal tab may let you straight through and make you think nothing happened.
  2. Confirm the status code from the command line:
    curl -I https://staging.yourdomain.com/
    # Expect: HTTP/1.1 401 Unauthorized
    
    curl -I -u clientpreview:YourStrongPassword https://staging.yourdomain.com/
    # Expect: HTTP/1.1 200 OK
  3. Check that assets load after login: CSS, fonts, AJAX calls and any iframe content must share the same credentials or be explicitly excluded.

Protecting a single file instead of a whole directory

Sometimes you only need to lock config-export.php or a stray backup.sql.gz. Wrap the auth block in a <Files> container placed in the directory’s .htaccess:

<Files "config-export.php">
    AuthType Basic
    AuthName "Restricted File"
    AuthUserFile /home/youruser/secure/.htpasswd
    Require valid-user
</Files>

For several files or a pattern, use <FilesMatch> with a regular expression:

<FilesMatch "\.(sql|sql\.gz|zip|log|env)$">
    AuthType Basic
    AuthName "Restricted Files"
    AuthUserFile /home/youruser/secure/.htpasswd
    Require valid-user
</FilesMatch>

The reverse case: protect a folder but leave one file public

This is the classic WordPress staging problem, where admin-ajax.php, wp-cron.php or a payment webhook must stay reachable. On Apache 2.4:

AuthType Basic
AuthName "Staging Area"
AuthUserFile /home/youruser/secure/.htpasswd
Require valid-user

<Files "admin-ajax.php">
    Require all granted
</Files>

<Files "wp-cron.php">
    Require all granted
</Files>

On legacy Apache 2.2 the equivalent is Satisfy any with Allow from all inside the <Files> block.

Letting specific IPs skip the password prompt

On a staging site you usually want your office or VPN to pass straight through while everyone else gets the dialog. Apache 2.4 handles this with <RequireAny>:

AuthType Basic
AuthName "Staging Area"
AuthUserFile /home/youruser/secure/.htpasswd

<RequireAny>
    Require ip 203.0.113.45
    Require ip 198.51.100.0/24
    Require valid-user
</RequireAny>

The request is allowed if any one condition matches. Swap to <RequireAll> if you want both an approved IP and valid credentials, which is a strong combination for admin tools. havenswift-hosting.co.uk has a solid rundown on this.

Apache 2.2 syntax (older shared hosts)

AuthType Basic
AuthName "Staging Area"
AuthUserFile /home/youruser/secure/.htpasswd
Require valid-user

Order deny,allow
Deny from all
Allow from 203.0.113.45
Satisfy any

Mixing the two syntaxes in one file is a reliable way to generate a 500 error. Check your version first with httpd -v or apache2 -v, or look at the PHP info page under Server API.

Behind Cloudflare or a load balancer

If traffic passes through a proxy, Apache sees the proxy IP, not the visitor’s. Require ip will silently fail or whitelist the whole world. Fix it by enabling mod_remoteip with the proxy ranges, or by matching on the forwarded header instead:

SetEnvIf X-Forwarded-For "^203\.0\.113\.45" allowed_ip

<RequireAny>
    Require env allowed_ip
    Require valid-user
</RequireAny>

Note that a forwarded header can be spoofed if requests can reach Apache directly, so lock the origin to proxy IPs as well. Password Protect File or Folder using .htaccess tackles the same question from another angle.

password lock laptop

Troubleshooting: the errors you will actually hit

Symptom Most likely cause Fix
500 Internal Server Error Wrong path in AuthUserFile, typo in a directive, or 2.2 syntax on a 2.4 server Read the error log, verify the absolute path, match the Apache version syntax
500 with “Invalid command AuthType” mod_auth_basic or mod_authn_file not loaded a2enmod auth_basic authn_file authz_user then restart Apache
Login prompt reappears forever Password hash mismatch, trailing whitespace, or a corrupted .htpasswd line Regenerate the user with htpasswd -B, check for CRLF line endings
Correct credentials rejected File not readable by the Apache user chmod 644 .htpasswd and verify directory permissions on the parent folders
Nothing happens, no prompt at all AllowOverride None in the vhost, or the file is named htaccess.txt Set AllowOverride AuthConfig (or All), rename the file correctly
Site loads but CSS and images are missing Assets served from a different domain or subdirectory that also returns 401 Extend protection to that path or whitelist the asset folder
WordPress admin loops or AJAX fails admin-ajax.php and wp-cron.php blocked by the 401 Whitelist them with <Files> + Require all granted
Dialog shows on the login page in a loop after a plugin update Theme or plugin intercepting the 401 error document Add ErrorDocument 401 "Authorization Required" to the same .htaccess

How to read the actual error

Guessing wastes time. Open the log:

# VPS / dedicated
tail -f /var/log/apache2/error.log
tail -f /var/log/httpd/error_log

# cPanel
tail -f /home/youruser/logs/domain.com.error.log

A path problem looks like this and is unmistakable:

(2)No such file or directory: [client 203.0.113.9:51233] AH01620: Could not open password file: /home/youruser/secure/.htpasswd

Line endings: the invisible saboteur

If you created .htpasswd in Notepad or any Windows editor, it may contain \r\n endings. Apache treats the carriage return as part of the hash and the password never matches, producing that endless prompt. Convert it:

dos2unix .htpasswd
# or
sed -i 's/\r$//' .htpasswd

What to do when your host blocks .htaccess overrides

Some managed hosts run AllowOverride None, some run Nginx in front of PHP-FPM with no Apache at all, and some simply ignore auth directives in user files. Your .htaccess is then decoration. Options, roughly in order of preference:

  1. cPanel Directory Privacy / Plesk Password-protected directories. The control panel writes the configuration at a level you are allowed to use. Open cPanel, go to Files > Directory Privacy, pick the folder, tick Password protect this directory, then create a user.
  2. Nginx HTTP Basic Auth. If you control the server config, add to the relevant location block:
    auth_basic "Staging Area";
    auth_basic_user_file /etc/nginx/.htpasswd;

    The .htpasswd format is identical, so the file you already generated works as is.

  3. Managed host staging toggle. Most WordPress platforms (Kinsta, WP Engine, SiteGround, Cloudways and similar) ship a one-click password protection switch for staging environments. Use it before building anything custom.
  4. Cloudflare Access or a similar zero trust layer. Protection happens at the edge, before the request reaches your host. This also solves the “host blocks everything” problem permanently and supports real identity providers.
  5. Application-level gate. A small PHP include that sends the 401 header manually, or a plugin such as a maintenance mode tool. Weaker than server-level auth because PHP has to boot first, but acceptable for low-risk content.

Ask your host the right question

Rather than “my .htaccess doesn’t work”, send this: “Is AllowOverride set to AuthConfig or All for my document root, and are mod_auth_basic and mod_authn_file enabled?” You will get a useful answer on the first reply instead of the third.

password lock laptop

Staging-specific hardening checklist

Password protection is one layer. For a staging site that must never leak, combine it with the rest:

  • Force HTTPS so credentials are not sent in clear text
  • Add X-Robots-Tag: noindex, nofollow as a header, since a 401 already blocks crawlers but a misconfiguration should not expose you
  • Use a unique password per environment and never reuse production credentials
  • Scrub or anonymise real customer data in the staging database
  • Disable outgoing emails and payment gateways in test mode
  • Remove the protection and the .htpasswd file when the project goes live, rather than leaving dead credentials around

A compact staging .htaccess that does several of these at once:

AuthType Basic
AuthName "Staging Area - Authorized Access Only"
AuthUserFile /home/youruser/secure/.htpasswd

<RequireAny>
    Require ip 203.0.113.45
    Require valid-user
</RequireAny>

ErrorDocument 401 "Authorization Required"

<IfModule mod_headers.c>
    Header set X-Robots-Tag "noindex, nofollow"
</IfModule>

<Files "admin-ajax.php">
    Require all granted
</Files>

Quick reference: directives cheat sheet

Goal Directive
Any listed user may enter Require valid-user
Only one specific account Require user clientpreview
A group defined in AuthGroupFile Require group editors
Allow one IP without a password Require ip 203.0.113.45 inside <RequireAny>
Open a file to everyone Require all granted
Block a file completely Require all denied

FAQ

How do I remove password protection from a directory?

Delete or comment out the AuthType, AuthName, AuthUserFile and Require lines in the .htaccess file, then clear your browser cache or test in a private window. Deleting the .htpasswd file alone is not enough and will usually trigger a 500 error instead.

How do I log out of HTTP Basic Authentication?

There is no logout button. The browser keeps the credentials for the session. Close every window of that browser, use a private tab, or clear the site data. Some developers add a logout URL that returns a fresh 401 with a different realm name to force a new prompt.

Can I protect the entire domain including all subdirectories?

Yes. Place the .htaccess file in the document root. Protection cascades down to every subfolder automatically. If you need to exclude a path, put a second .htaccess in that subfolder containing Require all granted along with Satisfy any on Apache 2.2.

Does .htaccess password protection hurt SEO?

On a staging site it helps, because the 401 response prevents indexing and duplicate content issues. On a production page you want ranked, it is fatal: crawlers cannot read protected content, so the page will not appear in search results.

How many users can I put in one .htpasswd file?

Technically thousands, but the file is read linearly on every request. Past a few hundred entries, performance drops and you should move to a database-backed authentication module or a proper application login.

Is Basic Auth secure enough in 2026?

Over HTTPS, with a long random password and a bcrypt hash, it is perfectly adequate for staging environments and internal tools. It is not adequate for customer accounts, sensitive personal data or anything requiring audit trails and multi-factor authentication.

Why does my mobile browser keep asking for the password?

Mobile browsers drop Basic Auth credentials more aggressively, especially across tab restores and when switching between HTTP and HTTPS. Make sure every internal link and redirect uses the same scheme and hostname, otherwise each variation counts as a separate realm.

Wrapping up

Password protecting a directory with .htaccess comes down to four lines of configuration and one correctly generated hash file. Almost every failure traces back to the same short list: a wrong absolute path, Windows line endings, Apache 2.2 syntax on a 2.4 server, or a host that never allowed overrides in the first place. Check the error log before changing anything, test with curl rather than your cached browser, and whitelist the handful of endpoints that genuinely need to stay open.

Need help locking down a staging environment or auditing your server configuration? The team at Vibe Midia works with these setups daily and can get it done without the trial and error.

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *