Skip to content

Instantly share code, notes, and snippets.

@sybeck2k
Last active March 5, 2026 14:59
Show Gist options
  • Select an option

  • Save sybeck2k/0fab0e1e96113d7c7685cf25535b59e2 to your computer and use it in GitHub Desktop.

Select an option

Save sybeck2k/0fab0e1e96113d7c7685cf25535b59e2 to your computer and use it in GitHub Desktop.
Extend jc21/nginx-proxy-manager with nginx-ultimate-bad-bot-blocker — no source changes, drop-in Dockerfile
# nginx-ultimate-bad-bot-blocker, NPM integration init script
#
# This file is *sourced* (not exec'd) by 00-all.sh during the s6 prepare phase,
# so it shares the bash environment and helpers (log_info, log_fatal, etc.)
# defined in /usr/bin/common.sh.
#
# NOTE: botblocker-nginx-settings.conf and globalblacklist.conf live in
# /etc/nginx/conf.d/ and are already loaded automatically by NPM's nginx.conf
# wildcard: include /etc/nginx/conf.d/*.conf;
# No http_top.conf injection is needed; adding them again would cause
# duplicate-directive errors.
#
# What this script does (idempotent, safe to run on every container start):
#
# Appends per-server bot-blocking directives to the three server-level
# custom-conf stubs that NPM's Liquid templates include inside every
# generated server block:
# server_proxy.conf (proxy hosts)
# server_redirect.conf (redirect hosts)
# server_dead.conf (404 hosts)
log_info "Initializing nginx-ultimate-bad-bot-blocker..."
mkdir -p /data/nginx/custom
for CONF in server_proxy server_redirect server_dead; do
CONF_FILE="/data/nginx/custom/${CONF}.conf"
if ! grep -q "blockbots.conf" "${CONF_FILE}" 2>/dev/null; then
printf '\n# nginx-ultimate-bad-bot-blocker\ninclude /etc/nginx/bots.d/ddos.conf;\ninclude /etc/nginx/bots.d/blockbots.conf;\n' \
>> "${CONF_FILE}"
log_info "Bot blocker: added server-block includes to ${CONF_FILE}"
fi
done
log_info "nginx-ultimate-bad-bot-blocker ready."

What this does

Adds nginx-ultimate-bad-bot-blocker to Nginx Proxy Manager via a single Dockerfile.botblocker, without touching any NPM source files.

How to use

  docker build -f Dockerfile.botblocker -t npm-with-botblocker .

Or with docker compose — replace image: in your docker-compose.yml with:

  build:
    context: .
    dockerfile: Dockerfile.botblocker
  image: npm-with-botblocker:latest

How it works

  • Build time: install-ngxblocker downloads the full blocklist into /etc/nginx/conf.d/ and /etc/nginx/bots.d/. NPM's nginx.conf already wildcards conf.d/*.conf so no extra http-block config is needed.
  • Start time: 15-botblocker.sh runs during the s6 prepare phase and appends bot-blocking includes to NPM's server_proxy.conf, server_redirect.conf, and server_dead.conf custom stubs — injecting protection into every generated proxy/redirect/dead-host server block.
  • Runtime: The ngxblocker-update s6 service refreshes the blocklist every 24 h (first run after 1 h).

Notes

  • Bad bots receive 444 (connection dropped — no response sent)
  • Googlebot and other legitimate crawlers are allowlisted by default
  • To persist custom allow/denylists across rebuilds, mount /etc/nginx/bots.d as a volume
