Skip to content

nftset-access: Zero-latency IP blocking using Linux kernel nftables sets

Requires the Pro plan (or higher) of the GetPageSpeed NGINX Extras subscription.

Installation

You can install this module in any RHEL-based distribution, including, but not limited to:

  • RedHat Enterprise Linux 7, 8, 9 and 10
  • CentOS 7, 8, 9
  • AlmaLinux 8, 9
  • Rocky Linux 8, 9
  • Amazon Linux 2 and Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-nftset-access
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-nftset-access

Enable the module by adding the following at the top of /etc/nginx/nginx.conf:

load_module modules/ngx_http_nftset_access.so;

This document describes nginx-module-nftset-access v3.0.0 released on Feb 14 2026.


Enterprise-grade IP-based access control for NGINX using Linux nftables sets. Block threats, rate-limit abusers, challenge bots, and protect your infrastructure.

Version GetPageSpeed

⚠️ Commercial Software This is a closed-source premium module available exclusively through the GetPageSpeed Repository.

Plan requirement: Requires the Pro plan of the GetPageSpeed NGINX Extras subscription.

✨ Features

Core Features

Feature Description
Whitelist/Blacklist Allow or deny based on nftables set membership
Multiple sets Check against multiple nft sets in one directive
Live updates Modify nft sets without reloading NGINX
Custom status codes Return any HTTP status when blocking
CIDR support Use interval sets for network ranges (e.g., 192.168.1.0/24)

Performance Features

Feature Description
Per-thread sessions Thread-local libnftables contexts eliminate lock contention
LRU Cache Shared memory cache with configurable TTL
Cache hit rates Typically 95%+ hit rate reduces kernel calls

Security Features

Feature Description
Rate limiting Limit requests per IP with configurable windows
Auto-ban Automatically blacklist rate limit violators
JS Challenge Proof-of-work challenge stops automated bots
Honeypot traps Auto-blacklist IPs hitting trap URLs
Entry timeout Auto-expire blacklist entries

Operational Features

Feature Description
Dry-run mode Test configuration without blocking
Fail-open/close Control behavior on nft set errors
Prometheus metrics Native /metrics endpoint for Grafana
JSON stats Detailed statistics API
NGINX variables $nftset_result and $nftset_matched_set
##

🚀 Quick Start

1. Create nftables sets

## Create a table (if not exists)
sudo nft add table ip filter

## Create a blacklist with timeout support
sudo nft add set ip filter bad_guys '{ type ipv4_addr; flags timeout; timeout 1d; }'

## Create a rate-limit ban list
sudo nft add set ip filter ratelimited '{ type ipv4_addr; flags timeout; timeout 30m; }'

## Create a honeypot trap list
sudo nft add set ip filter honeypot '{ type ipv4_addr; flags timeout; timeout 1d; }'

## Create a whitelist with CIDR support
sudo nft add set ip filter trusted '{ type ipv4_addr; flags interval; }'
sudo nft add element ip filter trusted '{ 10.0.0.0/8, 192.168.0.0/16 }'

2. Configure NGINX

load_module modules/ngx_http_nftset_access_module.so;

http {
    server {
        listen 80;

        # Block known bad IPs (format: table:setname)
        nftset_blacklist filter:bad_guys;

        # Rate limit: 100 requests per minute
        nftset_ratelimit rate=100 window=60s autoban=filter:ratelimited;

        # Your content
        location / {
            root /var/www/html;
        }

        # Honeypot trap - returns 404 by default
        location /wp-admin.php {
            nftset_autoadd filter:honeypot timeout=86400;
        }

        # Metrics endpoint
        location /metrics {
            nftset_metrics;
            allow 127.0.0.1;
            deny all;
        }
    }
}

3. Test and reload

sudo nginx -t && sudo nginx -s reload

📦 Installation

This module is available exclusively through the GetPageSpeed Premium Repository.

Step 1: Subscribe to GetPageSpeed Repository

Visit GetPageSpeed Repository Subscription to get access.

Step 2: Install the Repository

## RHEL/CentOS/Rocky/Alma Linux 8+
sudo dnf install https://extras.getpagespeed.com/release-latest.rpm

