• Halloween is coming! To celebrate the occasion, we dusted off a couple of old skins, and even created a brand new one (Pixel).
    To get into the festive spirit, install the Skins extension.
  • The new Python extension is now available. It allows customers to deploy and manage WSGI-based Python applications on their websites directly from Plesk.
  • The Plesk Mobile apps for iOS and Android and the Plesk Mobile Center extension have been deprecated, and are no longer available for new installations. In Plesk Obsidian 18.0.84, the Plesk Mobile Center extension will be removed from servers during the upgrade, and the Plesk Mobile apps will lose their server management features.
  • The Social Login extension has been deprecated and will be removed in Plesk Obsidian 18.0.84. After that, it will no longer be possible to log in to Plesk with a Google, Facebook, GitHub account, or with a passkey. Plesk usernames and passwords, as well as logging in with a WebPros Account, are not affected.

Question Nginx Directive Using .htpasswd While Blocking Others

tetrahall

Regular Pleskian
Server operating system version
CentOS 7.6
Plesk version and microupdate number
Plesk Obsidian 18.0.81.6
Hi,

I need to add a directive from within Plesk (Additional nginx directive)

The aim is:

1. Allow users to access URIs beginnig with specific words.

2. The word can be "a folder name" or just a word in the path immediately after mydomain.com

3. I need to allow myself to access URIs blocked for others

I have created the file ".htpasswd" containing a generated string (username and password). It is located in "/var/www/vhosts" outside "mydomain.com"

I have sought the help of AI on Google. Ai suggestions kept causing errors and AI kept changing its mind correcting the errors with an apology until I gave up.

The number of words is around 30.

I was told that using "MAP" is the better option.

Please help in writing the directive.

KInd regards.
 
Hi,

the reason the AI kept running into errors is probably that "map" is only allowed in the http {} context, but the "Additional nginx directives" field in Plesk is included inside the server {} block of your domain. So you need two pieces: the map in a separate file, and two small directives in the Plesk field.

The idea: the map decides per request whether basic auth is required or not. For the allowed words it returns "off" (= no login), for everything else it returns a realm text (= login required). Since you have around 30 words, this stays tidy and you only ever edit one list.

Step 1 - create the map file (replace "mydomain" and the words with your own):

Code:
nano /etc/nginx/conf.d/mydomain-auth-map.conf

Content:

Code:
map $uri $mydomain_auth {
    default                                      "Restricted area";
    ~^/(word1|word2|word3|folder1|folder2)(/|$)  off;
    ~^/\.well-known/                             off;
}

Notes on the map:
- Just extend the list inside the brackets with your ~30 words, separated by "|".
- "(/|$)" means the word has to be a full path segment, so /word1 and /word1/anything are open, but /word1abc is not. If you want real "begins with" matching (/word1abc also open), remove the "(/|$)" part.
- If your homepage should be public too, add this line: ~^/$ off;
- I left the /.well-known/ line in so Let's Encrypt renewals don't break.
- I used $uri (not $request_uri) on purpose, because it is already normalized/decoded, so nobody can sneak past the rules with encoded characters.

Step 2 - in Plesk go to Domains > mydomain.com > Apache & nginx Settings > Additional nginx directives and add:

Code:
auth_basic $mydomain_auth;
auth_basic_user_file /var/www/vhosts/mydomain.com/.htpasswd;

This works because auth_basic accepts a variable, and if that variable evaluates to "off", the authentication is simply skipped for that request.

Step 3 - make sure nginx can read your .htpasswd. On CentOS 7 it's best to use the default apr1 (MD5) hash, because the system crypt() there doesn't understand the bcrypt hashes that "htpasswd -B" creates:

Code:
htpasswd -c /var/www/vhosts/mydomain.com/.htpasswd yourusername
chown root:nginx /var/www/vhosts/mydomain.com/.htpasswd
chmod 640 /var/www/vhosts/mydomain.com/.htpasswd

