I finally got around to putting a honeypot on the public side of my home connection. I wasn’t trying to catch APTs. I wanted to see what hits a random residential IP when nothing is hiding it.
This is a notes post about standing it up, how it’s contained, and what actually showed up in the logs after a month.
Why bother
Most threat intelligence I read describes the internet as a battlefield. Every unpatched device is five minutes from compromise. Every IP gets 30,000 probes a day. The numbers are usually correct. They aren’t useful unless you can map them to what your environment looks like.
I wanted my own baseline. Not a vendor’s feed, not an aggregated report. What does my ISP-assigned IP attract, right now, and what does the traffic look like when you strip out the marketing spin.
Secondary reason: I wanted to segment the network, and a honeypot is a good forcing function. My homelab had been flat for too long. Nothing makes you VLAN a network faster than hanging something deliberately exposed off it.
Threat model and ground rules
Before anything went online, I wrote down what I was willing to tolerate and what I wasn’t.
Willing to accept:
- My IP appearing on scanning blocklists.
- My ISP sending me a polite note. (They never did.)
- Getting buried in logs I’d then have to process.
Not willing to accept:
- Anything the honeypot attracts touching my real LAN.
- A honeypot compromise turning into a pivot into anything else I own.
- Outbound traffic that looks like I’m participating in someone else’s botnet.
Those three constraints drove every architectural decision that followed.
Architecture
The honeypot runs as a single VM on a secondary host, isolated on its own VLAN behind OPNsense. It has one purpose, no shared storage, no shared credentials, and nothing legitimate behind it. If it gets popped, I wipe the VM from snapshot and start again.
The physical picture:
- Honey-VM: 4 vCPU, 8 GB RAM, 60 GB disk. Ubuntu 22.04 base, T-Pot on top.
- VLAN 66: dedicated “DMZ-lite”. No inter-VLAN access. DHCP scoped tight.
- OPNsense: port-forwards a curated set of TCP ports from WAN to the honey-VM.
- Suricata on the OPNsense WAN interface logs everything hitting those ports, independent of what the honeypot itself sees.
- Outbound rules on VLAN 66: no outbound except to a specific syslog collector and to Cloudflare DoH for DNS. No SSH out, no SMB out, no SMTP out, no arbitrary outbound anything.
The last point is the one that matters most. A honeypot with open outbound is a honeypot that can participate in the abuse you’re trying to study. If Cowrie accepts a shell and the intruder tries to curl a second-stage payload, they should fail at the network, not at the endpoint.
Ports exposed to the internet: 22, 23, 80, 443, 445, 1433, 2222, 3306, 3389, 5060, 5900, 8080. Nothing else.
Stack
I used T-Pot for the heavy lifting. It’s maintained, it aggregates sensible honeypots, and its dashboards are ready out of the box. The components that mattered for me:
- Cowrie on 22 and 2222. SSH and Telnet. Logs full session transcripts.
- Dionaea on 445, 1433, 3306, 5060. Protocol emulation for SMB, MSSQL, MySQL, SIP.
- Heralding on anything else that smells like a login prompt.
- Honeytrap as the generic catch-all TCP listener.
- Snare / Tanner serving HTTP decoy content on 80 and 8080.
- Elasticsearch + Kibana for the dashboards, with data shipped out to my own Loki instance as a backup.
T-Pot’s internal firewalling is fine, but I don’t rely on it. The OPNsense rules are the real enforcement. If T-Pot broke tomorrow, nothing on VLAN 66 would suddenly start talking to my real network.
Standing it up
Provisioning was uneventful once the segmentation was in place.
# T-Pot install on a fresh Ubuntu 22.04
env bash -c "$(curl -sL https://ghst.ly/tpot-install)"
The installer handles Docker, pulls the containers, and wires up the reverse proxy for the Kibana side. The one thing I changed was restricting the admin web interface to the management VLAN, reachable only over WireGuard.
SSHD for actual host management got moved to a non-standard port on the management interface. Cowrie owns 22 on the WAN side.
Before opening the firewall, I ran a full scan from an external VPS against the advertised ports to make sure only the intended services responded, and that the banners looked realistic enough to not scream “honeypot” on the first handshake. A couple of Cowrie defaults were too obvious (the default SSH version string, for one) and needed tweaking. Anyone running OpenCanary fingerprints or Shodan’s honeypot detector will still figure it out. Most bots won’t.
First 24 hours
I expected a slow ramp while DNS caches and scanners noticed the IP. That wasn’t the experience.
Within 12 minutes of opening port 22, the first SSH login attempt came in. Inside the first hour: 340 SSH attempts from 47 unique source IPs. By the end of the first day: 8,200 SSH attempts, 1,100 HTTP requests, and around 60 SMB connections.
There is no onboarding period for a public IP. You’re in the database already. Opening a port just tells the scanners that something is finally listening.
What 30 days actually looked like
Rough totals at the 30-day mark (Suricata plus T-Pot aggregated):
- SSH and Telnet attempts: 412,000 across 22 and 2222, from 14,300 unique source IPs.
- HTTP requests to honeypot web roots: 28,400. Mostly scanner fingerprints and path probes.
- SMB connections: 3,900.
- MSSQL login attempts: 1,100.
- RDP connections: 9,700, almost all from a handful of subnets running NLA probes.
- SIP INVITE floods: two distinct campaigns, one targeting Asterisk defaults, one targeting a specific FreePBX module.
Geography is the least interesting dimension and the one vendors love to lead with. Source IPs spread across 90+ countries. That maps to compromised hosts, not operator location. Treating a GeoIP heatmap as a map of threat actors is a mistake.
The credential side is more useful. Top ten SSH username/password combinations over the 30 days, in order:
root/rootadmin/adminroot/123456root/passwordadmin/passworduser/userubnt/ubntpi/raspberryroot/1234support/support
None of those will surprise anyone who’s spent time on this. They confirm what the big honeypot operators have been saying for years: the low end of the attack surface is stuck on the same dictionary it’s been stuck on for a decade, because it keeps working.
What the payloads looked like
Cowrie logs full session transcripts, which is the part worth reading. Patterns I saw repeatedly:
uname -a; cat /proc/cpuinfo; free -mas environment fingerprinting before any payload drop. The bot wants to know whether it landed on an ARM router, a MIPS camera, or an x86 box, so it can pull the right binary.- A
wgetorcurlchain pointing at a staging server, usually on port 80 over a direct IP with no DNS. Almost always a short-lived URL, dead within days. - A
chmod 777on the downloaded binary, followed by./<binary>and a quickrmto clean up. - Busybox-style commands with shell tricks to survive minimal environments.
Three payloads I captured and detonated in an isolated environment later:
- A Mirai variant targeting MIPS and ARM, with the usual hardcoded C2 list.
- A Monero miner compiled for x86_64 with an embedded pool address and worker ID.
- An XorDDoS dropper with the “encrypted” strings still trivially XOR-decodable against a one-byte key.
Nothing novel. That’s the point. The mass of the internet’s attack noise is bots spraying five-year-old payloads at anything that looks like a vulnerable edge device. A residential IP running no listening services would never see this traffic because the TCP connections would simply RST. Exposing ports makes you legible to the layer that scans for this kind of target.
The quieter, more interesting traffic
Once you filter out the SSH brute force floor, and it is a floor of roughly 300 to 600 attempts per hour, the rest gets more varied.
Log4Shell probes still show up on HTTP. More than two years after the advisory, JNDI probes are a standing wave. Most point at self-hosted Burp collaborators or long-dead VPS callbacks. Somebody, somewhere, is still paying for a scanner that fires these shots and never checks whether anyone answered.
A handful of requests were clearly scripted against specific CVEs:
- GPON router authentication bypass (CVE-2018-10561 / 10562). Still hitting in 2026.
- Various Ivanti and Fortinet path traversals.
- Confluence OGNL injection strings.
- Generic WordPress
xmlrpc.phppingback fishing.
The unusual one: a small cluster of requests that tried to negotiate TLS with an SNI matching a real banking domain. No credentials, no follow-up. Probably a scanner doing inventory for cert transparency lookups. Possibly something less innocent. I don’t have enough data to tell, and that’s the honest answer.
Operational reality
A honeypot is not a set-and-forget box. In the first week I burned an evening chasing false positives and another fixing log rotation before a partition filled. The real costs:
- Disk grows fast. Cowrie session logs plus Elasticsearch indices ate about 18 GB in 30 days. That’s cheap, but it isn’t free.
- Containers drift. Watchtower handles the weekly pull. I still review breaking changes in the T-Pot release notes before merging.
- Elasticsearch is memory-hungry and will swap itself into uselessness if you underprovision. 8 GB is the practical floor.
- Alerting is where this gets useful. A 500-per-hour SSH baseline is noise. A successful Cowrie shell that persists for more than 30 seconds, or any outbound hit blocked at the OPNsense rule, is signal. Those page me. Everything else goes to a dashboard I check when I feel like it.
The alerting config is where most of my ongoing time goes. Without it, the whole thing is a pretty dashboard.
What I’d change
A few things I’ll do on the next iteration:
Split the honeypot across two IPs. Run Cowrie on one, everything else on the other. The SSH noise crowds the indices and makes queries slower than they need to be.
Move the dashboards off the honey-VM entirely. Shipping to an external Loki instance is already half the work. The remaining Kibana stack on-box is there for convenience, not necessity.
Add a second outbound-blocking layer inside the VM itself. Defence in depth against a container escape I’m still not fully satisfied with.
Log rolling PCAPs on a 7-day window. Right now I only have what Suricata and the honeypots chose to log. Full packet captures would let me revisit sessions I under-investigated at the time.
Does this change what I do at work
Partially. It doesn’t change how I think about APT-level adversaries. Nothing I saw in 30 days would strain a reasonable environment.
What it changes is how I talk about the baseline. The background radiation of the internet is real, it’s measurable, and it doesn’t stop. Any machine with an unpatched edge service survives hours, not days. Any default credential on a public interface is already compromised. You just haven’t noticed yet.
That isn’t a marketing line. That’s 412,000 login attempts across 30 days on one residential IP running an obvious honeypot.
Closing
Segment first. Then break something on purpose, in a place where it can’t reach anything you care about. The logs that come back are more honest than any vendor report.