NetAlertX on Ubuntu 24.04 on Azure User Guide
Overview
This image runs NetAlertX 26.7.1 on Ubuntu 24.04 LTS. NetAlertX (the project formerly known as Pi.Alert) is an open source network presence scanner and asset discovery framework. It sweeps the subnet it is attached to on a schedule, keeps an inventory of every device it has ever seen, records a presence timeline for each one, and raises an alert when a new device appears or a known device stops answering.
It answers a question most networks cannot answer honestly: what is actually on this network right now, and what changed?
What is included:
- NetAlertX 26.7.1, installed from the pinned upstream source tree, with its Python scanning and API server and its PHP web interface
- nginx on port
80as the authentication gate, with a password generated uniquely on your VM's first boot - SQLite as the device inventory and presence store
- The scanner toolchain:
fping,nmap,arp-scan,dnsutils,nbtscan,avahi-utils,traceroute,mtr - A scan scope automatically limited to your VM's own subnet on first boot
How this image is secured
NetAlertX upstream ships with its login switched off by default, and when you switch it on it hashes with unsalted SHA256 against a password published in its own documentation. Its web interface is also the control plane for a tool that scans your network. That is a reasonable default for a device on a trusted home LAN. It is not a reasonable default for a virtual machine with a public IP address.
So this image does not rely on it:
- nginx is the authentication gate. Every page requires a username and password that was generated on your VM, on its first boot, and written to a file only
rootcan read. Nothing in the image knows it, and no two VMs share one. - The application's API is bound to the loopback interface, so the gate cannot be bypassed by connecting to the API port directly.
- There is no default password anywhere in this image, and the services refuse to start if a credential has not been generated.
A note on what this product does
NetAlertX scans networks. Read the Scanning scope and authorisation section before you widen the scan scope. In short: only scan networks you own or are explicitly authorised to scan.
Prerequisites
- An Azure subscription
- The Azure CLI, if you deploy from the command line
- An SSH key pair
- A VM size of
Standard_B2sor larger
Step 1 - Deploy from the Azure Marketplace
- In the Azure portal, search the Marketplace for NetAlertX on Ubuntu 24.04 LTS by cloudimg.
- Select Create.
- Choose your subscription, resource group and region.
- Set the VM size to
Standard_B2sor larger. - Set the authentication type to SSH public key and the username to
azureuser. - On the Networking tab, allow inbound SSH (22) and HTTP (80).
- Select Review + create, then Create.
Restrict inbound port 80 to the addresses you will administer from. This is a management interface for a network scanner and it should not be open to the internet.
Step 2 - Deploy from the Azure CLI
az group create --name netalertx-rg --location eastus
az vm create \
--resource-group netalertx-rg \
--name netalertx-vm \
--image cloudimg:netalertx-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
Then open the ports you need, scoped to your own address:
# SSH and the web interface, restricted to your own public IP
MYIP="$(curl -s https://ifconfig.me)"
az vm open-port --resource-group netalertx-rg --name netalertx-vm --port 22 --priority 1001
az network nsg rule create \
--resource-group netalertx-rg \
--nsg-name netalertx-vmNSG \
--name allow-web-from-me \
--priority 1002 \
--source-address-prefixes "${MYIP}/32" \
--destination-port-ranges 80 \
--access Allow --protocol Tcp
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 - Confirm the services are running
Three services make up the appliance: the NetAlertX scanner and API server, the PHP processor, and the nginx gate in front of them.
systemctl is-active netalertx nginx php8.3-fpm
Expected output:
active
active
active
Confirm the network listeners. This is the security property that matters most: nginx is on port 80, and the application's API is on the loopback interface only.
sudo ss -lntp | grep -E ':80|:20212'
Expected output (process details trimmed):
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",...))
LISTEN 0 128 127.0.0.1:20212 0.0.0.0:* users:(("python3",...))
127.0.0.1:20212 is the important line. The API is not reachable from anywhere but the VM itself, so the nginx password gate is the only way in.

Step 5 - Retrieve your per-VM password
The password was generated on this VM's first boot. It is not in the image.
sudo cat /root/netalertx-credentials.txt
Expected output (your values will differ):
# NetAlertX -- credentials for THIS VM, generated on first boot.
# Generated: 2026-07-16T18:33:04Z
#
# These values are unique to this virtual machine. They are not present in the cloudimg
# image and cloudimg does not know them.
netalertx_url=http://<vm-public-ip>/
netalertx_admin_user=admin
netalertx_admin_password=<admin-password>
# The web interface is protected by the reverse proxy using the username and password
# above. The NetAlertX API uses its own bearer token:
netalertx_api_token=<api-token>

Step 6 - Confirm the gate is protecting the interface
Prove the authentication gate works before you rely on it. Without credentials the interface returns 401:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/devices.php
Expected output:
401
With your per-VM password it returns 200:
PW="$(sudo awk -F= '/^netalertx_admin_password=/{print $2; exit}' /root/netalertx-credentials.txt)"
curl -s -o /dev/null -w '%{http_code}\n' -u "admin:${PW}" http://127.0.0.1/devices.php
Expected output:
200
A wrong password is rejected:
curl -s -o /dev/null -w '%{http_code}\n' -u "admin:not-the-password" http://127.0.0.1/devices.php
Expected output:
401
The health endpoint is deliberately anonymous, so an Azure Load Balancer probe can reach it without a credential. It reports only that nginx is alive:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthz
Expected output:
200

Step 7 - Confirm the scan scope
On first boot the appliance read your VM's own network interface and scoped the scan to that subnet only.
grep '^SCAN_SUBNETS=' /app/config/app.conf
ip -4 -o addr show dev eth0 | awk '{print "interface network: " $4}'
Expected output (your subnet will differ):
SCAN_SUBNETS=['10.0.0.0/24 --interface=eth0']
interface network: 10.0.0.12/24
The configured scope is the network of the VM's own interface. It was derived on your VM, from your VM's address. The image does not ship a scan range.

Step 8 - Open the dashboard
Browse to your VM and sign in with the username and password from Step 5:
http://<vm-public-ip>/
Your browser will prompt for the username and password. The dashboard shows how many devices are known, how many are present right now, and what has changed recently.

Step 9 - Review the device inventory
Select Devices. Every host the scanner has found on your subnet is listed, with its address, its name where one could be resolved, and whether it is present right now.

Step 10 - Inspect a device
Select any device to open its detail view. Each device has its own identity, session count, notification settings and network details, across the Details, Tools, Sessions, Presence, Events and Plugins tabs.

Step 11 - Review the presence timeline
Select Monitoring > Presence. This is the heart of the product: a per-device timeline of when each host has actually been on your network, built from every scan the appliance has run.

