
Self-contained SSH honeypot for capturing attacker interactions and turning them into structured security intelligence.
SSHintel is a lightweight SSH honeypot built using Python and Paramiko. It simulates a fake Linux shell to log unauthorized access attempts, capture credentials, and analyze attacker behavior in a controlled environment.
ls, cd, pwd, cat, echo, grep, find, tree, head, tail, wc, stat, ps, df, free, env, id, whoami, etc.)--tarpit mode to slow down attackers with delayed outputSSHintel is not a full Bash/Linux shell. It simulates a believable subset of common commands to gather attacker telemetry. Commands are dispatched by a lightweight registry; adding a command means adding a small handler function.
pip install -r requirements.txt
Note: The SSH host key is automatically generated on first run. No manual key generation needed.
Run the honeypot with a specific port, username, and password:
python3 run.py serve --port 2222 --username user1 --password pass123
Default port is
2222and host is0.0.0.0.
The honeypot automatically generates an SSH host key at static/server.key on first run if one doesn't exist. No manual key generation is needed.
To enable tarpit mode:
python3 run.py serve --port 2222 --username user1 --password pass123 --tarpit
To disable SQLite telemetry (JSONL only):
python3 run.py serve --port 2222 --username user1 --password pass123 --no-db
Sensor settings use CLI > environment > configuration file > defaults.
Only explicitly supplied CLI flags override other sources. This applies to
serve; dashboard/query commands retain their own CLI settings.
On Python 3.11+:
cp sshintel.toml.example sshintel.toml
chmod 600 sshintel.toml
python3 run.py serve
On Python 3.8/3.10, use the dependency-free JSON equivalent:
cp sshintel.json.example sshintel.json
chmod 600 sshintel.json
python3 run.py serve
Edit the copied file before deployment. TOML requires Python 3.11+; older
versions fail with a clear instruction to use JSON. With no explicit file,
SSHintel looks for sshintel.toml, then sshintel.json in the current directory.
Use serve --config /path/to/settings.json or HONEYPOT_CONFIG to select another
file (--config wins). Explicit missing files, malformed files, unknown keys,
and invalid values fail before the server or database starts. File paths inside
configuration are relative to the process working directory, not the config file.
| File key | Environment variable | Default |
|---|---|---|
host | HONEYPOT_HOST | 0.0.0.0 |
port | HONEYPOT_PORT (legacy alias: PORT) | 2222 |
username | HONEYPOT_USERNAME | user1 |
password | HONEYPOT_PASSWORD | pass123 |
tarpit | HONEYPOT_TARPIT | false |
tarpit_max_seconds | HONEYPOT_TARPIT_MAX_SECONDS | 120 |
max_connections | HONEYPOT_MAX_CONNECTIONS | 50 |
auth_timeout | HONEYPOT_AUTH_TIMEOUT | 60 |
session_idle_timeout | HONEYPOT_SESSION_IDLE_TIMEOUT | 300 |
sensor_id | HONEYPOT_SENSOR_ID | unset |
db_path | HONEYPOT_DB_PATH | data/sshintel.db |
no_db | HONEYPOT_NO_DB | false |
fetch_payloads | HONEYPOT_FETCH_PAYLOADS | false (reserved) |
Environment booleans accept true/false, 1/0, yes/no, and on/off.
HONEYPOT_PORT takes precedence over PORT. Ports must be 1–65535 and limits
and timeouts must be positive integers. Use --no-tarpit, --db-enabled, or
--no-fetch-payloads to override a lower-priority true setting. Database paths
use the existing --db CLI flag. Payload fetching is not implemented: enabling
fetch_payloads is rejected rather than silently ignored.
Local sshintel.toml/sshintel.json files are Git-ignored and excluded from Docker
builds because they can contain credentials. Mount a configuration file read-only
into a container instead. Docker consumes environment variables directly (without
shell-interpolating credentials); its existing ENV defaults take precedence over
a mounted file. Override those variables as needed or use explicit CLI flags.
SSHintel guards against resource exhaustion from many concurrent connections or connections kept alive indefinitely. These are configurable via the CLI:
| Flag | Default | Purpose |
|---|---|---|
--max-connections | 50 | Maximum simultaneous active connections; extra connections are rejected and logged as a connection_rejected event |
--auth-timeout | 60 (s) | Time allowed to complete the SSH handshake/authentication; stalled clients are disconnected |
--session-idle-timeout | 300 (s) | Inactivity timeout for an authenticated shell; an idle session is ended, but an actively-typing attacker is never killed |
Example:
python3 run.py serve --port 2222 --username user1 --password pass123 \
--max-connections 25 --auth-timeout 30 --session-idle-timeout 300
When too many connections are open, the extra connection is closed immediately and a connection_rejected security event (with reason: connection_limit) is written to the JSONL log. A stalled authentication is recorded as a disconnect with reason: auth_timeout; an idle shell ends with reason: idle_timeout.
Tarpit mode intentionally sends output slowly to keep an attacker engaged, so the tarpit banner loop is not subject to the inactivity timeout — but tarpit sessions do count against the connection limit.
SSHintel includes a local web dashboard that visualizes the security telemetry stored in SQLite.
python3 run.py dashboard
Then open http://localhost:5000 in your browser.
The dashboard reads from the SQLite database at
data/sshintel.dbby default. Start the honeypot first so telemetry is being captured, then launch the dashboard to watch it populate.
Click a session ID (or navigate to /session/<session_id>) to open the session investigation view, which reconstructs a single attack chronologically: