DEV Community

Ayi NEDJIMI
Ayi NEDJIMI

Posted on

How to Build a Honeypot Logger with Go and Notify via Telegram

Attackers constantly probe infrastructure you never intended to expose — admin panels, .env files, forgotten login pages. A honeypot logger captures those attempts silently and fires an alert the moment something interesting hits. This article walks through building a minimal but production-useful honeypot in Go that sends Telegram notifications in real time, giving you immediate visibility into who is scanning your network.

What We're Building

The setup is intentionally simple:

  • A Go HTTP server listening on a configurable port (80, 8080, or whatever you expose)
  • Every incoming request is logged with: IP, timestamp, method, path, user-agent, and optional body
  • High-value hits (e.g., /.env, /admin, /wp-login.php) immediately trigger a Telegram message
  • All logs are written as newline-delimited JSON for easy parsing

This is not a replacement for a full deception stack like Canarytokens. It is a lightweight trip wire you can deploy in under 10 minutes on any VPS.

Setting Up the Telegram Bot

Before writing any Go, you need a bot token and a chat ID:

  1. Message @BotFather on Telegram and send /newbot
  2. Copy the token (format: 123456789:AAF...)
  3. Start a conversation with your bot, then call https://api.telegram.org/bot<TOKEN>/getUpdates in a browser
  4. Find your chat_id in the JSON response

Store both values as environment variables. Never hardcode them.

Building the Honeypot Server

Initialize the module and create main.go:

mkdir honeypot && cd honeypot
go mod init honeypot
Enter fullscreen mode Exit fullscreen mode

Here is the complete server code:

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
    "strings"
    "time"
)

var sensitivePaths = []string{
    "/.env", "/admin", "/wp-login", "/wp-admin",
    "/config", "/.git", "/phpmyadmin", "/xmlrpc",
    "/login", "/actuator", "/.aws", "/setup",
}

type HitLog struct {
    Timestamp  string            `json:"ts"`
    RemoteAddr string            `json:"ip"`
    Method     string            `json:"method"`
    Path       string            `json:"path"`
    UserAgent  string            `json:"ua"`
    Headers    map[string]string `json:"headers"`
    Body       string            `json:"body,omitempty"`
    Alert      bool              `json:"alert"`
}

var logFile *os.File

func isSensitive(path string) bool {
    lower := strings.ToLower(path)
    for _, prefix := range sensitivePaths {
        if strings.HasPrefix(lower, prefix) {
            return true
        }
    }
    return false
}

func honeypotHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(io.LimitReader(r.Body, 4096))
    defer r.Body.Close()

    headers := make(map[string]string)
    for k, v := range r.Header {
        headers[k] = strings.Join(v, ", ")
    }

    hit := HitLog{
        Timestamp:  time.Now().UTC().Format(time.RFC3339),
        RemoteAddr: r.RemoteAddr,
        Method:     r.Method,
        Path:       r.URL.RequestURI(),
        UserAgent:  r.UserAgent(),
        Headers:    headers,
        Body:       string(body),
        Alert:      isSensitive(r.URL.Path),
    }

    line, _ := json.Marshal(hit)
    fmt.Fprintln(logFile, string(line))

    if hit.Alert {
        msg := fmt.Sprintf(
            "Honeypot hit\nIP: %s\nPath: %s\nMethod: %s\nUA: %s\nTime: %s",
            hit.RemoteAddr, hit.Path, hit.Method, hit.UserAgent, hit.Timestamp,
        )
        go sendTelegramAlert(msg)
    }

    // Return 200 to avoid triggering retry logic on the scanner side
    w.WriteHeader(http.StatusOK)
    fmt.Fprintln(w, "OK")
}

