Self-hosted honeypot on a dedicated VPS
A complete first-run path: register the sensor, lock down admin SSH, and publish the decoy on the ports attackers actually scan.
On this page
- What you are building
- 1. Register the honeypot
- 2. Copy the token once
- 3. Prepare the VPS
- 4. Install an SSH key
- 5. Open the provider firewall
- 6. Install Docker
- 7. Move admin SSH to 2222
- 8. Start the container
- 9. Verify
- First-run lessons
What you are building
A dedicated public VPS should look like a real target. The honeypot therefore owns the classic service ports. Your real login moves aside.
- 22 / 23 / 80 — decoy SSH, Telnet and HTTP (mapped to the container).
- 2222 — the only real SSH into the box. Bookmark it.
- Attackers never get a shell or a cookie. Captured credentials go to jarnis.io over HTTPS.
Image: ghcr.io/j-a-r-n-i-s/honeypot:latest (linux/amd64). The only required environment variable is HONEYPOT_TOKEN.
1. Register the honeypot
In the app open Honeypots, add a sensor, give it a name (for example Hostinger Honeypot) and optional notes. Leave the public IP empty — the container reports it on first check-in, including when the WAN address later changes.
2. Copy the token once
The secret looks like hpt_ plus hex. The configuration panel only shows the prefix afterwards.
3. Prepare the VPS
A small KVM is enough: 1 vCPU, 2–4 GiB RAM, 20 GB disk, public IPv4, Ubuntu 24.04 or 26.04 amd64. Confirm you can reach the provider’s browser console (VNC) before you touch SSH — that is the recovery path if a socket restart goes wrong.
4. Install an SSH key
Log in once with the panel password and install your public key. After that, prefer key login from every machine that administers the box. Do not copy one private key between laptops.
mkdir -p /root/.ssh && chmod 700 /root/.ssh echo 'ssh-ed25519 AAAA… you@admin-workstation' >> /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys
5. Open the provider firewall
Hostinger and similar panels often filter inbound traffic in front of the VM. Opening ufw on the guest is not enough. Allow TCP 22, 23, 80 and 2222 in the panel before you move SSH. Probe the new ports from another host before you drop port 22.
nc -zv YOUR_VPS_IP 22 nc -zv YOUR_VPS_IP 2222 # only after something is listening
6. Install Docker
On current Ubuntu the distro packages are enough. Do this while SSH is still on port 22.
apt-get update apt-get install -y docker.io docker-compose-v2 ca-certificates curl systemctl enable --now docker docker --version
7. Move admin SSH to 2222
Ubuntu 24.04 and 26.04 enable ssh.socket. Changing Port in sshd_config alone does nothing — systemd owns port 22. Add 2222 first, keep 22, confirm a new login, then drop 22.
ss -tlnp | grep 2222 must be empty.mkdir -p /etc/systemd/system/ssh.socket.d cat > /etc/systemd/system/ssh.socket.d/listen.conf << 'EOF' [Socket] ListenStream= ListenStream=0.0.0.0:22 ListenStream=[::]:22 ListenStream=0.0.0.0:2222 ListenStream=[::]:2222 BindIPv6Only=ipv6-only EOF ss -tlnp | grep 2222 # must print nothing systemctl daemon-reload systemctl restart ssh.socket ss -tlnp | grep ssh
From a second terminal, log in on 2222 before you continue. Only then switch the socket to 2222 only:
cat > /etc/systemd/system/ssh.socket.d/listen.conf << 'EOF' [Socket] ListenStream= ListenStream=0.0.0.0:2222 ListenStream=[::]:2222 BindIPv6Only=ipv6-only EOF systemctl daemon-reload systemctl restart ssh.socket
Update your SSH config on the workstation: Port 2222. Keep the provider console open until that login works.
8. Start the container
Put the token in a root-only env file. Map host 80 to container 8080 — the process inside listens on 8080 by default.
HONEYPOT_TOKEN=hpt_paste_the_full_secret
chmod 600 /root/jarnis-honeypot.env docker pull ghcr.io/j-a-r-n-i-s/honeypot:latest docker run -d --name jarnis-honeypot --restart unless-stopped --memory 128m \ --env-file /root/jarnis-honeypot.env \ -p 22:22 -p 23:23 -p 80:8080 \ ghcr.io/j-a-r-n-i-s/honeypot:latest docker logs -f --tail 50 jarnis-honeypot # Auto-update twice daily (pull :latest, recreate only if the image changed) curl -fsSL https://jarnis.io/guides/jarnis-honeypot-update.sh -o /usr/local/sbin/jarnis-honeypot-update chmod 755 /usr/local/sbin/jarnis-honeypot-update printf '%s\n' '20 4,16 * * * root /usr/local/sbin/jarnis-honeypot-update >> /var/log/jarnis-honeypot-update.log 2>&1' > /etc/cron.d/jarnis-honeypot-update
In the app, set host ports to 22 / 23 / 80 so the Installation tab matches the dedicated-VPS mapping. Container ports stay 22 / 23 / 8080.
9. Verify
ssh -p 2222 root@YOUR_VPS_IPstill opens a real shell.ssh user@YOUR_VPS_IP(port 22) is denied. That is the decoy.curl -sI http://YOUR_VPS_IP/returns the honeypot login page, not a directory listing.- Within a minute the honeypot leaves pending, the public IP appears, and a test login shows up under Events.
First-run lessons (do not skip)
- The token cannot be read back from the database. Rotate if you lost it; the old container will stop authenticating.
- Ubuntu’s socket-activated SSH is the #1 lockout. Dual-listen, confirm, then drop 22. Never combine a port probe on 2222 with
systemctl restart ssh.socket. - If you do lock yourself out: reboot from the provider panel (not reinstall). If the 2222 drop-in is valid and the port is free, SSH comes back. Otherwise use the browser VNC console and remove
/etc/systemd/system/ssh.socket.d/listen.conf, thensystemctl daemon-reload && systemctl start ssh.socket. - Publish 80, not only 8080. Scanners hit 80. The container still binds 8080 inside.
- Leave the endpoint empty for a VPS with a changing WAN address. JARNIS learns the reporter IP on the config poll.
- 128 MiB is enough. Do not give the decoy the whole VM.
- The GHCR image is public for amd64. You do not log in to pull
:latest. - If logs show
config fetch failed/i/o timeoutto jarnis.io:443, the decoy is up but cannot check in. Outbound HTTPS to other sites can still work. Allow the VPS IP on the office / ISP firewall in front of jarnis.io, or put the API behind a public anycast (Cloudflare). Until then the sensor stays pending.