Step 3: Install the Module

sudo dnf install nginx-module-nftset-access

Step 4: Enable the Module

Add to /etc/nginx/nginx.conf before any http {} blocks:

load_module modules/ngx_http_nftset_access_module.so;

Step 5: Reload NGINX

sudo nginx -t && sudo systemctl reload nginx

📖 Configuration Reference

Set Specification Format

All directives that reference nftables sets use the format: table:setname

  • table — The nftables table name (e.g., filter, firewalld)
  • setname — The set name within that table

The IP family (ip for IPv4, ip6 for IPv6) is auto-detected from the client's IP address.

Examples:

nftset_blacklist filter:blocklist;           # IPv4 client → ip filter blocklist
                                             # IPv6 client → ip6 filter blocklist
nftset_whitelist filter:trusted;
nftset_autoadd filter:honeypot timeout=3600;

Access Control

nftset_blacklist table:set1 [table:set2 ...]

Context: http, server Default:

Blocks requests if the client IP appears in any of the listed nft sets. Multiple sets are checked in order until a match is found.

## Single set
nftset_blacklist filter:bad_guys;

## Multiple sets (OR logic - blocked if in ANY set)
nftset_blacklist filter:spammers filter:hackers filter:tor_exits;

## Disable
nftset_blacklist off;

nftset_whitelist table:set1 [table:set2 ...]

Context: http, server Default:

Allows requests only if the client IP appears in at least one of the listed nft sets. All other IPs are rejected.

## Only allow trusted IPs
nftset_whitelist filter:trusted_partners filter:office_ips;

Important: Whitelisted IPs bypass all module restrictions including: - Rate limiting (nftset_ratelimit) - JavaScript challenges (nftset_challenge)

This is useful for admin IPs that shouldn't be subject to rate limits or challenges:

## Admin IPs bypass rate limiting and challenges
nftset_whitelist filter:admin_ips;
nftset_ratelimit rate=100 window=1m autoban=filter:ratelimited ban_time=1800;
nftset_challenge on;

nftset_status code

Context: http, server Default: 403

HTTP status code returned when a request is blocked.

nftset_status 403;   # Forbidden (default)
nftset_status 444;   # Close connection without response (NGINX special)
nftset_status 429;   # Too Many Requests
nftset_status 503;   # Service Unavailable

Caching & Performance

nftset_cache_ttl time

Context: http, server Default: 60s

How long to cache nft set lookup results. Cached results avoid repeated kernel calls for the same IP.

nftset_cache_ttl 30s;    # 30 seconds
nftset_cache_ttl 5m;     # 5 minutes
nftset_cache_ttl 1h;     # 1 hour

Debugging Note: If you remove an IP from an nft set but the module still reports it as "matched", this is due to caching. The cached result will expire after the configured TTL. For immediate effect during testing, you can temporarily set nftset_cache_ttl 0; to disable caching (not recommended for production due to performance impact).

Performance Impact: - Higher TTL = Better performance, but slower to reflect nft set changes - Lower TTL = More responsive to nft set changes, but more kernel calls - Recommended: 30s to 5m for most use cases

nftset_fail_open on|off

Context: http, server Default: off