Why this image scans with ICMP rather than ARP
NetAlertX's usual default scanner is arp-scan, and on a normal physical LAN it is the right choice: it returns each device's real hardware address, which is a stable identity even when addresses change.
Azure virtual networks do not work that way. Azure's software defined network answers every ARP request with the same synthetic hardware address:
sudo arp-scan --localnet --interface=eth0 2>/dev/null | head -6
Example output from an Azure VM:
Interface: eth0, type: EN10MB, MAC: 00:0d:3a:9c:4b:73, IPv4: 10.0.0.12
10.0.0.1 12:34:56:78:9a:bc (Unknown: locally administered)
10.0.0.4 12:34:56:78:9a:bc (Unknown: locally administered)
10.0.0.5 12:34:56:78:9a:bc (Unknown: locally administered)
Every host reports 12:34:56:78:9a:bc. Because NetAlertX keys its inventory on the hardware address, arp-scan on Azure would collapse your entire network into a single device.
This image therefore uses NetAlertX's ICMP scanner with synthetic addressing (ICMP_FAKE_MAC), which derives a stable, clearly marked fa:ce:... identifier per address, so each host on your virtual network is tracked as its own device. Hosts whose real address the appliance can resolve, such as the VM itself, keep it.
If you attach this VM to a network where ARP does return real hardware addresses, you can enable ARPSCAN in Settings.
Step 12 - Widen or change the scan scope
Read Scanning scope and authorisation first.
The supported way is Settings > Core > Scan subnets in the web interface. Set the scan scope to the networks you are authorised to scan, each with the interface to reach it on, then Save:
SCAN_SUBNETS=['10.0.0.0/24 --interface=eth0', '10.1.0.0/24 --interface=eth0']
The same setting lives in /app/config/app.conf if you prefer to manage it with a configuration tool. Confirm what is currently configured:
grep '^SCAN_SUBNETS=' /app/config/app.conf
If you edit the file directly, restart the scanner so it re-reads the configuration into its settings store:
sudo systemctl restart netalertx
To scan a peered virtual network, an on-premises network over a VPN or ExpressRoute, or any other subnet, the VM needs a network route to it and your Network Security Groups must permit ICMP.
Step 13 - Set up alerting
NetAlertX raises events when a device appears or disappears. To be told about them, enable a notification plugin in Settings. Email, webhooks, MQTT, ntfy, Pushover, Pushsafer, Telegram and Apprise are all included.
For email, set SMTP_SERVER, SMTP_PORT, SMTP_USER and SMTP_PASS in Settings > SMTP, then set SMTP_RUN to on_notification. Send yourself a test event from Maintenance > Test notifications.
Credentials you enter here are yours and are stored in /app/config/app.conf on your VM only.
Step 14 - Use the API
Every view in the interface is backed by an API that you can use directly. It authenticates with the bearer token from your credentials file, and it listens on the loopback interface, so use it from the VM or over an SSH tunnel.
TOKEN="$(sudo awk -F= '/^netalertx_api_token=/{print $2; exit}' /root/netalertx-credentials.txt)"
curl -s -H "Authorization: Bearer ${TOKEN}" http://127.0.0.1:20212/devices | head -c 200; echo
The API returns JSON describing the devices the appliance knows about. Without the token it returns 403:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:20212/devices
Expected output:
403
Scanning scope and authorisation
This product scans networks. Only scan networks you own or have explicit, documented authorisation to scan.
- Scanning networks you do not control may breach the Microsoft Azure Acceptable Use Policy, your agreement with your own network providers, and computer misuse law in many countries. It can also be detected as hostile reconnaissance and get your subscription suspended.
- This image ships scoped to your VM's own subnet and resolves that scope on your VM at first boot. It never ships a scan range and it will not scan beyond the network it was deployed into unless you widen it.
- If you widen the scope, the responsibility for authorisation is yours. Get it in writing before you scan a network you do not own.
- Discovery here is ICMP based. It is passive in intent, it does not attempt to exploit anything, and it does not perform port scanning unless you explicitly enable a plugin that does.
Security
- No default password. The administrator password and the API token are generated on your VM's first boot. Nothing in the image knows them.
- The gate is the reverse proxy. nginx authenticates every request to the interface. The application's own login is a separate, weaker mechanism and is left off; the reverse proxy already authenticates you.
- The API is on loopback. It cannot be reached from the network, so the gate cannot be bypassed.
- Fail closed. Both nginx and the scanner refuse to start if the per-VM credentials have not been generated, or if the scan scope has not been resolved for this VM.
- Restrict port 80 to the addresses you administer from, with your Network Security Group. This is a management interface.
- Basic Auth is only as private as the transport. Port 80 is plain HTTP, so credentials cross the network base64 encoded, not encrypted. For anything beyond a trusted management network, terminate TLS with Azure Application Gateway or a TLS terminating reverse proxy, and restrict port 80 in your Network Security Group to the networks that need it.
- Rotate the administrator password whenever you need to, with
sudo htpasswd -B /etc/nginx/.netalertx.htpasswd adminfollowed bysudo systemctl reload nginx. The password is also recorded in/root/netalertx-credentials.txt; update it there, or delete the file, once you have stored the password somewhere safe.
Maintenance
Ubuntu security updates are applied automatically by unattended-upgrades.
Check the scanner's log:
sudo tail -20 /app/log/app.log
Back up the device inventory and its history. The whole data tier is one SQLite file:
sudo sqlite3 /app/db/app.db ".backup '/root/netalertx-backup.db'"
Restart the scanner:
sudo systemctl restart netalertx
Licence and source code
NetAlertX is published by the NetAlertX project under the GNU General Public License version 3. cloudimg packages it as a virtual machine image; cloudimg is not the NetAlertX project.
NetAlertX is written in Python and PHP and runs from source, so the complete corresponding source for the version running on your VM is the /app directory itself. It is a git checkout pinned to the exact upstream commit this image was built from:
git -C /app log -1 --format='%H %d'
You can see every change cloudimg made to upstream's code as a live diff:
git -C /app diff --stat
The licence, the written offer for the corresponding source, and a description of every cloudimg modification are on the image:
ls /usr/share/doc/netalertx/
Expected output:
CLOUDIMG-CHANGES.txt LICENSE.txt SOURCE-OFFER.txt pip-audit.json python-requirements.lock.txt
Support
This image is maintained by cloudimg. For help with the image, contact cloudimg support at support@cloudimg.co.uk.
For questions about NetAlertX itself, see the NetAlertX project.