A honeypot is not a thing you install. It’s a thing you run, in the verb sense, the way you run an experiment or a route. The box you stand up on Friday is a first guess about what will draw interesting traffic and how it ought to answer. The traffic that shows up over the weekend is the correction. Deploy-and-forget throws that correction away, which is why so many honeypots go quiet and their operators conclude there was nothing to catch.
The stock reality
The default lifecycle is one step long: stand it up, confirm it logs, walk away. The config from day one is still the config on day ninety. Meanwhile the traffic keeps trying to tell you things. Which commands people wanted and you didn’t answer. Which bait they ignored. Which persona detail they leaned on until it gave. None of it goes anywhere, because nobody’s reading it back into the box. So the honeypot asks the same questions forever and slowly stops getting interesting answers.
The lever
Let every catch set your next change. Read yesterday’s sessions, find the one gap that mattered, close it, redeploy, and do it again. The box gets more convincing and better-baited in the exact directions your real traffic is pushing, instead of the directions you guessed at on day one.
Do it
The loop has three moves, and you already have every tool you need from the earlier Groundworks.
1. Find the misses. The commands people ran that your box couldn’t answer are a to-do list your visitors wrote for you. Pull the most-run commands, the same query from the triage Groundwork:
jq -r 'select(.event.Command != "") | .event.Command' beelzebub.log \
| sort | uniq -c | sort -rn | head -30Set that list against your handler regexes. Anything frequent that you don’t answer, or answer with a canned miss, is your next iteration. (If your catch-all returns a fixed string, you can isolate the misses directly by filtering .event.CommandOutput for it.) Say the top keeps surfacing cat ~/.aws/credentials, docker ps, and history, none of which stg-01 currently handles. Those three just chose your weekend’s work.
2. Move the bait to where they actually look. You planted a canary in .env earlier in the series. If the logs show people reaching it, good. If nobody ever runs cat .env but they keep trying cat ~/.aws/credentials, your bait is in the wrong drawer. Put it where the traffic goes, and while you’re in there, answer the docker ps they kept running, consistent with the story:
commands:
- regex: "^cat ~/.aws/credentials$"
handler: |
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
- regex: "^docker ps$"
handler: "CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES\n3f9a1c2b4d5e postgres:15 \"docker-entrypoint.s…\" 3 weeks ago Up 3 weeks 127.0.0.1:5432->5432/tcp ingest-db"Mint a fresh canary for the new location so its memo names the right drawer, and notice the docker ps output pays off the DATABASE_URL you planted earlier: the postgres container is the database that connection string points at. The story keeps referencing itself.
3. Redeploy and check the same numbers. Ship it, and next week run the same counts. Are the misses down? Did the relocated bait get taken? Are sessions running longer before people give up? You’re not guessing whether the change helped. The jq you already have answers it.
The thinking
Deploy-and-forget rests on a hidden assumption: that on day one you already knew what would draw the traffic worth having, and how a believable box should answer it. You didn’t. Nobody does. The loop is how you find out, and it’s the only part of this whole series that compounds. A better persona helps once. A better lure helps once. The habit of turning every catch into the next change helps every week you keep it up.
There’s a decay argument sitting next to it. The internet doesn’t hold still. New CVEs change what scanners probe for, new tooling changes how they behave, and the model-driven traffic that barely existed a while ago is now its own category. A box tuned to last year’s traffic is quietly going deaf. So the loop isn’t only how the honeypot gets better. It’s how it stays current enough to catch anything at all.
The question to carry out of here isn’t “is my honeypot good.” It’s “when did I last change it, and what told me to.” If the answers are “I haven’t” and “nothing,” the box isn’t running. It’s just sitting there.
What changed for us
Almost nothing we run well was designed up front. The lures that catch the interesting traffic came from watching what people reached for and walking the bait toward it, one revision at a time. An over-exposed canary credential was not a day-one idea. It was the answer to a pattern we kept seeing: people hunting for exactly that kind of key and finding nothing worth taking. We gave them something worth taking, and the canary did the rest.
It’s worth being plain about what our setup actually is, because it’s tempting to imagine a secret. It’s the same loop you can run this weekend, wound tighter: more sensors feeding it, faster turnaround between a catch and the change it prompts, more instrumentation on the “what did I catch” step. The engine is identical to yours. The only difference is how many times we’ve turned it.
Try this next
You’ve now got the whole practice. A box with a reason to exist, bait worth taking, the triage to read what you caught, the tells filed off, and the loop that turns all of it into next week’s version. There’s one frontier left worth a look, and it’s the part moving fastest. Sometimes the thing knocking isn’t a person or a plain scanner but an autonomous, model-driven client, and it probes, takes, and fails in ways of its own. Last Groundwork, we point the same loop at that.