> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Blocking Traffic

> Guide showing Nginx allow and deny directives and configuring fail2ban to automatically block IPs after repeated failed HTTP Basic authentication attempts.

This guide demonstrates how to allow and deny client traffic with Nginx using the `allow` and `deny` directives, and how to use fail2ban to automatically block abusive clients that repeatedly fail HTTP Basic authentication. The example site is `example.com`; its `/admin` endpoint is protected by HTTP Basic auth. Instead of maintaining long static deny lists in Nginx, fail2ban can detect repeated failed authentication attempts and temporarily ban offending IPs.

Table of contents

* Environment summary
* Initial Nginx configuration
* Mapping example.com on test clients
* TLS note for curl
* Basic connectivity tests
* Manual IP deny examples (single IP, allow-only, CIDR)
* Using fail2ban to auto-block failed auth attempts
* fail2ban configuration and status checks
* Demonstration (browser)
* Why combine fail2ban and Nginx allow/deny
* Final notes and references

Environment summary

| Component | Purpose | Example / Notes |
| - | -: | - |
| Nginx server | Hosts `example.com` on HTTP (80) and HTTPS (443) | TLS uses self-signed / mkcert certs |
| Test clients | Two clients used for testing | `node01` (192.231.128.12) and `node02` (192.231.128.3) |
| Authentication | HTTP Basic for `/admin` | Credentials stored in `/etc/nginx/conf.d/.htpasswd` |
| Tooling | fail2ban for automated bans | Monitors Nginx error log for failed auth events |

Initial Nginx configuration
Below is the starting server block for `example.com`. It redirects HTTP to HTTPS, serves the site over TLS, and protects `/admin` with `auth_basic`:

```nginx theme={null}
server {
    listen 80;

    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;

    server_name example.com;

    ssl_certificate /etc/ssl/certs/example.com.pem;
    ssl_certificate_key /etc/ssl/certs/example.com-key.pem;

    root /var/www/html;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
    add_header X-Frame-Options "SAMEORIGIN";
    add_header Content-Security-Policy "default-src 'self'";
    add_header Referrer-Policy origin;

    # Add index.php to the list if you are using PHP
    index index.html index.htm index.nginx-debian.html;

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }

    location /admin {
        auth_basic "Restricted Access";
        auth_basic_user_file /etc/nginx/conf.d/.htpasswd;
    }
}
```

Mapping example.com on test clients
On both test client nodes (`node01` and `node02`) add the Nginx server IP to `/etc/hosts` so `example.com` resolves to the server used in this demo (example shown uses `192.231.128.10`). Also add entries for client hostnames so examples below map consistently:

```text theme={null}
127.0.0.1        localhost
::1              localhost ip6-localhost ip6-loopback
fe00::0          ip6-localnet
ff00::0          ip6-mcastprefix
ff02::1          ip6-allnodes
ff02::2          ip6-allrouters
192.231.128.12   node01
192.231.128.3    node02
192.231.128.10   example.com
```

Note about TLS and curl
Because the certificates were created with a non-public CA (mkcert), curl will refuse to verify the certificate unless you add `-k` / `--insecure` to bypass certificate verification. That only disables validation — traffic is still encrypted.

Using `curl -k` disables TLS certificate verification and should only be used for testing. Do not use `--insecure` in production.

Basic connectivity tests from a client (node01)

* Check HTTP redirect (expected 301):

```bash theme={null}
root@node01:~# curl http://example.com
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>
</body>
</html>
```

* HTTPS without `-k` (expected certificate verification error):

```text theme={null}
root@node01:~# curl https://example.com
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html
```

* HTTPS with `-k` to skip verification (use `--head`/`-I` for headers only):

```bash theme={null}
root@node01:~# curl -k --head https://example.com
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 13 Feb 2025 19:22:30 GMT
Content-Type: text/html
Content-Length: 8710
Connection: keep-alive
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: default-src 'self'
Referrer-Policy: origin
Accept-Ranges: bytes
```

