HellPot is an HTTP honeypot for clients that ignore robots.txt, probe common
application paths, or otherwise wander where they were told not to go. When a
client reaches a configured trap route, HellPot responds with an endless stream
of generated HTML-like text.
The generated stream comes from an internal adaptation of
Heffalump, a Markov-chain text
generator. HellPot keeps that code under internal/heffalump because it is an
implementation detail of the honeypot, not a public library API.
Under the hood HellPot uses:
- fasthttp for HTTP serving.
- koanf for TOML and environment-based configuration.
- zerolog for structured JSON logging.
- A bundled Markov source based on Nietzsche's The Birth of Tragedy.
HellPot is intentionally annoying to bad clients. That is the entire point. Deploy it thoughtfully.
- Prefer running HellPot behind a reverse proxy and route only known trap paths to it.
- Do not put normal website traffic behind HellPot unless you really want those users to receive an endless response.
- Use
http.router.catchall = trueonly for dedicated trap hosts or deliberate fallback/error routing. - Tune
performance.restrict_concurrency,performance.max_workers, and your reverse proxy rate limits for the hardware you actually have. - Forwarded client IP headers are used only when the immediate peer is listed
in
http.trusted_proxies; leave the list empty for direct public listeners. - Unix sockets default to owner-only access. Choose broader permissions only when local clients are intentionally allowed to connect.
- Logs can grow quickly on noisy hosts. Use log rotation or Docker stdout logging with an external log collector.
- Go 1.26.4 or newer for building from source.
- Docker or another OCI-compatible builder if you want container images.
- Linux, Windows, macOS, and FreeBSD are supported build targets.
- Unix socket serving is supported on Linux, macOS, and FreeBSD. Windows falls back to TCP serving.
Download a release binary from:
https://github.com/telekom-security/hellpot/releases/latest
Then run it directly:
./HellPot --help
./HellPot --genconfig
./HellPot -c config.tomlgit clone https://github.com/telekom-security/hellpot
cd hellpot
make
./HellPot --helpBuild a local image:
docker build --build-arg VERSION=0.61 -t hellpot:0.61 .Run with the bundled Docker config:
docker run --rm -p 8080:8080 hellpot:0.61Run with Docker Compose:
docker compose up --buildRun with your own config:
docker run --rm \
-p 8080:8080 \
-v "$PWD/config.toml:/config:ro" \
hellpot:0.61The Docker image runs from scratch as a numeric non-root user and uses
docker_config.toml by default. That file enables logger.docker_logging, so
logs go to stdout instead of a file under /logs.
Published image names used by the release workflow:
telekom-security/hellpot
ghcr.io/telekom-security/hellpot
The Makefile provides the usual local workflow:
make deps # go mod tidy -v
make check # go vet ./... and go test ./...
make build # build ./cmd/HellPot into ./HellPot
make run # go run ./cmd/HellPotThe default project version is stored in VERSION. make build embeds that
value in the binary unless you override it:
make build
VERSION=0.61 make buildEquivalent raw Go build:
go build -trimpath -o HellPot ./cmd/HellPotBuild with a version embedded in the binary:
go build -trimpath \
-ldflags "-s -w -X main.version=0.61" \
-o HellPot \
./cmd/HellPotThe release workflow passes the tag or ref name through the same linker flag.
HellPot [options]
Options:
-c, --config <file> Specify config file
-v, --debug Enable debug logging
-vv, --trace Enable trace logging
--nocolor Disable color and banner
--banner Show banner + version and exit
--genconfig Write default config to ./config.toml and exit
--version Show version and exit
-h, --help Show this help and exitExamples:
./HellPot --version
./HellPot --genconfig
./HellPot -c config.toml
./HellPot -c config.toml -v
./HellPot -c config.toml -vv --nocolorHellPot uses TOML configuration. Generate a starter file:
./HellPot --genconfigThe generated file is commented and includes every currently supported option.
Run with an explicit file:
./HellPot -c config.tomlWhen no -c or --config is provided, HellPot looks for configuration in this
order:
/etc/HellPot/config.tomlon non-Windows systems.- The user config directory, usually
$XDG_CONFIG_HOME/HellPot/config.tomlor$HOME/.config/HellPot/config.toml. ./config.tomlonly if the user config directory is unavailable.
If the chosen default config file is missing, HellPot writes a default config to
that location and then loads it. Explicit -c paths must already exist.
Every config key can be overridden with an environment variable prefixed with
HELLPOT_.
Rules:
- The prefix is stripped.
- The name is lowercased.
- Single underscores become dots.
- Double underscores become literal underscores inside a key segment.
Examples:
HELLPOT_HTTP_BIND__ADDR="0.0.0.0" ./HellPot
HELLPOT_HTTP_BIND__PORT="8081" ./HellPot
HELLPOT_LOGGER_DEBUG="false" ./HellPot
HELLPOT_HTTP_ROUTER_CATCHALL="true" ./HellPot[deception]
server_name = "nginx"
[http]
bind_addr = "127.0.0.1"
bind_port = "8080"
# Only trust this header when the peer is listed in trusted_proxies.
real_ip_header = "X-Real-IP"
trusted_proxies = []
uagent_string_blacklist = ["Cloudflare-Traffic-Manager", "curl"]
use_unix_socket = false
unix_socket_path = "/var/run/hellpot"
unix_socket_permissions = "0600"
[http.router]
catchall = false
makerobots = true
paths = ["wp-login.php", "wp-login"]
[logger]
debug = true
trace = false
nocolor = false
use_date_filename = true
docker_logging = false
console_time_format = "3:04PM"
[performance]
restrict_concurrency = false
max_workers = 256| Key | Type | Default | Description |
|---|---|---|---|
deception.server_name |
string | nginx |
Value used for the HTTP Server header. Reverse proxies may hide or replace it. |
http.bind_addr |
string | 127.0.0.1 |
TCP bind address. Use 0.0.0.0 for container or public network binds. |
http.bind_port |
string | 8080 |
TCP bind port. Kept as a string for compatibility with existing configs. |
http.real_ip_header |
string | X-Real-IP |
Header used for logging the original client IP behind a reverse proxy. |
http.trusted_proxies |
list of CIDRs | [] |
Peers allowed to supply http.real_ip_header; the header is ignored for all other peers and must contain one valid IP address. |
http.uagent_string_blacklist |
list | ["Cloudflare-Traffic-Manager"] |
Case-sensitive user-agent substrings that receive 404 Not found instead of the endless stream. |
http.use_unix_socket |
bool | false |
Enables Unix socket serving on supported platforms. Overrides TCP serving. |
http.unix_socket_path |
string | /var/run/hellpot |
Unix socket path when http.use_unix_socket is enabled. |
http.unix_socket_permissions |
string | 0600 |
Octal permissions applied to the Unix socket after binding. |
http.router.catchall |
bool | false |
When true, all GET paths are trap paths and robots.txt is not generated by HellPot. |
http.router.makerobots |
bool | true |
When true and catchall is false, HellPot serves /robots.txt. |
http.router.paths |
list | ["wp-login.php", "wp-login"] |
Trap paths used for route registration and robots.txt disallow entries. |
logger.debug |
bool | true |
Enables debug-level logging. Can also be forced with -v or --debug. |
logger.trace |
bool | false |
Enables trace-level logging. Can also be forced with -vv or --trace. |
logger.directory |
string | empty | Directory for JSON log files. Empty falls back to $HOME/.local/share/HellPot/logs. |
logger.nocolor |
bool | false |
Disables color and the decorative banner. Defaults to true on Windows. |
logger.use_date_filename |
bool | true |
Adds the current date/time to generated log filenames. |
logger.docker_logging |
bool | false |
Sends logs to stdout and disables file logging. The bundled Docker config sets this to true. |
logger.console_time_format |
string | 3:04PM |
Time format passed to Go's time.Format for console output. |
performance.restrict_concurrency |
bool | false |
When false, fasthttp uses its default concurrency. |
performance.max_workers |
int | 256 |
Server concurrency when performance.restrict_concurrency is true. |
[http]
bind_addr = "127.0.0.1"
bind_port = "8080"
use_unix_socket = false./HellPot -c config.tomlPath-based mode serves only configured trap paths and optionally /robots.txt.
[http.router]
catchall = false
makerobots = true
paths = ["wp-login.php", "wp-login", "admin"]Requests to /wp-login.php, /wp-login, and /admin trigger the endless
stream. /robots.txt advertises those paths as disallowed.
Catchall mode traps every GET path:
[http.router]
catchall = trueUse this for a dedicated honeypot vhost, error-document backend, or intentionally
isolated trap service. In catchall mode HellPot does not register its own
/robots.txt handler.
Unix socket mode is useful when the reverse proxy and HellPot run on the same host:
[http]
use_unix_socket = true
unix_socket_path = "/run/hellpot.sock"
unix_socket_permissions = "0660"HellPot never removes an existing path before listening. Remove a stale socket manually after confirming no HellPot process is using it; regular files and other existing paths are left untouched.
For a reverse proxy on loopback, explicitly trust only its address:
[http]
trusted_proxies = ["127.0.0.1/32", "::1/128"]location = /robots.txt {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8080$request_uri;
}
location = /wp-login.php {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8080$request_uri;
}
location = /wp-login {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8080$request_uri;
}location = /wp-login.php {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://unix:/run/hellpot.sock:$request_uri;
}This pattern sends missing paths to HellPot while normal existing files continue to be served by Apache.
<VirtualHost *:80>
ServerName example.com
ErrorDocument 404 "/content/404"
<Directory "/var/www/html/.well-known/">
ErrorDocument 404 default
</Directory>
ProxyPreserveHost On
ProxyPass "/content/" "http://127.0.0.1:8080/"
ProxyPassReverse "/content/" "http://127.0.0.1:8080/"
<Location "/content/">
RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 5
</Location>
</VirtualHost>The Dockerfile uses a multi-stage build:
golang:1.26.4builds and runsgo vetplusgo test.scratchruns the statically linked compiled binary as UID/GID65532.
Build:
docker build --build-arg VERSION=0.61 -t hellpot:0.61 .Run with port mapping:
docker run --rm -p 8080:8080 hellpot:0.61Run with a mounted config:
docker run --rm \
-p 8080:8080 \
-v "$PWD/docker_config.toml:/config:ro" \
hellpot:0.61Run with Docker Compose:
docker compose up --buildThe Compose file builds the local Dockerfile, publishes ${HELLPOT_PORT:-8080}
to container port 8080, mounts ./docker_config.toml read-only at /config,
and applies a read-only filesystem plus basic Linux capability hardening.
The image entrypoint is:
/app -c /config
The default docker_config.toml binds 0.0.0.0:8080, enables catchall mode, and
sends logs to stdout.
Default non-Docker behavior:
- Pretty console logs are written to stdout.
- JSON log files are written under
logger.directory. - If
logger.directoryis empty, HellPot uses$HOME/.local/share/HellPot/logs.
Docker behavior:
- Set
logger.docker_logging = true. - HellPot writes structured logs to stdout.
- File logging is disabled.
--nocolororlogger.nocolor = truesuppresses color/banner output.
Use -v or --debug for debug logs, and -vv or --trace for trace logs.
Common checks:
go test ./...
go test -race ./...
go vet ./...
go run github.com/securego/gosec/v2/cmd/gosec@latest ./...
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
docker build --build-arg VERSION=test -t hellpot:test .Local build and run:
make build
./HellPot --version
./HellPot --helpcmd/HellPot/ CLI entrypoint and application orchestration
internal/config/ CLI flags, TOML loading, env overrides, logger setup
internal/http/ fasthttp server, routes, robots.txt, socket listeners
internal/heffalump/ internal Markov stream generator adapted from Heffalump
internal/extra/ banner rendering and presentation helpers
docker_config.toml default container configuration
Dockerfile multi-stage Go 1.26.4 scratch image build
Makefile local development shortcuts
internal/heffalump is actively used by internal/http: trap responses call
DefaultHeffalump.WriteHell to produce the endless stream. It is internalized so
external projects do not rely on it as a public API.
HellPot's stream generator is based on
Heffalump by Carl Johnson. The
original Heffalump MIT license is retained in internal/heffalump/LICENSE.
- https://github.com/ginger51011/pandoras_pot
- A HellPot-inspired HTTP honeypot written in Rust.