# syntax=docker/dockerfile:1
# Dockerfile.botblocker
#
# Extends jc21/nginx-proxy-manager with nginx-ultimate-bad-bot-blocker:
# https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker
#
# Build:
# docker build -f Dockerfile.botblocker -t npm-with-botblocker .
#
# Run (same volumes / ports as standard NPM):
# docker run -d \
# -p 80:80 -p 81:81 -p 443:443 \
# -v ./data:/data \
# -v ./letsencrypt:/etc/letsencrypt \
# npm-with-botblocker
#
# How the integration works (no source files are modified):
#
# 1. BUILD TIME install-ngxblocker downloads the blocklist files:
# /etc/nginx/conf.d/botblocker-nginx-settings.conf (http-block rate limits)
# /etc/nginx/conf.d/globalblacklist.conf (http-block map directives)
# /etc/nginx/bots.d/blockbots.conf (per-server block rule)
# /etc/nginx/bots.d/ddos-protection.conf (per-server rate limiting)
# /etc/nginx/bots.d/whitelist-*.conf (customisable allowlists)
# /etc/nginx/bots.d/custom-bad-*.conf (customisable denylists)
#
# 2. START TIME the 15-botblocker.sh prepare script appends includes to NPM's
# custom-conf stubs (idempotent; safe to restart):
# /data/nginx/custom/http_top.conf  loaded inside the http { } block
# /data/nginx/custom/server_proxy.conf  loaded inside every proxy-host server block
# /data/nginx/custom/server_redirect.conf  loaded inside every redirect-host server block
# /data/nginx/custom/server_dead.conf  loaded inside every dead-host server block
#
# 3. RUNTIME the ngxblocker-update s6 service refreshes the blocklist every 24 h
# (first refresh occurs 1 h after container start).
#
# Customisation:
# Edit these files on the host (persisted in the /data volume) to whitelist
# IPs or domains, or add extra bad user-agents / referrers:
# /etc/nginx/bots.d/whitelist-ips.conf
# /etc/nginx/bots.d/whitelist-domains.conf
# /etc/nginx/bots.d/custom-bad-user-agents.conf
# /etc/nginx/bots.d/custom-bad-referrers.conf
# (These files live in the image; mount them as volumes to persist changes
# across image rebuilds, e.g. -v ./bots.d:/etc/nginx/bots.d)
FROM jc21/nginx-proxy-manager:latest
# wget may already be present; install it if not.
RUN apt-get update \
&& apt-get install -y --no-install-recommends wget \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Download and execute the official install script.
# This creates /etc/nginx/conf.d/botblocker-nginx-settings.conf,
# /etc/nginx/conf.d/globalblacklist.conf, and /etc/nginx/bots.d/*.
# The setup-ngxblocker step (which would patch nginx.conf directly) is
# intentionally skipped ÔÇö NPM's custom-conf hooks handle that instead.
RUN wget -q \
https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/install-ngxblocker \
-O /usr/local/sbin/install-ngxblocker \
&& chmod +x /usr/local/sbin/install-ngxblocker \
&& /usr/local/sbin/install-ngxblocker -x \
# NPM already sets server_names_hash_* and variables_hash_* in nginx.conf /
# its own conf.d files; comment them out to avoid duplicate-directive errors.
&& sed -i -E 's/^(server_names_hash_|variables_hash_)/#\1/' \
/etc/nginx/conf.d/botblocker-nginx-settings.conf
# Inject bot blocker into the fallback default server block (port 80) so
# bad bots are blocked even before any proxy host is configured.
RUN sed -i \
'/include conf.d\/include\/block-exploits.conf;/a\\tinclude /etc/nginx/bots.d/ddos.conf;\n\tinclude /etc/nginx/bots.d/blockbots.conf;' \
/etc/nginx/conf.d/default.conf
# ---------------------------------------------------------------------------
# s6 prepare-phase hook: inject the blocker into NPM's nginx config stubs.
# The script is sourced (not exec'd) by 00-all.sh, so it shares the bash
# environment including log_info / log_fatal helpers from common.sh.
# ---------------------------------------------------------------------------
COPY ./15-botblocker.sh \
/etc/s6-overlay/s6-rc.d/prepare/15-botblocker.sh
RUN sed -i 's/\r//' /etc/s6-overlay/s6-rc.d/prepare/15-botblocker.sh \
&& chmod +x /etc/s6-overlay/s6-rc.d/prepare/15-botblocker.sh
# Insert the source call just before the banner line so it runs after all
# paths, ownership, and dynamic-config steps have completed.
RUN sed -i \
's|\. /etc/s6-overlay/s6-rc\.d/prepare/90-banner\.sh|. /etc/s6-overlay/s6-rc.d/prepare/15-botblocker.sh\n. /etc/s6-overlay/s6-rc.d/prepare/90-banner.sh|' \
/etc/s6-overlay/s6-rc.d/prepare/00-all.sh
# ---------------------------------------------------------------------------
# s6 longrun service: refresh the blocklist daily.
# ---------------------------------------------------------------------------
RUN mkdir -p /etc/s6-overlay/s6-rc.d/ngxblocker-update/dependencies.d
COPY ./ngxblocker-update-run.sh \
/etc/s6-overlay/s6-rc.d/ngxblocker-update/run
RUN sed -i 's/\r//' /etc/s6-overlay/s6-rc.d/ngxblocker-update/run \
&& echo "longrun" > /etc/s6-overlay/s6-rc.d/ngxblocker-update/type \
&& chmod +x /etc/s6-overlay/s6-rc.d/ngxblocker-update/run \
&& touch /etc/s6-overlay/s6-rc.d/ngxblocker-update/dependencies.d/prepare \
&& touch /etc/s6-overlay/s6-rc.d/user/contents.d/ngxblocker-update
LABEL org.label-schema.name="nginx-proxy-manager-bot-blocker" \
org.label-schema.description="NPM extended with nginx-ultimate-bad-bot-blocker" \
org.label-schema.url="https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker"
#!/bin/sh
# s6 longrun service: refresh the nginx-ultimate-bad-bot-blocker blocklist.
#
# Waits 1 hour after container start (so nginx is fully up before the first
# reload), then refreshes every 24 hours. All output goes to the s6 log.
exec 2>&1
sleep 3600
while true; do
echo "[ngxblocker-update] Updating blocklist..."
/usr/local/sbin/update-ngxblocker -n \
&& echo "[ngxblocker-update] Update complete." \
|| echo "[ngxblocker-update] Update failed (will retry in 24 h)."
sleep 86400
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment