{"id":51820376,"url":"https://github.com/gistrec/nginx-scanner-trap","last_synced_at":"2026-07-22T04:45:30.788Z","repository":{"id":367827996,"uuid":"1281837231","full_name":"gistrec/nginx-scanner-trap","owner":"gistrec","description":"Turn an nginx box into a honeypot for vulnerability scanners: the first probe of /.env or /.git gets the bot banned on all ports via fail2ban + nftables. One script, verified install.","archived":false,"fork":false,"pushed_at":"2026-07-05T05:21:06.000Z","size":46,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-22T04:45:29.597Z","etag":null,"topics":["bash","bot-detection","debian","devops","fail2ban","hardening","honeypot","intrusion-prevention","linux","nftables","nginx","security","server-security","sysadmin","ubuntu","vulnerability-scanner","web-security"],"latest_commit_sha":null,"homepage":"https://gistrec.cloud/blog/nginx-honeypot-fail2ban/ru/","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gistrec.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-27T01:39:35.000Z","updated_at":"2026-07-05T05:20:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/gistrec/nginx-scanner-trap","commit_stats":null,"previous_names":["gistrec/nginx-scanner-trap"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gistrec/nginx-scanner-trap","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gistrec%2Fnginx-scanner-trap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gistrec%2Fnginx-scanner-trap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gistrec%2Fnginx-scanner-trap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gistrec%2Fnginx-scanner-trap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gistrec","download_url":"https://codeload.github.com/gistrec/nginx-scanner-trap/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gistrec%2Fnginx-scanner-trap/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35748424,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-22T02:00:06.236Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","bot-detection","debian","devops","fail2ban","hardening","honeypot","intrusion-prevention","linux","nftables","nginx","security","server-security","sysadmin","ubuntu","vulnerability-scanner","web-security"],"created_at":"2026-07-22T04:45:30.113Z","updated_at":"2026-07-22T04:45:30.781Z","avatar_url":"https://github.com/gistrec.png","language":"Shell","funding_links":[],"categories":["Tools"],"sub_categories":["Lua Modules"],"readme":"# nginx-scanner-trap\n\n**English** · [Русский](README.ru.md)\n\n[![CI](https://github.com/gistrec/nginx-scanner-trap/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/gistrec/nginx-scanner-trap/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/gistrec/nginx-scanner-trap)](https://github.com/gistrec/nginx-scanner-trap/releases) ![Platform](https://img.shields.io/badge/platform-Debian%20%7C%20Ubuntu-blue) [![License](https://img.shields.io/github/license/gistrec/nginx-scanner-trap)](LICENSE)\n\nTurn an nginx box into a honeypot for vulnerability scanners — and ban them automatically with **fail2ban + nftables**. One script, works out of the box.\n\nBots probe every public IP for `/.env`, `/.git/config`, `/wp-login.php`, `/phpmyadmin`… around the clock. Each is a harmless 404, but in bulk it's wasted load and noise that buries real 404s in your alerts. `setup-honeypot.sh` makes the **first** such request cost the bot its access to the whole host.\n\n\u003e Full write-up — the why and the how, step by step (in Russian): https://gistrec.cloud/blog/nginx-honeypot-fail2ban/ru/\n\n## How it works\n\n1. A scanner requests a path no real client ever asks for: `/.env`, `/.git/config`, `/backup.sql`, …\n2. nginx answers with a plain 404 and writes one line to `/var/log/nginx/honeypot.log`.\n3. fail2ban watches that log with `maxretry = 1` — the first probe is enough.\n4. nftables blocks the IP on **all** ports, SSH included. Repeat offenders get longer bans, up to a week.\n\nThe log line from step 2 looks like this:\n\n```\n203.0.113.7 - [04/Jul/2026:03:12:45 +0000] \"GET /.env HTTP/1.1\"\n```\n\n## What the script sets up\n\nEverything below is idempotent — re-running the script is safe.\n\n- **A honeypot `log_format`** (http context) that records the client IP, the timestamp, and the request line — so you can see *what* was probed.\n- **`honeypot.conf`** — traps paths that only scanners request: `/.env`, `/.git`, `/.aws`, `/.ssh`, `config.php.bak`, `backup.sql`. Logs the probe, returns 404.\n- **Aggressive extras, opt-in** via `--aggressive`: `wp-login.php`, `xmlrpc.php`, `phpmyadmin`. Off by default — on WordPress or phpMyAdmin hosts these are legitimate paths.\n- **`deny-dotfiles.conf`** — 404 for every other dotfile, with an exception for `/.well-known/` so Let's Encrypt renewals keep working.\n- **Wiring** — both snippets go into every `server { }` in `sites-enabled/`. Backups land outside the config tree, and if `nginx -t` fails the script rolls everything back on its own.\n- **A fail2ban jail** — `maxretry=1`, `nftables-allports`, incremental bans. The script verifies that the jail loaded, that it watches the honeypot log, and — via a synthetic ban of a TEST-NET IP — that bans actually reach nftables.\n- **A whitelist that keeps you out of your own trap** — the script detects your IP, validates it, asks for extras, and stops on an empty whitelist: proceeding takes an explicit confirmation, or `--allow-no-whitelist` in `-y` mode.\n\n## Requirements\n\nDebian/Ubuntu, nginx already installed, root access. The script installs `fail2ban` and `nftables` itself if they're missing.\n\n## Quick start\n\n\u003e ⚠️ The script runs as root and edits nginx + fail2ban configs. **Read it before you run it** — never pipe a random script into `sudo bash` blind.\n\nStep 1 — download:\n\n```bash\ncurl -fsSLO https://raw.githubusercontent.com/gistrec/nginx-scanner-trap/main/setup-honeypot.sh\n```\n\nStep 2 — verify it against the published checksum of the current `main`:\n\n```bash\necho \"d7bd727335602b57ea5b250d03c53ca5  setup-honeypot.sh\" | md5sum -c -\n# → setup-honeypot.sh: OK\n\n# or the stronger one:\nsha256sum setup-honeypot.sh\n# expected: 2c34338c3973b3edf8df565eb8d5122d33d66c1aa2ed3d61166b65ee6f2eff23\n```\n\n\u003e The checksums change with every script update — CI fails if the published sums drift from the script. A hash served from the same repo only catches transport corruption, not a tampered repo — so still read the script.\n\nStep 3 — read it:\n\n```bash\nless setup-honeypot.sh\n```\n\nStep 4 — run it (interactive: confirms your IP, asks for extra whitelist entries):\n\n```bash\nsudo bash setup-honeypot.sh\n```\n\nPrefer a preview first? `--dry-run` shows every change without making any:\n\n```bash\nsudo bash setup-honeypot.sh --dry-run\n```\n\nOne-liner — only if you trust it:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/gistrec/nginx-scanner-trap/main/setup-honeypot.sh | sudo bash\n```\n\n## Options\n\n| flag | meaning |\n|------|---------|\n| `--ip \u003cip\u003e` | your admin IP (skip auto-detection) |\n| `--extra \"\u003cips\u003e\"` | extra IPs/CIDRs to whitelist (space-separated) |\n| `--aggressive` | also trap `wp-login.php` / `phpmyadmin` / `xmlrpc.php` (not for WordPress/phpMyAdmin hosts) |\n| `--no-wire` | don't touch `sites-enabled`; print the include lines instead |\n| `--allow-no-whitelist` | proceed even if no admin IP gets whitelisted (risky) |\n| `--bantime \u003cspec\u003e` | base ban time (default `86400`; accepts `1h`/`1d`/`1w`) |\n| `--maxtime \u003cspec\u003e` | max incremental ban (default `1w`) |\n| `-y, --yes` | assume yes, no prompts |\n| `--dry-run` | show what would happen, change nothing |\n| `-h, --help` | show this help and exit |\n\n## Safety\n\n- **Idempotent** — re-running never duplicates includes; the script rewrites its managed files in place.\n- **Backups outside nginx** — every touched file gets a copy under `/var/backups/nginx-scanner-trap/\u003ctimestamp\u003e/`, away from the config tree where `include sites-enabled/*` could load a stray `.bak`.\n- **`nginx -t` with rollback** — if wiring breaks the config, the script restores every file it created or modified and aborts.\n- **Won't lock you out** — the whitelist gets your IP (detected via `SSH_CONNECTION` → `SSH_CLIENT` → `who am i` → `ss`, then validated), anything you add, and always `127.0.0.1/8 ::1`. With an empty whitelist the script stops and asks for an explicit confirmation; in `-y` mode it refuses unless you pass `--allow-no-whitelist`.\n- **Scoped fail2ban changes** — only `ignoreip` goes into `jail.local` `[DEFAULT]`, so how other jails such as `sshd` ban stays untouched; the script never overwrites an existing `jail.local`.\n\n## Behind a CDN / reverse proxy\n\nIf nginx sits behind Cloudflare or any reverse proxy, configure [`real_ip`](https://nginx.org/en/docs/http/ngx_http_realip_module.html) (`set_real_ip_from` + `real_ip_header`) **before** enabling the trap. Otherwise `$remote_addr` holds the proxy's address, and fail2ban bans your proxy or CDN instead of the scanner.\n\n## Check / unban\n\n```bash\nsudo fail2ban-client status nginx-honeypot\nsudo nft list table inet f2b-table\nsudo fail2ban-client set nginx-honeypot unbanip \u003cIP\u003e   # one IP\nsudo fail2ban-client unban --all                       # everything\n```\n\nA healthy jail looks like this:\n\n```\nStatus for the jail: nginx-honeypot\n|- Filter\n|  |- Currently failed: 0\n|  |- Total failed:     23\n|  `- File list:        /var/log/nginx/honeypot.log\n`- Actions\n   |- Currently banned: 17\n   |- Total banned:     23\n   `- Banned IP list:   203.0.113.7 198.51.100.23 …\n```\n\n## Uninstall\n\nEverything the script manages is listed below; removal is four steps. Order matters — un-wire the includes first, or `nginx -t` fails on missing snippets.\n\n```bash\n# 1. Un-wire: drop the two include lines from every server block\n#    (--follow-symlinks keeps the sites-enabled symlinks intact)\nsudo sed -i --follow-symlinks \\\n  '\\#include /etc/nginx/snippets/honeypot.conf;#d; \\#include /etc/nginx/snippets/deny-dotfiles.conf;#d' \\\n  /etc/nginx/sites-enabled/*\n\n# 2. Delete the managed nginx files, validate, reload\nsudo rm -f /etc/nginx/conf.d/honeypot-log.conf \\\n           /etc/nginx/snippets/honeypot.conf \\\n           /etc/nginx/snippets/deny-dotfiles.conf\nsudo nginx -t \u0026\u0026 sudo systemctl reload nginx\n\n# 3. Stop the jail (this also clears its nftables rules), delete its config\nsudo fail2ban-client stop nginx-honeypot\nsudo rm -f /etc/fail2ban/jail.d/nginx-honeypot.conf \\\n           /etc/fail2ban/filter.d/nginx-honeypot.conf\nsudo fail2ban-client reload\n\n# 4. Optional: the honeypot log and the backups\nsudo rm -f /var/log/nginx/honeypot.log\nsudo rm -rf /var/backups/nginx-scanner-trap\n```\n\nIf you wired the includes into files outside `sites-enabled/` (the `--no-wire` path), remove those two lines there as well.\n\nDeliberately left in place:\n\n- `/etc/fail2ban/jail.local` — if the script created it, its `[DEFAULT]` `ignoreip` is a global whitelist that other jails (e.g. `sshd`) may now rely on. Remove it only if nothing else needs it.\n- The `fail2ban` / `nftables` packages — other jails may be using them.\n\nHeads-up: without `deny-dotfiles.conf`, dotfiles (`/.git`, `/.env`, …) are reachable again unless your vhosts block them some other way.\n\n## License\n\n[MIT](LICENSE) © Aleksandr Kovalko\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgistrec%2Fnginx-scanner-trap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgistrec%2Fnginx-scanner-trap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgistrec%2Fnginx-scanner-trap/lists"}