Beelzebub Honeypot on Ubuntu 24.04 on Azure User Guide
Overview
Beelzebub is a self hosted, open source honeypot and deception framework. It stands up low interaction decoy services that impersonate real infrastructure, so that any connection to them is suspicious by definition. Every interaction is captured as a structured event and exposed as Prometheus metrics, giving you an early warning tripwire for reconnaissance and intrusion attempts. Because nothing legitimate should ever touch a decoy, a single hit is a high signal alert rather than noise you have to sift out of production logs.
The cloudimg image installs the Beelzebub binary at /usr/local/bin/beelzebub, running as the beelzebub service and driven by YAML under /etc/beelzebub/. Out of the box it runs three decoy services, and its Prometheus metrics endpoint is bound to loopback 127.0.0.1:2112 and served through nginx on port 80. nginx is ready for your TLS certificate.
Two surfaces, two deliberately different postures. A honeypot's decoys are meant to be inviting bait, so this image does not lock them down; they run inside Beelzebub's own sandbox and grant no real access to the host. What the image does secure is the management surface. The Prometheus metrics endpoint is bound to loopback and served through nginx: GET /health is an unauthenticated liveness endpoint for load balancer probes, while /metrics requires HTTP Basic Auth (username cloudimg). A beelzebub-firstboot.service oneshot generates a unique password on each VM's first boot, writes it to a root only file and to an nginx htpasswd, resolves the instance URL and writes a root only info note, then disables itself. No two VMs share a password and none is baked into the image.
Real SSH stays out of the way. Your administrative SSH login is the normal Ubuntu sshd on port 22 (azureuser). The SSH decoy runs on a separate port (2222), so an attacker poking the decoy never touches your real management access.
The decoy services shipped by default:
| Decoy | Port | Pretends to be |
|---|---|---|
| SSH decoy | 2222 | An OpenSSH server with a fake shell |
| HTTP decoy | 8080 | An Apache web server |
| MySQL decoy | 3306 | A MySQL 5.7 database |
Note on the interface: Beelzebub is a headless daemon. It has no web UI; you configure it through YAML (/etc/beelzebub/), you observe it through its Prometheus metrics, and you extend it by adding decoy service definitions. This guide uses curl against the metrics endpoint and the decoys, and edits the YAML directly.
What is included:
- Beelzebub (verified at 3.8.0) built as a single self contained binary at
/usr/local/bin/beelzebub beelzebub.servicerunning three decoy services (SSH 2222, HTTP 8080, MySQL 3306) with a loopback Prometheus metrics endpoint on127.0.0.1:2112- nginx reverse proxy on port 80: public
/health, password protected/metrics beelzebub-firstboot.servicefor the first boot metrics password and info note- A unique per VM metrics password generated on first boot, in a root only
0600file - Ubuntu 24.04 LTS base, fully patched
- 24/7 cloudimg support, 24h response SLA
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet with a subnet. Beelzebub is lightweight. Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM) is sufficient for typical deployments. Decide which decoys you want to expose to the networks you are monitoring, and open those ports on the network security group accordingly.
Step 1: Deploy from the Azure Portal
Search the Marketplace for Beelzebub Honeypot on Ubuntu 24.04, choose your VM size, and attach an NSG. Allow TCP 22 (your real SSH) from your management network only, and TCP 80 (the metrics endpoint) from the hosts that scrape it. Then open the decoy ports (2222, 8080, 3306) to the network segment you want to monitor: exposing a decoy to a subnet is what lets it catch anything scanning or probing that subnet. Front port 80 with TLS in production (see the HTTPS section below).
Step 2: Deploy from the Azure CLI
RG="beelzebub-prod"; LOCATION="eastus"; VM_NAME="beelzebub-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/beelzebub-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name beelzebub-vnet --address-prefix 10.90.0.0/16 --subnet-name beelzebub-subnet --subnet-prefix 10.90.1.0/24
az network nsg create -g "$RG" --name beelzebub-nsg
az network nsg rule create -g "$RG" --nsg-name beelzebub-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name beelzebub-nsg --name allow-metrics --priority 110 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 443 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name beelzebub-nsg --name allow-decoys --priority 120 \
--source-address-prefixes "<monitored-subnet-cidr>" --destination-port-ranges 2222 8080 3306 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name beelzebub-vnet --subnet beelzebub-subnet --nsg beelzebub-nsg --public-ip-sku Standard
Step 3: Connect via SSH
Your real administrative SSH is the standard Ubuntu sshd on port 22. This is separate from the SSH decoy on port 2222.
ssh azureuser@<vm-ip>
Step 4: Verify the services are running
Beelzebub's Prometheus metrics endpoint listens on loopback 127.0.0.1:2112; nginx fronts it on port 80. The three decoy services listen on their own ports (2222, 8080, 3306). The health endpoint is public; the metrics endpoint requires your password.
sudo systemctl start beelzebub nginx 2>/dev/null || true
sudo systemctl is-active beelzebub nginx
ss -tln | grep -E ':80 |127.0.0.1:2112|:2222|:8080|:3306'
curl -s http://127.0.0.1/health
You should see both services active; the Prometheus metrics socket bound to loopback 127.0.0.1:2112; nginx on port 80; the three decoy ports listening; and ok from the public health endpoint. Note that 2112 is bound to 127.0.0.1 only, so the metrics are never reachable except through the authenticated nginx proxy.

