Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

Restrict access by an IP or password in nginx

profile picture
Author
wpdevelopment11
Published
2023-04-12
Time to read
20 minutes reading time

Introduction

In this tutorial you will learn how to restrict access to your website by editing the server and location blocks in the nginx configuration file. It is explained how to block selected client IP addresses and IP addresses from specific geographical locations (geo-based restriction), and how to restrict access by setting up a pair of username and password (HTTP Basic Authentication). This can be useful if you want to add additional protection for your web server. You will also learn how to restrict access to one web page only (e.g. https://example.com/admin/), and leave access to all other web pages on your website (e.g. https://example.com/) unrestricted.

Prerequisites

  • Configured Ubuntu server and a user with sudo privileges (you can follow this tutorial).
  • Installed nginx (you can follow this tutorial).
  • Basic understanding of nginx and its configuration. You can follow the official tutorial.
  • For the password-based restriction, you should enable HTTPS for your server. You can follow this tutorial.

Example terminology

  • Server IPv4: 10.0.0.1
  • Local IPv4: 10.0.0.5
  • Domain: example.com

Please replace 10.0.0.5 with an IP of your local computer, 10.0.0.1 with an IP of your server, and example.com with your own domain in all example commands.

Formatting conventions

  • In some configuration examples, the ellipsis ... is used to indicate that part of the configuration is omitted.

  • server vs. server

    • server is your Ubuntu server on which nginx is installed.

    • server, on the other hand, is the block in the nginx configuration file which looks like this:

      server {
          ...
      }

Configuration places

You need to know where nginx stores its configuration.

  • /etc/nginx is the nginx configuration directory. All configuration files are placed there. This directory is used for the relative paths in the include directive.

  • /etc/nginx/nginx.conf is the main nginx configuration file. nginx starts reading its configuration here.

  • /etc/nginx/sites-enabled is the directory where you can place your HTTP(S) server configuration files. After nginx is started or reloaded, all files from there will be loaded by nginx. This works because the main file /etc/nginx/nginx.conf contains the directive include sites-enabled/*;. Files are loaded in alphabetical order.

  • /etc/nginx/sites-available is the directory where you can place your HTTP(S) server configuration files, but they will not be loaded by nginx. A configuration file in this directory can be activated by creating a symbolic link in the /etc/nginx/sites-enabled directory. For example:

    ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

    enables the configuration file example.com. After the next startup or reload, nginx will load this file.

Log files

Log files are useful when you need to troubleshoot something. There are two log files by default:

  • /var/log/nginx/access.log is the file where nginx logs all requests. The format of this log file is called combined and is explained here.

  • /var/log/nginx/error.log is the file where nginx logs all error conditions and status information.

Error on reload

If you try to reload your configuration with the command sudo systemctl reload nginx and nginx complains that you made a mistake, you can see the precise error by running sudo nginx -t.

Step 1 - Locating the server block file

You need to find the file that includes the server block for your domain. You can use ls -al /etc/nginx/sites-available/ to list the server block files that are saved on your server. Let's say you want to add restrictions for a server block that responds to:

curl http://example.com

example.com is your domain or the IP of your server

You need to find the server block that includes the line server_name example.com;.

In our example, the server block we are looking for is saved in the file /etc/nginx/sites-available/example.com and it looks like this:

server {
    ...
    server_name example.com;
    ...
}
  • For HTTP, the listen directive should be listen 80; or listen 10.0.0.1:80; (10.0.0.1 should be the IP of your server).
  • For HTTPS, the listen directive should be listen 443 ssl; or listen 10.0.0.1:443 ssl;.

For the curl example above, the configuration should be similar to this:

server {
    listen 80;
    server_name example.com;
    ...
}

If you can't find a server block with the appropriate server_name line, it means that the server block in the default file (/etc/nginx/sites-available/default) is used. In this case, you need to find the server block with a listen directive that corresponds to the request port that appears first in the nginx configuration, or a listen directive that is marked as default, like this: listen 80 default_server;.

After the configuration of the server is found, you can start adding restrictions to your server.

Step 2 - Using IP addresses of clients to allow or deny access

nginx provides allow and deny directives to allow and block access for clients by using their IP addresses. For testing purposes, you can block access to yourself.

Before doing so, you need to first check that you can access your server by running this command on your local computer:

curl -4 --head http://example.com
  • Replace example.com with server_name of the server you want to configure, and http with https if necessary.

  • 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).

You should get the output similar to this, with the HTTP status 200:

HTTP/1.1 200 OK
Server: nginx
Date: Fri, 10 Mar 2023 13:22:45 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive

Step 2.1 - Finding out your public IP address

You need to know the public IP address of your local computer to block it in nginx. You can run this command to determine it:

curl -4 https://2ip.io

Your IP address will be outputted to the terminal, for example: 10.0.0.5.

  • If the curl command above doesn't work, try this Google search query. You should see your public IP right in the search results.

  • If nginx is only listening on a local port, you need to use your local IP instead of the public one.

Step 2.2 - Blocking access

You need to add this line to the appropriate location context in your server block:

deny 10.0.0.5;

Replace 10.0.0.5 with your own public IP.

The complete configuration may look like this:

server {
    listen 80;
    server_name example.com;
    location / {
        deny 10.0.0.5;
    }
}

To apply your changes, reload the nginx configuration:

sudo systemctl reload nginx

Now, let's try to run the same command from the introduction of this step on your local computer:

curl -4 --head http://example.com

This time, the output must be like this:

HTTP/1.1 403 Forbidden
Server: nginx
Date: Tue, 21 Mar 2023 08:27:58 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive

HTTP status 403 means that access is denied.

In the nginx error log /var/log/nginx/error.log you should see an error like this:

2023/03/20 17:59:25 [error] 25216#1896: *18 access forbidden by rule, client: 10.0.0.5, server: example.com, request: "GET / HTTP/1.1", host: "10.0.0.1"

10.0.0.5 is the IP of the blocked client, and example.com is the value of the server_name directive.

Congratulations! You blocked access to yourself. To rollback your changes, you need to remove the deny directive and reload the nginx configuration.

Multiple allow and deny directives can be specified. The first directive that matches will be used. The two special directives allow all; and deny all; always match, and they should be placed at the bottom of the allow / deny list. For example:

    allow 10.0.0.5;
    allow 10.0.0.6;
    deny all;

Allow access to 10.0.0.5 and 10.0.0.6, and block everybody else.

The complete configuration:

server {
    listen 80;
    server_name example.com;
    location / {
        allow 10.0.0.5;
        allow 10.0.0.6;
        deny all;
    }
}

If your server listens on IPv6 ports, make sure to allow or deny IPv6 addresses too.

Instead of using the location context, you can also place the list of deny and allow directives in the server context:

server {
    listen 80;
    server_name example.com;
    allow 10.0.0.5;
    allow 10.0.0.6;
    deny all;
    location / {
    }
}

In this case, the child context location will inherit the list of allow and deny directives from its parent context server.

However, if you specify at least one deny or allow directive in the location context, the entire list of allow and deny directives from the parent context server will be ignored. For example:

server {
    listen 80;
    server_name example.com;
    allow 10.0.0.5;
    allow 10.0.0.6;
    deny all;
    location / {
        deny 10.0.0.7;
    }
}

In the location context, only 10.0.0.7 is blocked, all other IPs are allowed. The list of allow and deny directives from the parent context server is ignored.

Instead of IP addresses, you can also specify subnets. For example:

    allow 10.0.0.0/24;
    deny all;

Do not forget to reload the configuration after you made your modifications. Simply run:

sudo systemctl reload nginx
Attention!
  1. If you have a configuration like this, requests to example.com/admin/index.php will not be protected:

    The request is handled with the location ~ \.php$ context. This means that the allow and deny directives of location /admin are not used.

    server {
        listen 80;
        server_name example.com;
        location / {
        }
        location /admin {
            allow 10.0.0.5;
            allow 10.0.0.6;
            deny all;
        }
        location ~ \.php$ {
            try_files $fastcgi_script_name =404;
            fastcgi_pass unix:/run/php/php-fpm.sock;
            include fastcgi.conf;
        }
    }
    • location / and location /admin are "prefix locations"
    • location ~ \.php$ is a "regular expression location"

    In the example request example.com/admin/index.php:

    • The path /admin matches with the "prefix location" location /admin.
    • The file index.php matches with the "regular expression location" location ~ \.php$.

    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.

    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.

    Hence, the regular expression location must be defined separately for the protected prefix location as show below.

  2. If you edit the configuration from above like this, requests to example.com/admin/index.php will be protected:

    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.

    server {
        listen 80;
        server_name example.com;
        location / {
        }
        location /admin {
            allow 10.0.0.5;
            allow 10.0.0.6;
            deny all;
            location ~ \.php$ {
                try_files $fastcgi_script_name =404;
                fastcgi_pass unix:/run/php/php-fpm.sock;
                include fastcgi.conf;
            }
        }
        location ~ \.php$ {
            try_files $fastcgi_script_name =404;
            fastcgi_pass unix:/run/php/php-fpm.sock;
            include fastcgi.conf;
        }
    }
  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:

    try_files $fastcgi_script_name =404;
    fastcgi_pass unix:/run/php/php-fpm.sock;
    include fastcgi.conf;

    Now, it can be included:

    server {
        listen 80;
        server_name example.com;
        location / {
        }
        location /admin {
            allow 10.0.0.5;
            allow 10.0.0.6;
            deny all;
            location ~ \.php$ {
                include php.conf;
            }
        }
        location ~ \.php$ {
            include php.conf;
        }
    }

Step 3 - Using the geo module to allow or deny access

As a more flexible alternative to the allow and deny directives, you can use the nginx module geo. The geo module lets you create a variable with a value that depends on a client IP address. This variable can be used later in request processing. This mechanism is not specifically for restricting access but can be used as such. Let's return to our example from the previous chapter which uses allow and deny, and which allows two IP addresses and blocks all others.

server {
    listen 80;
    server_name example.com;
    location / {
        allow 10.0.0.5;
        allow 10.0.0.6;
        deny all;
    }
}

This example can be translated to use the geo module:

0 = allow and 1 = deny

geo $not_allowed {
    10.0.0.5  0;
    10.0.0.6  0;
    default   1;
}

server {
    listen 80;
    server_name example.com;
    location / {
        if ($not_allowed) {
            return 403 "You're not allowed to access. Your IP is $remote_addr";
        }
    }
}

This approach is a little more flexible as you can see. You can set any HTTP status code by changing 403 to somethings else, and you can show the message to the blocked client. The message can contain variables.

  • In contrast to the deny directive, you will not see anything in error.log when the user is blocked. As with other requests, it will be logged to access.log.

  • If you want to use return, then you need to use geo with if instead of allow and deny.

    allow and deny will not work with return, because return is executed earlier than allow and deny, and the result will be returned regardless if a user is allowed to access.

Step 4 - Configuring restrictions based on the geographical location

Requirements:

  • Ubuntu 20.04 or later.

  • nginx is installed from the Ubuntu repositories.

    If you installed nginx from third-party repositories provided by nginx project, this instruction will not work because their repositories don't provide the required dynamic module.

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.

  • Install the required package:

    sudo apt update && sudo apt install libnginx-mod-http-geoip2
  • Check the /etc/nginx/nginx.conf file:

    By default, at the beginning of the file /etc/nginx/nginx.conf and outside of any curly brackets, you must have the uncommented line:

    include /etc/nginx/modules-enabled/*.conf;

    If it's not the case, you need to add or uncomment it.

    Alternatively, you can load only geoip2 module like this:

    load_module modules/ngx_http_geoip2_module.so;
  • Reload the nginx configuration:

    sudo systemctl reload nginx

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

You need to download the geoip2 compatible IP database that can be used to check the country of a user.

  • Install the required package:

    cd /etc/nginx && sudo curl --fail -LO https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-Country.mmdb

    Alternatively you can create an account on the MaxMind website and download the GeoLite2 Country database from there:

    1. Open the registration form in your browser and fill it in.
    2. You will receive an email with the link to create your password.
    3. Log in with your email and password.
    4. Go to Account / Manage License Keys and create a new license key. Copy your license key and save it somewhere.
    5. Now you can download the GeoLite2 Country database by running this command:
    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

    Replace YOUR_LICENSE_KEY with your license key. The database is saved to the file /etc/nginx/GeoLite2-Country.mmdb.

  • Edit the nginx configuration

    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. The complete configuration may look like this:

    0 = allow and 1 = deny

    geoip2 /etc/nginx/GeoLite2-Country.mmdb {
        $user_country country iso_code;
    }
    
    map $user_country $not_allowed {
        CA       1;
        US       1;
        default  0;
    }
    
    server {
        listen 80;
        server_name example.com;
        location / {
            if ($not_allowed) {
                return 403 "You're not allowed to access.";
            }
        }
    }
    • geoip2 directive is used to specify the path to an IP database and to define variables.

    In the example above, the variable $user_country is defined. When the request is processed by nginx, this variable is set to the user country's two-letter code.

    • 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.

    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.

    • Finally, the $not_allowed variable is used inside the if statement to block or allow a request.

You can find more information about the geoip2 module on its GitHub page.

Step 5 - Password-based authentication

You can restrict access by only allowing access to users with a password. This is called HTTP Basic authentication. To prevent sending your username and password in clear text, you first need to setup HTTPS for your server.

You can use this tutorial to setup HTTPS. The configuration you will end up with will be similar to this:

server {
    server_name example.com;
    location / {
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

After you have configured HTTPS, you can setup the password-based authentication.

First of all, you need to create a special file where all usernames and corresponding passwords are stored. You can create it like this:

sudo touch /etc/nginx/users

You can use this command to add users:

  • Follow the prompts. If you made a mistake, you can cancel the command by pressing Ctrl + C.

  • Note that this command uses sudo and may ask for your password, this is not the password for the new user.

python3 -c 'from subprocess import *; import sys; print("Username: ", end="", file=sys.stderr); user = input();
passwd = run(["openssl", "passwd", "-6"], encoding="utf-8", stdout=PIPE).stdout.strip();
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

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 :. The password itself is not stored on the server.

You can add more users to /etc/nginx/users by repeating the same python3 command above. You can manually edit /etc/nginx/users to remove users. Each user is on a separate line.

The special file with users is created and you can now modify your server block. You need to add the two directives auth_basic and auth_basic_user_file to your server configuration. The resulting configuration will look like this:

server {
    server_name example.com;
    location / {
    }
    auth_basic "Protected area!";
    auth_basic_user_file /etc/nginx/users;

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

Reload nginx to apply your changes:

sudo systemctl reload nginx

You can test the new configuration with curl:

  • Without username and password:

    curl --verbose https://example.com

    In this case you should get HTTP status 401 and no requested content. Response headers will look like this:

    < HTTP/1.1 401 Unauthorized
    < Server: nginx
    < Date: Fri, 24 Mar 2023 09:13:54 GMT
    < Content-Type: text/html
    < Content-Length: 179
    < Connection: keep-alive
    < WWW-Authenticate: Basic realm="Protected area!"
  • With username and password:

    Replace user with your username, password with your password created using the python3 command above, and example.com with your domain.

    curl --verbose -u user:password https://example.com

    This time you should get HTTP status 200 and the requested content. Response headers will look like this:

    < HTTP/1.1 200 OK
    < Server: nginx
    < Date: Fri, 24 Mar 2023 09:06:12 GMT
    < Content-Type: text/html
    < Content-Length: 615
    < Last-Modified: Mon, 20 Mar 2023 14:48:42 GMT
    < Connection: keep-alive
    < ETag: "6418724a-267"
    < Accept-Ranges: bytes

Restricting access to a single URL

  • 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:

    server {
        server_name example.com;
        location / {
        }
        location /admin {
            auth_basic "Protected area!";
            auth_basic_user_file /etc/nginx/users;
        }
    
        listen 443 ssl; # managed by Certbot
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    }

    Here, location /admin is protected by password-based authentication.


Logging of wrong access details

  • If someone used the wrong password, it will be logged to the /var/log/nginx/error.log file like this:

    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"
  • 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:

    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"

Conclusion

Hopefully this tutorial was useful to you and you learned something new. nginx can be tricky to configure, the official documentation and mailing list archives are useful resources to learn more. Mailing list archives can be searched by this Google search query.

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get 20€ free credit!

Valid until: 31 December 2024 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more