(No htpasswd tool? "yum install httpd-tools", or generate the hash with "openssl passwd -apr1" and write the line "username:hash" into the file yourself.)

Step 4 - test the config and reload:

Code:
nginx -t && systemctl reload nginx

Then test with curl, one open and one blocked URL:

Code:
curl -I https://mydomain.com/word1/
curl -I https://mydomain.com/something-else/

The first one should return your normal status (200 or whatever the page gives), the second one should return "401 Unauthorized". With the correct login (curl -u yourusername https://mydomain.com/something-else/) you get in everywhere.

Two more things:
- Don't put the map into the Plesk field, that is exactly what throws "map directive is not allowed here".
- Files in /etc/nginx/conf.d/ are not touched by Plesk when it regenerates the domain configs, so the map survives updates. Just remember to run "nginx -t" before each reload when you edit the list.

Hope that helps, let us know if something doesn't work and post the output of "nginx -t".

Regards
Tessa
 
Tessa,

Thank you very much for your detailed and useful reply.

A few points, please:

1) Is "?word" considered a word or should "?" be ignored?

2) I did mention "30 words" in my post. Just in case more are needed, Is there a limit?

3) I am doing this straight on the server as I don't have the means to test it on a production pc.

As a quick test, can I use a directive in Plesk to do the following:

block everyone visiting a uri beginning with "users". I would use the username and password to access that uri.

The file .htpasswd has been created using the code you suggested.

Kind regards
 
Hi,

1) The "?" is not part of the path. $uri only contains the path without the query string, so /word1?foo=bar is treated as /word1 (open), and /?word1 is treated as just / (so "word1" is not matched). Simply don't put any "?" into your list. Two small things: the list is a regex, so if a word contains a dot or another special character, escape it with a backslash (word.txt becomes word\.txt). And matching is case-sensitive, so use ~* instead of ~ if /Word1 should match too.

2) I'm not aware of any hard limit, 30 words is nothing and a few hundred are no problem either. If the line gets too long for your taste, you can simply split it over several lines, nginx checks regex entries in the order they appear and the first match wins:
Code:
    ~^/(word1|word2|word3)(/|$)     off;
    ~^/(word4|word5|word6)(/|$)     off;

3) Testing directly on the live server is fine if you stick to this order, because "nginx -t" checks the config first and a reload with a broken config is refused, so the old working config simply keeps running.

For your quick test ("block everything starting with users") use the same mechanism, just inverted: everything is open by default, only /users... asks for the login. First create the map file (before touching Plesk, otherwise nginx complains about an unknown variable):
Code:
nano /etc/nginx/conf.d/mydomain-auth-map.conf

Content:
Code:
map $uri $mydomain_auth {
    default         off;
    ~^/users        "Restricted area";
}

This matches /users, /users/anything and also /usersfoo. If you only want /users and below it, use ~^/users(/|$) instead.

Check and reload:
Code:
nginx -t && systemctl reload nginx

Only then add the two lines in Plesk (Domains > mydomain.com > Apache & nginx Settings > Additional nginx directives), same as before:
Code:
auth_basic $mydomain_auth;
auth_basic_user_file /var/www/vhosts/mydomain.com/.htpasswd;

Then test (the first should work without login, the second should give 401, the third should work with your password):
Code:
curl -I https://mydomain.com/
curl -I https://mydomain.com/users/
curl -I -u yourusername https://mydomain.com/users/

Please don't try to do the quick test with a "location /users { ... }" block in the Plesk field. In Plesk's nginx config such a prefix location can bypass the handlers for PHP and other files, and then your PHP files under /users could be delivered as plain source code. With the variable approach the auth_basic sits on server level and is inherited by all of Plesk's locations, so nothing gets bypassed.

When the test works, just replace the map with the real list from my first post (default "Restricted area", your words set to off), run "nginx -t && systemctl reload nginx" again, and you're done. The two lines in Plesk stay exactly the same.

Regards
Tessa
 
Back
Top