* Request to the protected admin endpoint without credentials (expected 401):

```bash theme={null}
root@node01:~# curl -k --head https://example.com/admin
HTTP/1.1 401 Unauthorized
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 13 Feb 2025 19:22:52 GMT
Content-Type: text/html
Content-Length: 188
Connection: keep-alive
WWW-Authenticate: Basic realm="Restricted Access"
```

Manual deny/allow examples

1. Manually denying a specific client IP
   To block `node02` from accessing the site, add a `deny` for its IP in the `location /` block (for a single IP you can use `/32`, or simply the address without CIDR):

```nginx theme={null}
location / {
    deny 192.231.128.3/32; # node02
    # First attempt to serve request as file, then
    # as directory, then fall back to displaying a 404.
    try_files $uri $uri/ =404;
}
```

After saving the config, always test and reload Nginx:

```bash theme={null}
root@nginx:~# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf test is successful

root@nginx:~# nginx -s reload
```

Results:

* From `node01`: access to `example.com` returns `200 OK`.
* From `node02`: requests now return `403 Forbidden` because the IP is explicitly denied.

2. Allow only a single IP for `/admin` and deny all others
   Scope allow/deny rules to the `/admin` location so only a trusted IP (example: `node01`) can reach it:

```nginx theme={null}
location /admin {
    allow 192.231.128.12/32; # node01
    deny all;
    auth_basic "Restricted Access";
    auth_basic_user_file /etc/nginx/conf.d/.htpasswd;
}
```

After reloading Nginx:

* `node02` receives `403 Forbidden` for `/admin`.
* `node01` receives `401 Unauthorized` when hitting `/admin` (because it is allowed by IP but did not provide valid credentials).

3. Allowing a CIDR range (/24) for a subnet
   To permit an entire subnet, use `/24` instead of listing multiple individual `/32` entries:

```nginx theme={null}
location / {
    allow 192.231.128.0/24; # node01/02
    deny all;
    try_files $uri $uri/ =404;
}

location /admin {
    allow 192.231.128.0/24; # node01/02
    deny all;
    auth_basic "Restricted Access";
    auth_basic_user_file /etc/nginx/conf.d/.htpasswd;
}
```

Troubleshooting tip: each `allow`/`deny` accepts exactly one address/keyword per statement and must end with a semicolon. If you see `invalid number of arguments in "allow" directive`, check for missing semicolons or malformed addresses and re-run `nginx -t`.

Using fail2ban to block repeated failed auth attempts
Maintaining long deny lists in Nginx is not scalable for dynamic threats (e.g., brute-force attempts). fail2ban automates this by scanning log files (such as the Nginx error log) for failed authentication entries and adding temporary firewall bans when thresholds are exceeded.

Install and configure fail2ban (Debian/Ubuntu example):

```bash theme={null}
# Update package index and install fail2ban
sudo apt update -y
sudo apt install -y fail2ban

# Copy default jail configuration to a local override (do NOT edit jail.conf directly)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
```

Do not edit `/etc/fail2ban/jail.conf` directly. Use `/etc/fail2ban/jail.local` or `/etc/fail2ban/jail.d/*.conf` to override defaults so package updates do not overwrite your settings.

Configure the `nginx-http-auth` jail in `/etc/fail2ban/jail.local` (example settings):

```ini theme={null}
[nginx-http-auth]
enabled  = true
port     = http,https
filter   = nginx-http-auth
logpath  = %(nginx_error_log)s
maxretry = 1
bantime  = 600
```

fail2ban settings explained

| Option | Purpose | Example / Notes |
| - | -: | - |
| `enabled` | Enable the jail | `true` |
| `port` | Affected ports | `http,https` |
| `filter` | Log filter that matches failed auth entries | `nginx-http-auth` (built-in) |
| `logpath` | Path to Nginx error log | `%(nginx_error_log)s` |
| `maxretry` | Number of failures before ban | `1` (ban on first failure — adjust as needed) |
| `bantime` | Ban duration in seconds | `600` (10 minutes) |

