Overview
Block known malicious IPs at the Nginx level before they reach your application. A cron job pulls the latest threat data from the Blacklist API in plaintext and converts it into an Nginx geo map file. Only reloads Nginx when the list has actually changed. Works for standalone web servers, reverse proxies, and CDN edge nodes — no Lua modules or OpenResty required.
Data flow
Cron triggers→Fetches blacklist→Writes geo map file→nginx -s reload→Blocked IPs get 403
Installation
Download the update script, save your API key, and run it once to generate the initial blocklist file. The script handles the full pipeline: fetch, convert, diff, and conditional reload.
1Download the update script
$ sudo curl -o /usr/local/bin/sikkerapi-nginx-update \
"https://sikkerapi.com/nginx/sikkerapi-nginx-update.sh"
$ sudo chmod +x /usr/local/bin/sikkerapi-nginx-update
2Save your API key
$ echo "sk_free_..." | sudo tee /etc/nginx/sikkerapi.key
$ sudo chmod 600 /etc/nginx/sikkerapi.key
3Run initial update
$ sudo /usr/local/bin/sikkerapi-nginx-update
SikkerAPI: blocklist updated (4832 IPs), Nginx reloaded
Nginx Configuration
Add the geo block to your nginx.conf inside the http context. The geo module is included by default in all standard Nginx builds (ngx_http_geo_module). Then use the variable in your server blocks to deny matched IPs.
nginx.conf — http context
http {
# Load SikkerAPI blocklist
geo $sikkerapi_blocked {
default 0;
include /etc/nginx/sikkerapi_blocklist.conf;
}
server {
# Block matched IPs
if ($sikkerapi_blocked) {
return 403;
}
# ... rest of your config
}
}
Generated geo map file format
# SikkerAPI blocklist - 2026-03-15 00:00:00 UTC
# IPs: 4832 | Score >= 70
203.0.113.42 1;
198.51.100.17 1;
192.0.2.99 1;
# ... one IP per line
Cron Schedule
Schedule the update script to run automatically. The script only reloads Nginx when the blocklist has actually changed, so frequent runs are safe. One daily pull gives maximum coverage without wasting quota. The blacklist is sorted by confidence score (highest first), then by recency.
Recommended cron schedules
Once daily at 3 AM0 3 * * * — fits all tiers, recommended default.
Twice daily0 */12 * * * — more frequent updates.
Hourly0 * * * * — use higher tiers for hourly refreshes.
crontab -e
# Update SikkerAPI blocklist daily at 3 AM
0 3 * * * /usr/local/bin/sikkerapi-nginx-update >> /var/log/sikkerapi-nginx.log 2>&1
Reverse Proxy & CDN
If Nginx sits behind a load balancer, CDN, or proxy, the client IP is in the X-Forwarded-For header instead of $remote_addr. Use the realip module to extract the real client IP before the geo check runs. For CDN edge nodes, block at the edge to prevent malicious traffic from reaching upstream servers.
nginx.conf — real IP behind proxy
http {
# Trust your upstream proxy/LB
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
# Now geo uses the real client IP
geo $sikkerapi_blocked {
default 0;
include /etc/nginx/sikkerapi_blocklist.conf;
}
}
HTTP-only blocklist for web servers
# Add to /etc/nginx/sikkerapi.key or set as env var
# Only fetch IPs with observed HTTP-layer attack activity
$ SIKKERAPI_EXTRA="&protocols=http" sikkerapi-nginx-update
Two-Tier Setup (Block + Challenge)
Instead of a single blocklist, use two lists at different confidence thresholds: hard-block high-confidence threats and challenge medium-confidence ones with a captcha or interstitial page. This reduces false positives for borderline IPs while still protecting against confirmed threats. The captcha page is served by your application — SikkerAPI provides the signal, you decide the challenge.
Two-tier strategy
90+Hard block (403 Forbidden). Very high confidence, suitable for all environments.
50–89Redirect to captcha/challenge page. On success, set a cookie to bypass.
No matchAllow through normally.
Update script (two lists)
#!/bin/bash
set -euo pipefail
API_KEY="sk_free_..."
API_URL="https://api.sikkerapi.com/v1/key/blacklist"
# High confidence - hard block
curl -sf \
-H "Authorization: Bearer $API_KEY" \
"$API_URL?plaintext=true&scoreMinimum=90" \
| awk '{print $1" 1;"}' \
> /etc/nginx/sikkerapi_block.conf
# Medium confidence - challenge
curl -sf \
-H "Authorization: Bearer $API_KEY" \
"$API_URL?plaintext=true&scoreMinimum=50&limit=5000" \
| awk '{print $1" 1;"}' \
> /etc/nginx/sikkerapi_challenge.conf
nginx -t && nginx -s reload
nginx.conf — two-tier geo blocks
geo $sikkerapi_block {
default 0;
include /etc/nginx/sikkerapi_block.conf;
}
geo $sikkerapi_challenge {
default 0;
include /etc/nginx/sikkerapi_challenge.conf;
}
server {
# Hard block high-confidence threats
if ($sikkerapi_block) {
return 403;
}
# Challenge medium-confidence (skip if cookie set)
set $needs_challenge 0;
if ($sikkerapi_challenge) {
set $needs_challenge 1;
}
if ($cookie_sikker_verified) {
set $needs_challenge 0;
}
if ($needs_challenge) {
return 302 /challenge;
}
}
Filtering Options
Customize the blacklist with query parameters or environment variables. The most important parameter is scoreMinimum — the confidence threshold for including an IP. See the Blacklist API for the full parameter reference.
Script environment variables
SIKKERAPI_KEY/etc/nginx/sikkerapi.keyYour SikkerAPI key (sk_...). Falls back to key file.
SIKKERAPI_SCORE70Minimum confidence score (1–100).
SIKKERAPI_LIMIT5000Maximum IPs to fetch.
SIKKERAPI_GEO_FILE/etc/nginx/sikkerapi_blocklist.confOutput geo map file path.
SIKKERAPI_EXTRA—Extra API parameters (e.g. &protocols=http).
SSH attacks only, score 80+
$ SIKKERAPI_SCORE=80 SIKKERAPI_EXTRA="&protocols=ssh" sikkerapi-nginx-update
High severity, exclude US/EU
$ SIKKERAPI_EXTRA="&minSeverity=high&exceptCountries=US,GB,DE,FR,NL" sikkerapi-nginx-update
IPv4 only, top 5,000
$ SIKKERAPI_LIMIT=5000 SIKKERAPI_EXTRA="&ipVersion=4" sikkerapi-nginx-update
Confidence score guidance
90+Very high confidence. Safe for hard-blocking in most environments.
70–89High confidence. Good default. Review logs periodically for false positives.
50–69Moderate confidence. Consider rate limiting or challenging instead of blocking.
Rate Limits
The blacklist endpoint has its own daily quota, separate from lookup quotas. Daily refreshes stay well within limits for all tiers. Check the response headers to monitor your usage.
Daily quotas by tier
Free10,000 blocklist IPs/day. Create free key Paid tiersHigher limits. See pricing for details. Check remaining quota
$ curl -sI \
-H "Authorization: Bearer sk_..." \
"https://api.sikkerapi.com/v1/key/blacklist?plaintext=true&limit=1" \
| grep -i x-blacklist
X-Blacklist-Limit: 10000
X-Blacklist-Used: 5000
X-Blacklist-Remaining: 5000
Verification
After setup, verify the blocklist was created and IPs are being blocked. Check your Blacklist Export dashboard to see which IPs are in your current list.
1Check blocklist file
$ wc -l /etc/nginx/sikkerapi_blocklist.conf
4832 /etc/nginx/sikkerapi_blocklist.conf
$ head -5 /etc/nginx/sikkerapi_blocklist.conf
# SikkerAPI blocklist - 2026-03-15 00:00:00 UTC
# IPs: 4832 | Score >= 70
203.0.113.42 1;
198.51.100.17 1;
192.0.2.99 1;
2Test Nginx config
$ nginx -t
nginx: configuration file /etc/nginx/nginx.conf test is successful
3Verify a blocked IP returns 403
$ curl -s -o /dev/null -w "%{http_code}" \
--resolve example.com:80:203.0.113.42 \
http://example.com/
403
4Watch access log for blocks
$ tail -f /var/log/nginx/access.log | grep " 403 "
Troubleshooting
Common issues and how to resolve them. Run the update script manually first to isolate whether the issue is with the API or Nginx configuration.
Common issues
Empty blocklist fileverify key starts with sk_ and is enabled Nginx won’t reloadrun nginx -t to check geo file syntax
IPs not being blockedgeo block must be in http context, not inside server
Behind a proxy — wrong IPs blockedconfigure set_real_ip_from before the geo block
429 — Quota exceededupgrade tier or reduce refresh frequency Script exits with “No API key”set SIKKERAPI_KEY or create /etc/nginx/sikkerapi.key
Script Reference
The update script at /usr/local/bin/sikkerapi-nginx-update handles the full pipeline: fetch the plaintext blacklist, convert to Nginx geo format, compare with the existing file, and only reload Nginx if the list has changed. Handles 429 (quota exceeded) gracefully by keeping the existing list. All configuration via environment variables — no editing the script required.
/usr/local/bin/sikkerapi-nginx-update (excerpt)
#!/usr/bin/env bash
# SikkerAPI Nginx Blocklist Updater
set -euo pipefail
# Configuration via environment variables
API_KEY="$${SIKKERAPI_KEY:-}"
SCORE="$${SIKKERAPI_SCORE:-70}"
LIMIT="$${SIKKERAPI_LIMIT:-5000}"
GEO_FILE="$${SIKKERAPI_GEO_FILE:-/etc/nginx/sikkerapi_blocklist.conf}"
# Falls back to /etc/nginx/sikkerapi.key
# Fetches blacklist, converts to "IP 1;" format
# Only reloads Nginx if list changed
# Handles 429 gracefully (keeps existing list)
if cmp -s "$GEO_TMP" "$GEO_FILE"; then
echo "SikkerAPI: blocklist unchanged"
else
mv "$GEO_TMP" "$GEO_FILE"
nginx -t && nginx -s reload
fi
Get started — Block malicious IPs at the Nginx level in under 10 minutes. The blocklist is powered by first-party honeypot data across 16 protocols with
confidence scoring. Need kernel-level blocking? Try
iptables/ipset. Want automated reporting too? See
Fail2Ban or
CSF integrations.
Create free API key →