Skip to content

Commit 1d156c5

Browse files
📝 Update "Restrict access by an IP or password in nginx" (#1054)
1 parent 63256c3 commit 1d156c5

File tree

1 file changed

+65
-55
lines changed
  • tutorials/restrict-access-by-ip-or-password-in-nginx

1 file changed

+65
-55
lines changed

tutorials/restrict-access-by-ip-or-password-in-nginx/01.en.md

Lines changed: 65 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ curl -4 --head http://example.com
133133
```
134134

135135
> * Replace `example.com` with `server_name` of the `server` you want to configure, and `http` with `https` if necessary.
136-
>
136+
>
137137
> * The combination of the domain (or an IP) and port must correspond to the nginx `server_name` and `listen` directive (in this example, port `80` is used because it is the default for HTTP).
138138
139139
You should get the output similar to this, with the HTTP status `200`:
@@ -168,7 +168,7 @@ You need to add this line to the appropriate `location` context in your `server`
168168
deny 10.0.0.5;
169169
```
170170

171-
> Replace `10.0.0.5` with your own public IP.
171+
> Replace `10.0.0.5` with your own public IP.
172172
173173
The complete configuration may look like this:
174174

@@ -297,9 +297,9 @@ sudo systemctl reload nginx
297297
<summary><b>Attention!</b></summary>
298298

299299
1. If you have a configuration like this, requests to `example.com/admin/index.php` will **not** be protected:
300-
300+
301301
> The request is handled with the `location ~ \.php$` context. This means that the allow and deny directives of `location /admin` are **not** used.
302-
302+
303303
```nginx
304304
server {
305305
listen 80;
@@ -318,23 +318,23 @@ sudo systemctl reload nginx
318318
}
319319
}
320320
```
321-
321+
322322
> * `location /` and `location /admin` are "prefix locations"
323323
> * `location ~ \.php$` is a "regular expression location"
324-
324+
325325
In the example request `example.com/admin/index.php`:
326-
326+
327327
* The path `/admin` matches with the "**prefix location**" `location /admin`.
328328
* The file `index.php` matches with the "**regular expression location**" `location ~ \.php$`.
329-
329+
330330
Nginx remembers the longest **prefix location** that matches the request, in this example that's `location/admin`. However, it will only use the **prefix location** to process the request if no **regular expression location** matches the request. In this example, the regular expression location `location ~ \.php$` matches the request and is used.
331-
331+
332332
**Regular expression locations** are marked with a tilde `~` or `~*`. Starting from the top of the `server` block, nginx first checks every **prefix location** inside out. The first matching regular expression location that nginx can find is automatically used.
333-
333+
334334
Hence, the regular expression `location` must be defined separately for the protected prefix `location` as show below.
335-
335+
336336
2. If you edit the configuration from above like this, requests to `example.com/admin/index.php` will be protected:
337-
337+
338338
> The request is handled with the first **regular expression locations** that matches the request. In this configuration, that's the `location ~ \.php$` context within the `location /admin` context. This means that the allow and deny directives of `location /admin` **are used**.
339339
340340
```nginx
@@ -360,17 +360,17 @@ sudo systemctl reload nginx
360360
}
361361
}
362362
```
363-
363+
364364
3. If you don't like duplication, you can put the common parts into a file and refer to this file via an `include` directive. In this example, the file `/etc/nginx/php.conf` can be created with these directives:
365-
365+
366366
```nginx
367367
try_files $fastcgi_script_name =404;
368368
fastcgi_pass unix:/run/php/php-fpm.sock;
369369
include fastcgi.conf;
370370
```
371-
371+
372372
Now, it can be included:
373-
373+
374374
```nginx
375375
server {
376376
listen 80;
@@ -455,75 +455,75 @@ and you can show the message to the blocked client. The message can contain vari
455455
First of all, you need to install the nginx dynamic module `geoip2` which adds support for the IP databases with geographical information. The database will be queried to determine the country of the user by their IP. This information in turn will be used to allow or block access.
456456

457457
- Install the required package:
458-
458+
459459
```bash
460460
sudo apt update && sudo apt install libnginx-mod-http-geoip2
461461
```
462-
462+
463463
- Check the `/etc/nginx/nginx.conf` file:
464-
464+
465465
By default, at the beginning of the file `/etc/nginx/nginx.conf` and outside of any curly brackets, you must have the uncommented line:
466-
466+
467467
```nginx
468468
include /etc/nginx/modules-enabled/*.conf;
469469
```
470-
470+
471471
If it's not the case, you need to add or uncomment it.
472-
472+
473473
Alternatively, you can load only `geoip2` module like this:
474-
474+
475475
```nginx
476476
load_module modules/ngx_http_geoip2_module.so;
477477
```
478-
478+
479479
- Reload the nginx configuration:
480-
480+
481481
```bash
482482
sudo systemctl reload nginx
483483
```
484484

485485
Now you only have the `geoip2` module, this is not enough to start enabling restrictions.
486486

487-
You need to download the `geoip2` compatible IP database that can be used to check the country of a user.
487+
You need to download the `geoip2` compatible IP database that can be used to check the country of a user.
488488

489489
- Install the required package:
490-
491-
490+
491+
492492
```bash
493493
cd /etc/nginx && sudo curl --fail -LO https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-Country.mmdb
494494
```
495-
495+
496496
Alternatively you can [create an account](https://www.maxmind.com/en/geolite2/signup?lang=en) on the MaxMind website and download the GeoLite2 Country database from there:
497-
497+
498498
1. Open the [registration form](https://www.maxmind.com/en/geolite2/signup?lang=en) in your browser and fill it in.
499499
2. You will receive an email with the link to create your password.
500500
3. [Log in](https://www.maxmind.com/en/account/login) with your email and password.
501501
4. Go to `Account / Manage License Keys` and create a new license key. Copy your license key and save it somewhere.
502502
5. Now you can download the GeoLite2 Country database by running this command:
503-
503+
504504
```bash
505505
cd /etc/nginx && sudo curl --fail -o GeoLite2-Country.tar.gz "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_LICENSE_KEY&suffix=tar.gz" && sudo tar --strip-components=1 -xzf GeoLite2-Country.tar.gz --wildcards "*.mmdb" && sudo rm GeoLite2-Country.tar.gz
506506
```
507507
> Replace `YOUR_LICENSE_KEY` with your license key. The database is saved to the file `/etc/nginx/GeoLite2-Country.mmdb`.
508-
508+
509509
- Edit the nginx configuration
510-
510+
511511
Now you need to edit your nginx configuration to add the `geoip2` directive which loads the database into nginx. After the database is loaded, it can be used to block or allow access.
512512
The complete configuration may look like this:
513-
513+
514514
> `0 = allow` and `1 = deny`
515515

516516
```nginx
517517
geoip2 /etc/nginx/GeoLite2-Country.mmdb {
518518
$user_country country iso_code;
519519
}
520-
520+
521521
map $user_country $not_allowed {
522522
CA 1;
523523
US 1;
524524
default 0;
525525
}
526-
526+
527527
server {
528528
listen 80;
529529
server_name example.com;
@@ -534,16 +534,16 @@ You need to download the `geoip2` compatible IP database that can be used to che
534534
}
535535
}
536536
```
537-
537+
538538
* `geoip2` directive is used to specify the path to an IP database and to define variables.
539539
In the example above, the variable `$user_country` is defined.
540540
When the request is processed by nginx, this variable is set to the user country's two-letter code.
541-
541+
542542
* `map` directive is used to create the boolean variable `$not_allowed`. Its value is chosen according to the `$user_country` variable defined in the `geoip2` block.
543543
In this example configuration, the users from United States and Canada are blocked, users from other countries are allowed. The two-letter country codes which can be used inside the `map` are listed [on this Wikipedia page](https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes).
544-
544+
545545
* Finally, the `$not_allowed` variable is used inside the `if` statement to block or allow a request.
546-
546+
547547
You can find more information about the `geoip2` module on its [GitHub page](https://github.com/leev/ngx_http_geoip2_module).
548548
549549
## Step 5 - Password-based authentication
@@ -552,21 +552,21 @@ You can restrict access by only allowing access to users with a password. This i
552552
553553
> You can use [this tutorial](https://community.hetzner.com/tutorials/add-ssl-certificate-with-lets-encrypt-to-nginx-on-ubuntu-20-04) to setup HTTPS.
554554
> The configuration you will end up with will be similar to this:
555-
>
555+
>
556556
> ```nginx
557557
> server {
558558
> server_name example.com;
559559
> location / {
560560
> }
561-
>
561+
>
562562
> listen 443 ssl; # managed by Certbot
563563
> ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
564564
> ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
565565
> include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
566566
> ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
567567
> }
568568
> ```
569-
>
569+
>
570570
> After you have configured HTTPS, you can setup the password-based authentication.
571571
572572
First of all, you need to create a special file where all usernames and corresponding passwords are stored.
@@ -576,22 +576,32 @@ You can create it like this:
576576
sudo touch /etc/nginx/users
577577
```
578578
579-
You can use this command to add users:
579+
You can use this command to add users: <a id="create_user"></a>
580580
581581
* Follow the prompts. If you made a mistake, you can cancel the command by pressing `Ctrl + C`.
582582
583583
* Note that this command uses `sudo` and may ask for your password, this is not the password for the new user.
584584
585585
```bash
586-
python3 -c 'from subprocess import *; import sys; print("Username: ", end="", file=sys.stderr); user = input();
587-
passwd = run(["openssl", "passwd", "-6"], encoding="utf-8", stdout=PIPE).stdout.strip();
588-
print("Username or password is empty. Try again!", file=sys.stderr) if user == "" or passwd == "" else print(user + ":" + passwd)' | sudo tee -a /etc/nginx/users
586+
while true; do
587+
read -r -p "Username: " ngxuser
588+
if [[ -z "$ngxuser" ]]; then
589+
echo "Username is empty. Try again!"
590+
continue
591+
fi
592+
ngxpass="$(openssl passwd -6)"
593+
if (( $? != 0 )); then
594+
echo "Try again!"
595+
else
596+
break
597+
fi
598+
done; echo "$ngxuser:$ngxpass" | sudo tee -a /etc/nginx/users
589599
```
590600
591601
Above command will add the user to the `/etc/nginx/users` file, and additionally output the username and hash of the password to your terminal separated by a colon `:`.
592602
The password itself is not stored on the server.
593603
594-
You can add more users to `/etc/nginx/users` by repeating the same `python3` command above.
604+
You can add more users to `/etc/nginx/users` by repeating the same command above.
595605
You can manually edit `/etc/nginx/users` to remove users. Each user is on a separate line.
596606
597607
The special file with users is created and you can now modify your `server` block.
@@ -642,14 +652,14 @@ You can test the new configuration with `curl`:
642652
643653
* With username and password:
644654
645-
Replace `user` with your username, `password` with your password created using the `python3` command above, and `example.com` with your domain.
655+
Replace `user` with your username, `password` with your password created using the [command above](#create_user), and `example.com` with your domain.
646656
647657
```bash
648658
curl --verbose -u user:password https://example.com
649659
```
650660
651661
This time you should get HTTP status `200` and the requested content. Response headers will look like this:
652-
662+
653663
```
654664
< HTTP/1.1 200 OK
655665
< Server: nginx
@@ -667,7 +677,7 @@ You can test the new configuration with `curl`:
667677
**Restricting access to a single URL**
668678
669679
- You can restrict access to a single URL by adding the `auth_basic` and `auth_basic_user_file` directives directly to a `location` context instead of its parent-context `server`. For example:
670-
680+
671681
```nginx
672682
server {
673683
server_name example.com;
@@ -677,29 +687,29 @@ You can test the new configuration with `curl`:
677687
auth_basic "Protected area!";
678688
auth_basic_user_file /etc/nginx/users;
679689
}
680-
690+
681691
listen 443 ssl; # managed by Certbot
682692
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
683693
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
684694
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
685695
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
686696
}
687697
```
688-
698+
689699
Here, `location /admin` is protected by password-based authentication.
690700
691701
-------------
692702
693703
**Logging of wrong access details**
694704
695705
- If someone used the wrong password, it will be logged to the `/var/log/nginx/error.log` file like this:
696-
706+
697707
```log
698708
2023/03/28 12:06:49 [error] 1865#1865: *14 user "holu": password mismatch, client: 10.0.0.5, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
699709
```
700710
701711
- If someone used a username that doesn't exist in the `/etc/nginx/users` file, it will be logged to the `error.log` file like this:
702-
712+
703713
```log
704714
2023/03/28 12:08:21 [error] 1865#1865: *16 user "root" was not found in "/etc/nginx/users", client: 10.0.0.5, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
705715
```

0 commit comments

Comments
 (0)