Start (or restart) fail2ban and check status:

```bash theme={null}
sudo systemctl restart fail2ban
sudo fail2ban-client status
```

Check the status of the `nginx-http-auth` jail and list banned IPs:

```bash theme={null}
sudo fail2ban-client status nginx-http-auth
```

Sample fail2ban interaction (ban/unban)
Common commands and example outputs:

```bash theme={null}
# Show all jails
root@nginx:/etc/fail2ban# fail2ban-client status
Status
|- Number of jail: 2
`- Jail list: nginx-http-auth, sshd

# Show status for nginx-http-auth
root@nginx:/etc/fail2ban# fail2ban-client status nginx-http-auth
Status for the jail: nginx-http-auth
|- Filter
|  |- Currently failed: 0
|  |- Total failed: 1
|  `- File list: /var/log/nginx/error.log
`- Actions
   |- Currently banned: 1
   |- Total banned: 1
   `- Banned IP list: 174.0.252.84

# Unban an IP
root@nginx:/etc/fail2ban# fail2ban-client unban ip 174.0.252.84
1
```

Demonstration: triggering fail2ban from a browser
If a client attempts to authenticate to `/admin` with invalid credentials multiple times, Nginx logs failed auth attempts to the error log. fail2ban monitors that log; once `maxretry` is exceeded, fail2ban bans the client IP using the configured action (typically iptables/nftables).

Here is the browser prompt when entering credentials (attempting an auth that fail2ban will count as a failure):

![A web browser screenshot showing a centered "Sign in" dialog for example.com with "admin" filled in the username field and a masked password. The dialog includes "Cancel" and "Sign In" buttons and a green browser toolbar across the top.](https://mintcdn.com/kodekloud-c4ac6d9a/x1wBthvq78ZWHRHX/images/Nginx-for-Beginners/Security/Demo-Blocking-Traffic/example-com-signin-admin-green-toolbar.jpg?fit=max\&auto=format\&n=x1wBthvq78ZWHRHX\&q=85\&s=5727ec12b73eb89c055023c2d8098dda)

After a couple of failed attempts the fail2ban status will show the client IP as banned, and network-level traffic from that IP to the server will be blocked until the ban expires or is manually removed.

Why use fail2ban vs. static Nginx deny lists

* Use Nginx `allow`/`deny` for static whitelists/blacklists (trusted administrative networks, known bad IPs).
* Use fail2ban for dynamic, automated blocking of abusive clients (brute-force or repeated failed auth attempts).
* Combine both: keep a small static allow list for trusted sources and rely on fail2ban to handle unknown or changing threats.

Final notes and references

* Always validate Nginx config changes with `nginx -t` and reload Nginx: `nginx -s reload` or `systemctl reload nginx`.
* After changing fail2ban configuration, restart the service and verify jails with `fail2ban-client status` and `fail2ban-client status <jail>`.

Further reading:

* Nginx access control: [https://nginx.org/en/docs/http/ngx\_http\_access\_module.html](https://nginx.org/en/docs/http/ngx_http_access_module.html)
* fail2ban project: [https://www.fail2ban.org/](https://www.fail2ban.org/)
* curl SSL docs: [https://curl.se/docs/sslcerts.html](https://curl.se/docs/sslcerts.html)

That completes this demo of blocking traffic with Nginx and using fail2ban to automate IP bans for repeated failed authentication attempts.

> **[Watch Video](https://learn.kodekloud.com/user/courses/nginx-for-beginners/module/8905470e-b1ea-48ec-b0cd-711687ce7159/lesson/a317f900-cb06-48ac-822a-12a9f64d432f)**

> **[Practice Lab](https://learn.kodekloud.com/user/courses/nginx-for-beginners/module/8905470e-b1ea-48ec-b0cd-711687ce7159/lesson/66fef843-b5b8-4c77-a01b-ce3999f9643c)**


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.