Controls behavior when an nft set lookup fails (e.g., set doesn't exist).

nftset_fail_open off;   # Deny on error (secure, default)
nftset_fail_open on;    # Allow on error (available but risky)

nftset_dryrun on|off

Context: http, server Default: off

When enabled, logs what would be blocked but doesn't actually block. Perfect for testing new rules in production.

nftset_dryrun on;   # Log but don't block

Check logs for messages like:

nftset: DRYRUN would block 1.2.3.4 (matched: filter:bad_guys)

Important: When using $nftset_result and $nftset_matched_set variables with dryrun mode, these values reflect the point-in-time state when the request was processed—not the current state of the nft set. If you check the nft set manually later and don't find the IP, possible reasons include:

  1. Timeout expiration: The IP was added with a timeout (e.g., timeout 1d) and has since expired
  2. Cache lag: The module caches lookup results (default 60s). An entry removed from the nft set may still show as "matched" until the cache expires
  3. Manual removal: Someone or something (fail2ban, scripts) removed the entry

This is expected behavior—dryrun shows you exactly what production would see at request time.

Rate Limiting

nftset_ratelimit parameters

Context: http, server Default:

Limits requests per IP within a time window. Can automatically add violators to an nft set.

Parameters:

Parameter Required Description
rate=N Yes Maximum requests per window
window=TIME No Time window (default: 60s)
autoban=TABLE:SET No nft set to add violators
ban_time=N No Seconds until auto-expire (default: 3600)

Examples:

## Basic: 100 requests per minute
nftset_ratelimit rate=100;

## With custom window: 1000 requests per hour
nftset_ratelimit rate=1000 window=1h;

## With auto-ban: Add violators to nft set for 30 minutes
nftset_ratelimit rate=60 window=1m autoban=filter:ratelimited ban_time=1800;

## Strict API protection
nftset_ratelimit rate=10 window=1s autoban=filter:api_abusers ban_time=3600;

How it works: 1. Each IP gets a request counter and window start time 2. Counter increments on each request 3. When window expires, counter resets 4. If counter exceeds rate, returns 429 Too Many Requests 5. If autoban is set, IP is added to specified nft set

Note: Rate limit state is stored in shared memory and survives worker restarts.

JavaScript Challenge

nftset_challenge on|off

Context: http, server Default: off

Enables JavaScript challenge mode. Browsers must solve a proof-of-work puzzle to access the site. Effective against automated bots and scrapers.

nftset_challenge on;

How it works: 1. First request receives a challenge page (HTTP 503) 2. Browser executes JavaScript that solves a hash puzzle 3. Solution is stored in a cookie (_nftset_verified) 4. Subsequent requests with valid cookie pass through 5. Cookie expires after 24 hours

nftset_challenge_difficulty level

Context: http, server Default: 2

Controls challenge difficulty (1-8). Higher = longer solve time.

Level Approximate Solve Time
1 ~100ms
2 ~500ms (default)
3 ~1 second
4 ~2 seconds
5 ~5 seconds
6+ ~10+ seconds
nftset_challenge on;
nftset_challenge_difficulty 3;  # ~1 second solve time

Challenge Page Features: - Modern, responsive design - Animated loading spinner - Progress feedback - Auto-redirect on success - No external dependencies

Honeypot Auto-add

nftset_autoadd table:setname [table:setname2 ...] [timeout=seconds] [status=code]

Context: server, location Default:

Automatically adds client IP to specified nft set(s) when the location is accessed and returns an HTTP status code. Perfect for honeypot traps.

Parameters:

Parameter Required Description
table:setname Yes Target nft set (can specify multiple)
timeout=N No Entry timeout in seconds
status=N No HTTP status code to return (default: 404)

Examples:

## Basic: Add to honeypot set and return 404 (default)
location /config.php {
    nftset_autoadd filter:honeypot;
}

## With timeout: Auto-expire after 24 hours
location /wp-admin.php {
    nftset_autoadd filter:scanners timeout=86400;
}

## Return 403 Forbidden instead of 404
location /admin.php {
    nftset_autoadd filter:honeypot timeout=86400 status=403;
}

## Add to multiple sets (IPv4 and IPv6)
location /trap {
    nftset_autoadd filter:honeypot4 filter:honeypot6 timeout=86400;
}

Common Honeypot Paths:

## WordPress traps - return 404 to look like missing file
location ~ ^/(wp-admin\.php|wp-login\.php|xmlrpc\.php)$ {
    nftset_autoadd filter:honeypot timeout=86400;
}

## Config file traps - return 403 to simulate forbidden access
location ~ ^/(\.env|config\.php|phpinfo\.php)$ {
    nftset_autoadd filter:honeypot timeout=86400 status=403;
}

## Shell/exploit traps - severe, block for 1 week
location ~ ^/(shell|cmd|eval|exec)\.php$ {
    nftset_autoadd filter:malicious timeout=604800 status=403;
}

Note: When an IP is auto-added, the module immediately returns the specified HTTP status code (default 404), preventing further request processing. The connection's keep-alive is also disabled to prevent further requests on the same connection.

Observability

nftset_stats

Context: location Default:

Enables the JSON statistics endpoint.

location = /_stats {
    nftset_stats;
    allow 127.0.0.1;
    allow 10.0.0.0/8;
    deny all;
}

See JSON Stats API for response format.

nftset_metrics

Context: location Default:

Enables the Prometheus metrics endpoint.

location = /metrics {
    nftset_metrics;
    allow 127.0.0.1;
    allow 10.0.0.0/8;
    deny all;
}

See Prometheus Metrics for available metrics.

📝 NGINX Variables

The module exposes two variables for use in logging, headers, or conditionals.

$nftset_result

The access decision made for this request.

Value Description
allow Request allowed
deny Request blocked
dryrun Would be blocked (dry-run mode)
ratelimited Rate limit exceeded
challenged Challenge page served

$nftset_matched_set

Name of the nft set that matched (if any), in table:setname format. Empty if no match.

Note: This variable reflects the match state at request time, not the current state of the nft set. If you check the nft set manually and don't find the IP: - The entry may have expired (nft sets support per-entry timeouts) - The module's cache (default 60s) may show a recently-removed entry as still matched - Something may have removed the entry after the request was processed

Usage Examples

Custom access log:

log_format security '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    'nftset_result="$nftset_result" '
                    'matched_set="$nftset_matched_set"';

access_log /var/log/nginx/security.log security;

Add headers for debugging:

add_header X-NFTSet-Result $nftset_result always;
add_header X-NFTSet-Matched $nftset_matched_set always;

Conditional logging:

## Only log blocked requests
map $nftset_result $loggable {
    "deny"  1;
    default 0;
}

access_log /var/log/nginx/blocked.log combined if=$loggable;

📊 Prometheus Metrics

The /metrics endpoint returns metrics in Prometheus exposition format.

Available Metrics

## HELP nginx_nftset_requests_total Total requests processed
## TYPE nginx_nftset_requests_total counter
nginx_nftset_requests_total{result="checked"} 1234567
nginx_nftset_requests_total{result="allowed"} 1234000
nginx_nftset_requests_total{result="blocked"} 500
nginx_nftset_requests_total{result="error"} 67

## HELP nginx_nftset_cache_total Cache operations
## TYPE nginx_nftset_cache_total counter
nginx_nftset_cache_total{result="hit"} 1200000
nginx_nftset_cache_total{result="miss"} 34567

## HELP nginx_nftset_cache_entries Current cache entries
## TYPE nginx_nftset_cache_entries gauge
nginx_nftset_cache_entries 5432

## HELP nginx_nftset_autoadd_total Auto-add operations
## TYPE nginx_nftset_autoadd_total counter
nginx_nftset_autoadd_total{result="success"} 42
nginx_nftset_autoadd_total{result="failed"} 3

## HELP nginx_nftset_ratelimit_total Rate limit events
## TYPE nginx_nftset_ratelimit_total counter
nginx_nftset_ratelimit_total{action="triggered"} 156
nginx_nftset_ratelimit_total{action="autobanned"} 23

## HELP nginx_nftset_challenge_total Challenge events
## TYPE nginx_nftset_challenge_total counter
nginx_nftset_challenge_total{result="issued"} 1000
nginx_nftset_challenge_total{result="passed"} 950
nginx_nftset_challenge_total{result="failed"} 50

## HELP nginx_nftset_uptime_seconds Module uptime
## TYPE nginx_nftset_uptime_seconds gauge
nginx_nftset_uptime_seconds 86400

Grafana Dashboard Queries

Request rate by result:

rate(nginx_nftset_requests_total[5m])

Block rate:

rate(nginx_nftset_requests_total{result="blocked"}[5m])

Cache hit rate:

rate(nginx_nftset_cache_total{result="hit"}[5m]) /
(rate(nginx_nftset_cache_total{result="hit"}[5m]) + rate(nginx_nftset_cache_total{result="miss"}[5m]))

Rate limit triggers per minute:

rate(nginx_nftset_ratelimit_total{action="triggered"}[1m]) * 60

📈 JSON Stats API

The /_stats endpoint returns detailed statistics in JSON format.

Response Format

{
  "version": "3.0.0",
  "uptime_seconds": 86400,
  "requests": {
    "checked": 1234567,
    "allowed": 1234000,
    "blocked": 500,
    "errors": 67
  },
  "cache": {
    "hits": 1200000,
    "misses": 34567,
    "entries": 5432,
    "hit_rate": 97.20
  },
  "autoadd": {
    "success": 42,
    "failed": 3
  },
  "ratelimit": {
    "triggered": 156,
    "autobanned": 23
  },
  "challenge": {
    "issued": 1000,
    "passed": 950,
    "failed": 50
  }
}

Field Descriptions

Field Description
version Module version
uptime_seconds Seconds since module loaded
requests.checked Total requests processed
requests.allowed Requests that passed
requests.blocked Requests that were blocked
requests.errors nft set lookup errors
cache.hits Cache hits (avoided kernel call)
cache.misses Cache misses (required kernel call)
cache.entries Current cached entries
cache.hit_rate Hit rate percentage
autoadd.success Successful honeypot additions
autoadd.failed Failed honeypot additions
ratelimit.triggered Rate limit violations
ratelimit.autobanned IPs auto-added to ban list
challenge.issued Challenge pages served
challenge.passed Challenges solved successfully
challenge.failed Challenge failures
##

🏗️ Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                           REQUEST FLOW                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   Incoming Request                                                   │
│         │                                                            │
│         ▼                                                            │
│   ┌───────────────┐                                                  │
│   │  Rate Limit   │──── Exceeded? ────▶ 429 + Auto-ban              │
│   │    Check      │                                                  │
│   └───────┬───────┘                                                  │
│           │ OK                                                       │
│           ▼                                                          │
│   ┌───────────────┐                                                  │
│   │   Challenge   │──── No cookie? ────▶ Serve JS Puzzle            │
│   │    Check      │                                                  │
│   └───────┬───────┘                                                  │
│           │ Passed                                                   │
│           ▼                                                          │
│   ┌───────────────┐     ┌─────────────┐                             │
│   │  Cache Check  │────▶│   HIT       │────▶ Use cached result      │
│   └───────┬───────┘     └─────────────┘                             │
│           │ MISS                                                     │
│           ▼                                                          │
│   ┌───────────────┐                                                  │
│   │  nft Query    │──── Thread-local libnftables context            │
│   │  (kernel)     │                                                  │
│   └───────┬───────┘                                                  │
│           │                                                          │
│           ▼                                                          │
│   ┌───────────────┐                                                  │
│   │ Store in Cache│                                                  │
│   └───────┬───────┘                                                  │
│           │                                                          │
│           ▼                                                          │
│   ┌───────────────┐                                                  │
│   │    Decision   │──── Blacklist match? ────▶ Block (403/444)      │
│   │               │──── Whitelist miss?  ────▶ Block (403/444)      │
│   └───────┬───────┘                                                  │
│           │ Allow                                                    │
│           ▼                                                          │
│   ┌───────────────┐                                                  │
│   │   Honeypot    │──── Location match? ────▶ Add to nft set        │
│   │    Check      │                                                  │
│   └───────┬───────┘                                                  │
│           │                                                          │
│           ▼                                                          │
│       Continue to                                                    │
│       Content Handler                                                │
│                                                                      │
├─────────────────────────────────────────────────────────────────────┤
│                        SHARED MEMORY                                 │
│  ┌────────────────┬─────────────────┬─────────────────────────────┐ │
│  │     Stats      │    LRU Cache    │    Rate Limit Buckets       │ │
│  │   (counters)   │  (IP → Result)  │   (IP → Request Count)      │ │
│  └────────────────┴─────────────────┴─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘

Memory Layout

Component Location Purpose
libnftables context Thread-local Per-worker context to avoid locks
Lookup cache Shared memory LRU cache of IP→result mappings
Rate limit buckets Shared memory Per-IP request counters
Statistics Shared memory Atomic counters for metrics
##

📚 Examples

Example 1: Basic Blacklist

## Create nft table and set
sudo nft add table ip filter
sudo nft add set ip filter blacklist '{ type ipv4_addr; }'
sudo nft add element ip filter blacklist '{ 1.2.3.4 }'
server {
    listen 80;

    nftset_blacklist filter:blacklist;

    location / {
        root /var/www/html;
    }
}

Example 2: API with Rate Limiting

server {
    listen 80;

    # Strict rate limiting for API
    nftset_ratelimit rate=100 window=1m autoban=filter:api_banned ban_time=3600;

    # Only allow known partners
    nftset_whitelist filter:api_partners;
    nftset_status 401;

    location /api/ {
        proxy_pass http://backend;
    }
}

Example 3: Full Security Stack

server {
    listen 80 default_server;

    # Layer 1: Known threats
    nftset_blacklist filter:malware_ips filter:tor_exits filter:datacenter_ranges;
    nftset_status 444;
    nftset_cache_ttl 5m;

    # Layer 2: Rate limiting
    nftset_ratelimit rate=60 window=1m autoban=filter:ratelimited ban_time=1800;

    # Layer 3: Bot challenge
    nftset_challenge on;
    nftset_challenge_difficulty 2;

    # Real content
    location / {
        root /var/www/html;
    }

    # Honeypot traps - return 404 (default) to look like missing files
    location ~ ^/(wp-admin|phpmyadmin|admin)\.php$ {
        nftset_autoadd filter:honeypot timeout=86400;
    }

    # Monitoring
    location = /metrics {
        nftset_metrics;
        allow 10.0.0.0/8;
        deny all;
    }
}

Example 4: Dry-run Testing

server {
    listen 80;

    # Test new rules without blocking
    nftset_blacklist filter:new_threat_list;
    nftset_dryrun on;

    location / {
        root /var/www/html;
    }
}

Check logs:

tail -f /var/log/nginx/error.log | grep "DRYRUN"

Example 5: CIDR Whitelist (Network Ranges)

## Create interval set for CIDR ranges
sudo nft add table ip filter
sudo nft add set ip filter trusted '{ type ipv4_addr; flags interval; }'
sudo nft add element ip filter trusted '{ 192.168.1.0/24, 10.0.0.0/8 }'

## IPv6 version
sudo nft add table ip6 filter
sudo nft add set ip6 filter trusted6 '{ type ipv6_addr; flags interval; }'
sudo nft add element ip6 filter trusted6 '{ 2001:db8::/32 }'
server {
    listen 80;

    # Whitelist entire networks
    nftset_whitelist filter:trusted;

    location / {
        root /var/www/html;
    }
}

🔧 Troubleshooting

Module not loading

nginx: [emerg] dlopen() failed

Solution: Ensure NGINX was built with --with-compat and the module was built against the same NGINX version.

nft set not found

nftset: set 'filter:myset' does not exist

Solution: Create the nft table and set before starting NGINX:

sudo nft add table ip filter
sudo nft add set ip filter myset '{ type ipv4_addr; }'

TIP: List available sets with:

nft list sets

Permission denied

nftset: kernel error

Solution: NGINX worker needs CAP_NET_ADMIN capability:

sudo setcap cap_net_admin+ep /usr/sbin/nginx

SELinux denials (RHEL/CentOS/AlmaLinux)

SELinux is preventing /usr/sbin/nginx from using netlink_netfilter_socket

Solution: Install the included SELinux policy module:

cd selinux/
sudo ./install.sh

Or manually:

## Verify
semodule -l | grep nginx_nftset

The policy allows httpd_t (NGINX's SELinux domain) to use netlink_netfilter sockets required by libnftables.

High memory usage

Solution: Reduce cache TTL or limit cache size in shared memory configuration.

Rate limiting not working

Solution: Ensure the nft set for auto-ban exists and has timeout support:

sudo nft add table ip filter
sudo nft add set ip filter ratelimited '{ type ipv4_addr; flags timeout; timeout 1h; }'

Log shows "matched=table:setname" but IP isn't in the nft set

This is expected behavior. The module reports what it saw at request time. If you check the nft set later and don't find the IP:

  1. Timeout expiration: The IP was added with a timeout and has since expired

    # Check set flags
    nft list set ip filter setname
    # Look for "flags timeout" in the output
    

  2. Module cache: The module caches lookups (default 60s). A recently-removed IP may still appear as "matched"

    # Temporarily disable cache for debugging (not for production!)
    nftset_cache_ttl 0;
    

  3. Entry was removed: fail2ban, scripts, or manual commands may have removed it

  4. Trap configuration issue: If using honeypot traps with nftset_autoadd, legitimate bots may have triggered traps. Check your trap locations don't overlap with legitimate bot paths (like sitemaps, robots.txt). Use robots.txt to exclude trap paths from crawling.

autoadd fails with timeout error

This means you're using timeout=N in nftset_autoadd but the nft set was created without timeout support.

Solution: Recreate the nft set with timeout support:

## Delete and recreate with timeout flag
sudo nft delete set ip filter honeypot
sudo nft add set ip filter honeypot '{ type ipv4_addr; flags timeout; timeout 1d; }'

🔄 Migration from ipset-access

If you're migrating from the older ngx_http_ipset_access_module, follow these steps:

1. Convert ipsets to nft sets

## Old ipset
ipset create bad_guys hash:ip timeout 86400

## New nft set equivalent
nft add table ip filter
nft add set ip filter bad_guys '{ type ipv4_addr; flags timeout; timeout 1d; }'

## For CIDR ranges (hash:net → interval flag)
## Old: ipset create networks hash:net
## New:
nft add set ip filter networks '{ type ipv4_addr; flags interval; }'

2. Update NGINX configuration

Old (ipset) New (nftset)
ipset_blacklist bad_guys; nftset_blacklist filter:bad_guys;
ipset_whitelist trusted; nftset_whitelist filter:trusted;
ipset_autoadd honeypot timeout=3600; nftset_autoadd filter:honeypot timeout=3600;
ipset_ratelimit rate=100 autoban=ratelimited; nftset_ratelimit rate=100 autoban=filter:ratelimited;
ipset_challenge on; nftset_challenge on;
ipset_challenge_difficulty 3; nftset_challenge_difficulty 3;
ipset_dryrun on; nftset_dryrun on;
ipset_fail_open on; nftset_fail_open on;
ipset_cache_ttl 60s; nftset_cache_ttl 60s;
ipset_status 403; nftset_status 403;
ipset_stats; nftset_stats;
ipset_metrics; nftset_metrics;
$ipset_result $nftset_result
$ipset_matched_set $nftset_matched_set

3. Update log formats

## Old
log_format security '... ipset_result="$ipset_result" matched_set="$ipset_matched_set"';

## New
log_format security '... nftset_result="$nftset_result" matched_set="$nftset_matched_set"';

4. Update Prometheus/Grafana queries

Old Metric New Metric
nginx_ipset_requests_total nginx_nftset_requests_total
nginx_ipset_cache_total nginx_nftset_cache_total
nginx_ipset_cache_entries nginx_nftset_cache_entries
nginx_ipset_autoadd_total nginx_nftset_autoadd_total
nginx_ipset_ratelimit_total nginx_nftset_ratelimit_total
nginx_ipset_challenge_total nginx_nftset_challenge_total
nginx_ipset_uptime_seconds nginx_nftset_uptime_seconds

5. Key differences

Feature ipset-access nftset-access
Backend libipset (kernel ipsets) libnftables (nftables)
Set format setname table:setname
CIDR sets hash:net type flags interval
Family Specified in set type Auto-detected from client IP
firewalld iptables backend only nftables backend compatible

Why migrate?

  • RHEL 9/Rocky 9 compatibility: firewalld defaults to nftables backend
  • Modern kernel support: nftables is the future of Linux firewalling
  • Unified management: Use nft commands for both firewall and access control
  • Better CIDR support: Interval sets handle network ranges efficiently

📋 Requirements

  • NGINX ≥ 1.22 (built with --with-compat)
  • Linux kernel with nftables support
  • libnftables library and development headers
  • Capabilities: CAP_NET_ADMIN for nftables operations

📜 License

This is proprietary software. All rights reserved.

Available exclusively through GetPageSpeed Premium Repository.

👤 Author

Danila Vershinin GetPageSpeed LLC

🆘 Support

NGINX NFTSet Access Module
A premium NGINX module by GetPageSpeed LLC
www.getpagespeed.com