How to Run an SSH Honeypot (Watch the Attackers, Waste Their Time and Feed CrowdSec)

Tutorial 7 showed the thousands of daily login attempts; tutorial 39 said moving SSH's port isn't security. Both true — and once real SSH is on another port (or only on the VPN), port 22 is free for something more interesting. Endlessh answers SSH connections with an endless, slow banner that keeps a bot stuck for hours at the cost of almost nothing. Cowrie goes further: a fake Ubuntu shell that accepts logins, records every command, and saves every file they try to download. Both are an education in what the internet does to an exposed port, and both export their findings to CrowdSec.
Quick reference
| Tool | What it does | Port |
|---|---|---|
| Endlessh | tarpit: sends a never-ending SSH banner, slowly | 22 |
| Cowrie | fake shell: logs usernames, passwords, commands, downloads | 22 (or 2222 behind a redirect) |
| Real SSH | on another port, or VPN/Tailscale-only (33, 47) | e.g. 2222 |
| Watch | docker compose logs -f, Cowrie's JSON log into Loki (155) | |
| Feed CrowdSec | the crowdsecurity/endlessh / cowrie parsers |
First: move the real SSH
Never run a honeypot on the port your real SSH is on. Either:
- Port 2222 in /etc/ssh/sshd_config (and ufw allow 2222/tcp, your SSH config Port 2222 — tutorial 2), or
- Keep SSH on 22 but only on the LAN and VPN: remove the port forward, ufw allow from 192.168.1.0/24 to any port 22, and forward public 22 to the honeypot instead.
The second is better: the honeypot is on the public side, real SSH never is.
Option A: Endlessh (the tarpit)
mkdir -p ~/docker/endlessh && cd ~/docker/endlessh
nano compose.yaml
services:
endlessh:
image: linuxserver/endlessh:latest
container_name: endlessh
ports:
- "22:2222"
environment:
MSDELAY: 10000
MAXLINES: 32
LOGFILE: "true"
volumes:
- ./config:/config
restart: unless-stopped
A line of banner every ten seconds, forever. On the router, forward public 22 to the server. Then:
docker compose up -d
docker compose logs -f
"ACCEPT host=203.0.113.5 … n=4" — four bots stuck. Average time trapped per bot runs to hours. CPU and memory are negligible; it's a few bytes every ten seconds per connection.
Option B: Cowrie (the fake shell)
mkdir -p ~/docker/cowrie/{etc,var} && cd ~/docker/cowrie
sudo chown -R 999:999 etc var
nano compose.yaml
services:
cowrie:
image: cowrie/cowrie:latest
container_name: cowrie
ports:
- "22:2222"
- "23:2223"
volumes:
- ./etc:/cowrie/cowrie-git/etc
- ./var:/cowrie/cowrie-git/var
restart: unless-stopped
nano etc/cowrie.cfg
[honeypot]
hostname = homeserver
[shell]
filesystem = ${honeypot:share_path}/fs.pickle
[output_jsonlog]
enabled = true
logfile = ${honeypot:log_path}/cowrie.json
[ssh]
listen_endpoints = tcp:2222:interface=0.0.0.0
Pick a hostname that looks real. docker compose up -d. Bots log in (any password after a couple of tries, by default), run uname -a, cat /proc/cpuinfo, wget http://…/x86, try to install a miner — and every keystroke is in var/log/cowrie/cowrie.json, every downloaded binary in var/lib/cowrie/downloads/ (don't run those). cowrie.json is a gift to Loki (155): a dashboard of top usernames, passwords, countries and commands.
Treat Cowrie as a hostile container: no shared networks, mem_limit, read-only where possible, and rootless Docker (166) or a dedicated VM/Incus container (142) is the careful home for it.
Feed CrowdSec
sudo cscli collections install crowdsecurity/endlessh
sudo cscli collections install crowdsecurity/cowrie
and point the acquisition at the containers' logs (Docker log source, 107). Every IP that touches the honeypot is banned from everything else on the box — and reported to the community blocklist, so the ten thousand friends benefit too. That's the practical payoff beyond the entertainment.
What you'll see
Within a day: hundreds of connections, mostly from a few networks; root/admin/ubuntu/pi with the top hundred passwords (the raspberry one is raspberry); scripted sessions that check the CPU and download a miner for the right architecture; the occasional human who types ls and leaves. It's the clearest argument for keys-only SSH (3) and a VPN (33) you'll ever get, and a good thing to show someone who thinks "nobody would bother with my server".
Troubleshooting
Locked yourself out moving the port. Keep the session open while testing the new port (3, step 4); the console (37) otherwise.
Endlessh shows nothing. The router forward isn't reaching it — nmap -p 22 from outside (171) should show "open" with a slow banner.
Cowrie container keeps restarting. Ownership of etc/var (UID 999), or a typo in cowrie.cfg.
Honeypot IPs flood Fail2ban/CrowdSec alerts. That's the job; lower the notification level for those jails, keep the bans.
Wrapping up
Real SSH on the VPN or another port; Endlessh or Cowrie on public 22; CrowdSec reading their logs. Bots waste hours, you see exactly what hits a home server every day, and every attacker who knocks is banned from everything else before trying.
