Honeypots and Canary Tokens: Cheap Detection That Actually Works
Deploy low-interaction honeypots, canary tokens, and honey accounts to get high-confidence intrusion alerts with almost no false positives and minimal ongoing cost.
Most detection investment goes into things that generate noisy signal: SIEM rules over firewall logs, WAF alerts, anomaly detection on authentication events. All of that is worth having, but it shares a structural weakness — it has to distinguish malicious activity from an enormous background of legitimate traffic that looks similar. A honeypot or a canary token flips that problem around entirely: it is a resource with no legitimate reason to ever be touched, so any interaction with it is, almost by construction, a signal worth investigating. There is no baseline of normal use to separate from the anomaly, because there is no normal use.
This is why deception-based detection punches so far above its operational cost. A single canary token — a fake AWS key embedded in a config file nobody should be reading, a tracked document sitting in a file share, a DNS hostname that exists for no reason other than to be resolved — costs nothing to run and produces essentially zero false positives once it’s deployed correctly. A low-interaction honeypot listening on a handful of commonly-scanned ports gives you an early warning that a scanner or attacker has reached your internal network, well before they find anything real. None of this requires the elaborate, high-maintenance honeynet infrastructure that made “deception technology” sound like an enterprise-only investment a decade ago; a small self-hosted stack can deploy the useful 80% of this in an afternoon.
This piece covers the practical deployment options in rough order of effort: canary tokens first (minutes to deploy, near-zero maintenance), then low-interaction honeypots (a Docker container and some routing), then honey accounts and honey tables inside your own databases, and finally the part that actually determines whether any of this is worth doing — alert routing and false-positive tuning.
Canary Tokens: The Cheapest Detection You’ll Ever Deploy
A canary token is a uniquely identifiable fake credential, document, or URL that alerts you the instant it’s used or opened. The open-source Canarytokens project (and its hosted version) generates several flavors:
- AWS key canaries: a fake AWS access key/secret pair that alerts via a Lambda-backed webhook the moment anyone tries to use it against the AWS API, regardless of whether the call succeeds.
- DNS canaries: a unique subdomain that triggers an alert on the first DNS resolution — useful embedded in a config file, a script, or even a PDF that phones home via an image beacon.
- Web bug / document canaries: a Word document or PDF with an embedded tracking beacon that fires when the file is opened, even offline in some cases via a delayed callback.
- Cloned website canaries: alerts if someone clones and rehosts your login page (relevant for phishing-kit detection).
Deploying an AWS key canary manually, without the hosted service, using your own infrastructure:
# Create an IAM user with zero permissions attached — the key itself
# should never be able to do anything; the value is purely in detecting its use
aws iam create-user --user-name canary-do-not-use
aws iam create-access-key --user-name canary-do-not-use
# Store the resulting AccessKeyId/SecretAccessKey where an attacker
# who compromises a host or repo would plausibly find it
# CloudTrail + EventBridge rule: alert on ANY API call from that access key
aws events put-rule \
--name canary-key-used \
--event-pattern '{
"source": ["aws.iam"],
"detail": {
"userIdentity": {
"accessKeyId": ["AKIA_CANARY_KEY_ID_HERE"]
}
}
}'
Because the IAM user has zero attached policies, every single call using that key fails authorization — but CloudTrail still logs the attempt, and that attempt is the signal. Drop the key into a .env.bak file left in a web root, a config.php.old, a Slack message in a channel you’d expect an attacker to search, or a Jupyter notebook checked into a low-traffic internal repo. Anywhere a real secret might plausibly get left behind by accident is a good canary placement.
Low-Interaction Honeypots
A low-interaction honeypot emulates just enough of a service’s banner and initial handshake to look real to an automated scanner, without implementing enough of the protocol to be genuinely exploitable. Cowrie (SSH/Telnet) and OpenCanary (a broad multi-protocol honeypot: SMB, RDP, HTTP, FTP, MySQL, and more) are the two most widely deployed open-source options, and both run comfortably in a container on the same VPS hosting your real services, as long as they’re firewalled off from anything that actually matters.
# docker-compose.yml — OpenCanary on a spare internal IP, isolated network
services:
opencanary:
image: honeynet/opencanary:latest
container_name: honeypot-opencanary
restart: unless-stopped
networks:
- honeypot-net
ports:
- "127.0.0.1:2222:22" # fake SSH banner
- "127.0.0.1:2121:21" # fake FTP
- "127.0.0.1:3389:3389" # fake RDP
volumes:
- ./opencanary-config:/etc/opencanaryd
- ./opencanary-logs:/var/tmp/opencanary
networks:
honeypot-net:
driver: bridge
internal: true # no route to the internet or the real service network
The key operational point: this container has no reason to exist on your network except to be probed, so route real internal traffic away from it and route unsolicited scanner traffic toward it. A common pattern on a single-VPS setup is binding the honeypot to an otherwise-unused internal IP or port range, and making sure nothing legitimate ever has a reason to connect — any connection attempt is then either a misconfiguration on your own side (worth knowing about anyway) or genuinely hostile reconnaissance.
// opencanary-config/opencanary.conf (excerpt) — enable a few modules,
// point logs at a webhook so alerts don't just sit in a local file
{
"device.node_id": "vps-edge-01",
"ssh.enabled": true,
"ssh.port": 22,
"ftp.enabled": true,
"ftp.port": 21,
"logger": {
"class": "PyLogger",
"kwargs": {
"handlers": {
"webhook": {
"class": "opencanary.logger.WebhookHandler",
"url": "https://alerts.internal.furybee.org/webhook/honeypot"
}
}
}
}
}
Honey Accounts and Honey Tables
The same deception principle applies inside application and database layers, not just at the network edge. A honey account is a fake user or service account with a plausible name (svc-backup-legacy, admin-readonly) that has no legitimate reason to authenticate, ever. Any successful login attempt — and especially any failed login attempt using correct-looking credentials that were never actually issued to a real person — is worth an immediate alert, because it usually means someone found the account name in a leaked credential dump, a config file, or lateral movement inside your network and is trying it.
-- Honey table inside a real production database, sitting next to genuine tables
-- Named to look valuable to anyone who got unauthorized read access
CREATE TABLE customer_payment_backup_2024 (
id SERIAL PRIMARY KEY,
card_last4 TEXT,
cardholder_name TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Seed with obviously fake but plausible-looking rows
INSERT INTO customer_payment_backup_2024 (card_last4, cardholder_name)
VALUES ('4242', 'Canary Token Row - Do Not Use');
-- A trigger that fires an alert (via NOTIFY, or a logging extension)
-- on ANY SELECT against this table -- legitimate app code never queries it
CREATE OR REPLACE FUNCTION alert_honey_table_access() RETURNS trigger AS $$
BEGIN
PERFORM pg_notify('security_alerts',
json_build_object('table', TG_TABLE_NAME, 'at', now())::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Postgres doesn’t support triggers on SELECT directly, so in practice this is usually implemented via query logging (log_statement = 'all' scoped narrowly, or a proxy like pgaudit) filtered for that specific table name, with the filtered hits routed to an alert channel — the point stands regardless of implementation detail: a table that legitimate code never touches becomes a tripwire the moment something does touch it.
Comparing the Options
| Technique | Setup effort | Ongoing cost | Signal quality | Good first deployment? |
|---|---|---|---|---|
| Canary tokens (AWS key, DNS, doc) | Minutes | Near zero | Very high — near-zero false positives | Yes, start here |
| Low-interaction honeypot (OpenCanary/Cowrie) | Hours | Low — occasional log review, container upkeep | High for network scanning/recon | Yes, second step |
| Honey accounts | Minutes per account | Low | Very high | Yes |
| Honey tables / DB canaries | Moderate — depends on logging setup | Low to moderate | High, but needs query-level logging | Once basic logging exists |
| High-interaction honeynet (full OS emulation) | Days to weeks | High — needs isolation, monitoring, patching | Very detailed, but rarely worth it for small teams | Usually not, for this audience |
Alert Routing and False-Positive Tuning
The entire value proposition of deception-based detection collapses if the alerts go into a channel nobody watches, or if they’re noisy enough to get muted. Two practical rules keep this working:
- Route honeypot and canary alerts to a dedicated, high-priority channel — never bury them in general application logs. A hit here is rare and important enough to justify paging someone, unlike a generic WAF alert that might be one of thousands per day.
# Example: routing honeypot webhook alerts into an existing alerting stack
# (Alertmanager receiver, adjust to your actual notification channel)
receivers:
- name: honeypot-critical
slack_configs:
- channel: '#security-canary'
send_resolved: false
title: "Honeypot / canary triggered"
text: "{{ .CommonAnnotations.description }}"
- Audit false positives immediately, and fix the root cause instead of muting the alert. The most common false-positive source is a legitimate internal tool that scans your own network (a vulnerability scanner, an asset inventory tool, a monitoring agent doing port checks) hitting the honeypot by accident. When that happens, exclude that specific source IP explicitly and document why — don’t lower the alert’s priority, because doing so erodes the one property that makes this detection method valuable: near-total trust that a hit means something real.
Key Takeaways
- Deception-based detection (honeypots, canary tokens, honey accounts) has an unusually good signal-to-noise ratio because there is no legitimate baseline activity to distinguish from — any interaction is inherently suspicious.
- Canary tokens (fake AWS keys, DNS beacons, tracked documents) are the cheapest entry point: minutes to deploy, essentially zero ongoing cost, and they should be your first move if you have none of this today.
- Low-interaction honeypots (OpenCanary, Cowrie) give early warning of network scanning and reconnaissance without the maintenance burden of a full high-interaction honeynet, which is rarely worth it for small teams.
- Honey accounts and honey tables extend the same principle inside your application and database layers — a fake account or table with no legitimate use case becomes a tripwire the moment something touches it.
- Isolate honeypot infrastructure on its own network segment with no route to real services — it exists to be probed, and a compromised honeypot should never become a pivot point.
- Route these alerts to a dedicated, high-priority channel distinct from routine logging noise; muting a rare, high-confidence alert defeats the entire purpose of deploying it.
- When a false positive does occur, fix the root cause (exclude the specific legitimate source) rather than lowering the alert’s priority — the value of this detection method depends entirely on it staying trustworthy.