SikkerGuard Update — Custom Blacklists, Stats API, and CIDR Support
When we shipped SikkerGuard, the pitch was simple: one Docker container, one API key, and your server starts blocking known attackers at the kernel level using SikkerAPI threat intelligence. No scripts, no cron jobs, no maintenance.
That covers the automated side. But the most common request since launch has been some version of the same question: can I add my own IPs too?
Now you can. This update adds three features:
- Custom blacklist files — mount a text file of IPs and CIDR ranges into the container
- Stats API endpoint — a persistent, authenticated endpoint for monitoring dashboards
- SQLite-backed block tracking — blocked connection stats that survive container restarts
All three are opt-in. If you don't set the env vars, SikkerGuard works exactly as before.
Custom Blacklists
You maintain a text file. SikkerGuard reads it on every pull cycle and merges it with the SikkerAPI blacklist. One IP or CIDR range per line. Comments and blank lines are ignored.
Example blacklist.txt:
# Scanners I've seen in my logs
45.33.32.156
198.51.100.42
# Block an entire subnet
203.0.113.0/24
# That one range from <website that provides blacklist ips>
185.220.101.0/24Mount it into the container and set the env var:
environment:
- SIKKER_CUSTOM_BLACKLIST=/etc/sikkerguard/blacklist.txt
volumes:
- /[sikkerguard_directory]/blacklist.txt:/etc/sikkerguard/blacklist.txt:roTwo things to know:
Mount to `/etc/sikkerguard/`, not `/var/lib/sikkerguard/`. SikkerGuard uses a named Docker volume at /var/lib/sikkerguard/ for its cache and stats database. Named volumes take precedence over bind mounts at the same path, so your file would be invisible. Mount to /etc/sikkerguard/ instead.
The file must exist before the container starts. If Docker encounters a bind mount target that doesn't exist on the host, it creates a directory instead of a file. Create the file first, then start the container.
When SikkerGuard loads the blacklist, you'll see the merge in the logs:
19:34:32 INFO Merged <count> custom IPs from /etc/sikkerguard/blacklist.txt (total: <total_count>)Your custom IPs go through the same safety pipeline as the SikkerAPI blacklist: the auto-whitelist still protects your gateway, DNS, and LAN. Private ranges are still stripped. Sanity checks still apply. You can't accidentally block yourself, even with a bad blocklist file.
CIDR Support
CIDR ranges are expanded to individual IPs before they hit the ipset. A /24 becomes 256 entries, a /28 becomes 16. This keeps the existing hash:ip ipset type and its O(1) per-packet lookup.
The minimum prefix is /16 (65,536 IPs). Anything larger is rejected with a warning, if you need to block a /8, you have bigger problems than a blocklist file can solve.
Only IPv4 CIDRs are supported. IPv6 addresses work as individual entries but not as ranges.
No Restart Required
SikkerGuard re-reads the file on every pull cycle. Edit it, save it, and the changes take effect on the next cycle. The default pull interval is 24 hours, but if you want faster pickup, set SIKKER_PULL_INTERVAL=60 for hourly refreshes.
Stats API
SikkerGuard now tracks every blocked connection in a local SQLite database and exposes the data through an authenticated API endpoint. This is designed for monitoring dashboards — Grafana, or anything that can make an HTTP request with a bearer token.
Setup
Generate a key:
openssl rand -base64 32Add it to your .env:
SIKKER_STATS_KEY=your_generated_key_hereQuery the endpoint:
curl -H "Authorization: Bearer your_generated_key_here" http://127.0.0.1:8080/v1/guard/statsResponse:
{
"blockedIps": 62348,
"uniqueIpsBlocked": 142,
"totalPacketsBlocked": 8431,
"mostBlockedProtocol": "ssh",
"mostBlockedProtocolCount": 5210,
"lastBlockedIp": "[ip]",
"lastBlockedProtocol": "telnet",
"lastBlockedAt": 1773083990075
}| Field | Description |
|---|---|
blockedIps | Total IPs currently in the blocklist (SikkerAPI + custom) |
uniqueIpsBlocked | Unique IPs that actually sent traffic and got blocked |
totalPacketsBlocked | Total blocked packets across all IPs |
mostBlockedProtocol | Protocol with the highest blocked packet count |
mostBlockedProtocolCount | Packet count for that protocol |
lastBlockedIp | Most recently blocked IP address |
lastBlockedProtocol | Protocol of the most recent block |
lastBlockedAt | Timestamp (epoch milliseconds) of the most recent block |
The key distinction is blockedIps vs uniqueIpsBlocked. The first is "how many IPs are in the firewall rules." The second is "how many of those IPs actually tried to connect and got dropped." On a server running SikkerGuard for a week, you might have 60,000 IPs in the blocklist but only a couple of thousand that actually hit your server.
Authentication
The stats key is a simple bearer token, SikkerGuard compares the Authorization header against the value in SIKKER_STATS_KEY. No remote validation, no API call, no network dependency. If the key is wrong, you get a 401 Unauthorized. If SIKKER_STATS_KEY isn't set, the endpoint returns 404 Not Found.
This is intentionally simple. The stats endpoint serves local data about your own firewall. It doesn't need the same authentication infrastructure as the SikkerAPI threat intelligence endpoints. Generate a key with openssl, put it in your .env, done.
Persistence
Stats are stored in SQLite at /var/lib/sikkerguard/stats.db (inside the named Docker volume, so they persist across container restarts). Every blocked connection is recorded with IP, protocol, packet count, and timestamps. The database grows with the number of unique IP+protocol combinations your firewall encounters, not with the total number of blocked packets.
On a typical server, after a month of operation, the database is a few megabytes.
New Environment Variables
| Variable | Default | Description |
|---|---|---|
SIKKER_CUSTOM_BLACKLIST | (empty) | Path to custom blacklist file inside the container |
SIKKER_STATS_KEY | (empty) | Bearer token for /v1/guard/stats |
SIKKER_STATS_DB | /var/lib/sikkerguard/stats.db | SQLite database path |
All three are optional. If unset, SikkerGuard behaves identically to the previous version.
Full docker-compose.yml Example
Here's a complete setup with all new features enabled:
services:
sikkerguard:
image: sikkerapi/guard:latest
network_mode: host
cap_add:
- NET_ADMIN
- SYSLOG
devices:
- /dev/kmsg:/dev/kmsg
env_file:
- .env
environment:
- SIKKER_CUSTOM_BLACKLIST=/etc/sikkerguard/blacklist.txt
volumes:
- /your_sikkerguard_directory/blacklist.txt:/etc/sikkerguard/blacklist.txt:ro
- sikkerguard-data:/var/lib/sikkerguard
restart: unless-stopped
volumes:
sikkerguard-data:.env:
SIKKER_API_KEY=sk_free_your_key_here
SIKKER_STATS_KEY=your_openssl_rand_base64_32_outputWhat's Next
The full SikkerGuard documentation has been updated with the new configuration options. If you're new to SikkerGuard, start there, one container, one API key, and your server is blocking known attackers in under 60 seconds. Free tier included. Read for full SikkerGuard installation guide.
Comments
No comments yet. Be the first to share your thoughts!