func main() {
    var err error
    logFile, err = os.OpenFile("honeypot.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
    if err != nil {
        log.Fatal(err)
    }
    defer logFile.Close()

    http.HandleFunc("/", honeypotHandler)
    addr := ":8080"
    log.Printf("Honeypot listening on %s", addr)
    log.Fatal(http.ListenAndServe(addr, nil))
}
Enter fullscreen mode Exit fullscreen mode

A few deliberate choices here:

  • Return 200 to everything. This keeps automated scanners from marking the host as down and stopping their scan. You want them to keep probing.
  • 4096-byte body limit. Enough to capture credential dumps and config file probes, not enough to crash under a payload flood.
  • Goroutine for Telegram. The alert call must not block the HTTP response. If the Telegram API is slow, the request still completes and the log entry is written.

Sending Telegram Alerts

Create telegram.go alongside main.go:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
)

func sendTelegramAlert(message string) {
    token := os.Getenv("TELEGRAM_TOKEN")
    chatID := os.Getenv("TELEGRAM_CHAT_ID")
    if token == "" || chatID == "" {
        return
    }

    payload, _ := json.Marshal(map[string]string{
        "chat_id": chatID,
        "text":    message,
    })

    url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", token)
    resp, err := http.Post(url, "application/json", bytes.NewReader(payload))
    if err != nil || resp.StatusCode != 200 {
        // Silent fail — Telegram outages must not affect the honeypot
        return
    }
    defer resp.Body.Close()
}
Enter fullscreen mode Exit fullscreen mode

Run the whole thing with:

TELEGRAM_TOKEN=123456789:AAF... TELEGRAM_CHAT_ID=987654321 go run .
Enter fullscreen mode Exit fullscreen mode

Deploying to a VPS

Build a static binary and deploy it with a minimal systemd unit:

[Unit]
Description=Go Honeypot Logger

[Service]
ExecStart=/usr/local/bin/honeypot
EnvironmentFile=/etc/honeypot/env
WorkingDirectory=/var/log/honeypot
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

Store TELEGRAM_TOKEN and TELEGRAM_CHAT_ID in /etc/honeypot/env with permissions 0600. To bind port 80 without running as root:

sudo setcap cap_net_bind_service=+ep /usr/local/bin/honeypot
Enter fullscreen mode Exit fullscreen mode

For log rotation, logrotate handles the newline-delimited JSON cleanly. You can also ingest logs into SQLite with a small Python script that parses fields into proper columns — this makes querying by IP or path significantly faster than grepping raw JSON.

Adding Rate Limiting on Alerts

In production, a single scanner can generate hundreds of hits per minute across different paths. A simple in-memory rate limiter prevents Telegram spam:

import "sync"

var (
    alertCounts = make(map[string]int)
    alertMu     sync.Mutex
)

func shouldAlert(ip string) bool {
    alertMu.Lock()
    defer alertMu.Unlock()
    alertCounts[ip]++
    return alertCounts[ip] <= 3
}
Enter fullscreen mode Exit fullscreen mode

Call shouldAlert(hit.RemoteAddr) before sendTelegramAlert and reset the map hourly with a background goroutine. This keeps notifications actionable rather than overwhelming.

What You'll Actually See

After deploying on a fresh VPS with a public IP and no DNS record, traffic starts within minutes:

  • Port scanners hit every path in the first hour
  • Automated bots probe /.env, /config/database.yml, /wp-login.php within a few hours
  • Credential stuffing attempts surface within 24 hours if port 22 is exposed

The Telegram alerts create a real-time picture of active campaigns. Cross-reference the top 20 most-probed paths against your actual services. If attackers are probing paths that exist in your production stack, that is worth acting on immediately. For concrete steps to harden those services, the security hardening checklists we maintain cover Linux servers, Docker hosts, and Go APIs with specific configuration changes.

The Takeaway

A Go honeypot logger is under 150 lines of code, has zero external dependencies, and deploys in minutes. It gives you real-time visibility into what is actively being probed in your environment. The JSON log format makes it straightforward to feed into a SIEM, blocklist generation, or WAF rule updates.

Start with one VPS, tune the sensitive path list based on what you observe, and add rate limiting once the volume picks up. The intelligence you collect from a honeypot is often more actionable than a vulnerability scan — because it reflects what attackers are doing right now, not what they could theoretically do.


I run AYI NEDJIMI Consultants, a cybersecurity consulting firm. We publish free security hardening checklists — PDF and Excel.

Top comments (0)