Step 5: Read your per VM metrics password
The password was generated on this VM's first boot and stored in a root only file. Read it and keep it secret: it authenticates every request to the metrics endpoint. Confirm that unauthenticated requests are rejected.
sudo cat /root/beelzebub-info.txt
echo "unauthenticated /metrics -> HTTP $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/metrics)"
The info note holds METRICS_PASSWORD, the instance URLs and the decoy endpoints; it is owned root:root with mode 0600. An unauthenticated call to /metrics returns 401 because nginx enforces authentication.

Step 6: Watch the honeypot catch a probe
This is what Beelzebub is for. Load your password into a shell variable, read the current event count from the authenticated metrics endpoint, then send a few requests to the HTTP decoy exactly as a scanner probing for a login page would. Read the metrics again and watch the counter climb.
export BZ_PW=$(sudo grep '^METRICS_PASSWORD=' /root/beelzebub-info.txt | cut -d= -f2-)
echo "authenticated /metrics -> HTTP $(curl -s -o /dev/null -w '%{http_code}' -u cloudimg:$BZ_PW http://127.0.0.1/metrics)"
echo "before: $(curl -s -u cloudimg:$BZ_PW http://127.0.0.1/metrics | grep '^beelzebub_http_events_total ')"
for i in 1 2 3; do curl -s -o /dev/null http://127.0.0.1:8080/wp-login.php; curl -s -o /dev/null http://127.0.0.1:8080/; done
echo "after: $(curl -s -u cloudimg:$BZ_PW http://127.0.0.1/metrics | grep '^beelzebub_http_events_total ')"
curl -s -u cloudimg:$BZ_PW http://127.0.0.1/metrics | grep -E '^beelzebub_.*_total '
The authenticated /metrics call returns 200, and beelzebub_http_events_total increases with every hit on the HTTP decoy. Each protocol has its own counter (beelzebub_ssh_events_total, beelzebub_http_events_total, beelzebub_tcp_events_total and more), and beelzebub_events_total is the grand total. Point Prometheus at this endpoint and you can alert the moment any of these counters moves.

Step 7: Interact with the decoys
The decoys are ordinary network services as far as an attacker can tell. You can poke them yourself to see what an intruder would see. The SSH decoy presents an OpenSSH banner and a fake shell; the HTTP decoy serves a fake Apache page; the MySQL decoy answers on the database port.
curl -s http://127.0.0.1:8080/
timeout 2 bash -c 'exec 3<>/dev/tcp/127.0.0.1/2222; head -c 40 <&3'
Every one of these interactions is recorded as an event and reflected in the metrics from Step 6. Because no legitimate traffic ever hits these ports, anything that does is worth investigating.
Step 8: Add or edit decoy services
Each decoy is a single YAML file in /etc/beelzebub/services/, and the core settings live in /etc/beelzebub/beelzebub.yaml. To add a decoy, drop in a new YAML file and restart the service. For example, a fake Telnet decoy on port 2323:
sudo tee /etc/beelzebub/services/telnet-2323.yaml >/dev/null <<'YAML'
apiVersion: "v1"
protocol: "tcp"
address: ":2323"
description: "Telnet decoy"
banner: "Ubuntu 22.04 LTS"
deadlineTimeoutSeconds: 10
YAML
sudo systemctl restart beelzebub
sudo systemctl is-active beelzebub
The protocol can be ssh, http or tcp; address is the listening port; and for HTTP and SSH you define commands with a regex and a handler response to script how the decoy behaves. See the Beelzebub documentation for the full configuration reference. Remember to open any new decoy port on your NSG for the network segment you want to monitor.
Step 9: Scrape the metrics from Prometheus
Point your Prometheus server at the metrics endpoint. Because it is password protected, use a basic_auth block in your scrape config (over HTTPS in production, see below).
scrape_configs:
- job_name: beelzebub
metrics_path: /metrics
basic_auth:
username: cloudimg
password: <your-metrics-password>
static_configs:
- targets: ['<your-domain>']
Alert on any increase in beelzebub_events_total, or on a specific protocol counter, to turn the honeypot into an active tripwire that pages you when something touches a decoy.
Step 10: Enable HTTPS
nginx fronts the metrics endpoint on port 80 and is ready for TLS. Point a DNS record at the VM, then obtain a certificate with certbot and let it configure nginx for port 443.
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain>
After issuance, restrict the NSG so only 443 (and 22 from your management network) reaches the management surface, and scrape the metrics over https://<your-domain>/metrics with the password. The decoy ports remain open to the segment you are monitoring.
Persistence, first boot and updates
The Beelzebub configuration (/etc/beelzebub/) lives on the OS disk and is captured into the image, so a VM launched from this image is ready immediately. The first boot service generates the per VM metrics password and writes the info note, then disables itself so subsequent reboots are unaffected. The binary is compiled from the pinned upstream release during image build and the build toolchain is removed before capture, so the image ships a single static binary with no compiler left behind. The OS ships fully patched with unattended security upgrades enabled.
/usr/local/bin/beelzebub version
systemctl is-enabled beelzebub-firstboot.service
ls -1 /etc/beelzebub/services/

Support
Every cloudimg deployment includes 24/7 support with a 24 hour response SLA. Contact support@cloudimg.co.uk for help with deployment, configuration or scaling.
This is a repackaged open source software product with additional charges for cloudimg support services. Beelzebub is distributed under the GNU General Public License v3.0. cloudimg is not affiliated with or endorsed by the Beelzebub project or its authors. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.