UpSnap on AWS User Guide
Overview
This image runs UpSnap 5.4.4 on Ubuntu 24.04 LTS. UpSnap is a small, fast, open source web application for powering the machines on your own network on and off. Register a device once with its MAC address, then wake it with a single click, reboot it, or shut it down with a command you define. A live dashboard pings every registered device so you always know what is up, and cron style schedules turn the whole thing into unattended automation.
Wake on LAN is built into virtually every business-class network adapter, yet almost nobody uses it, because the magic packet has to originate on the same broadcast domain as the target machine. UpSnap is the piece that closes that gap: one small always-on host inside your network that holds the device list and sends the packets, with a browser interface you can reach from anywhere you already trust.
What is included:
- UpSnap 5.4.4, installed from the pinned upstream release binary, verified against the SHA256 published in the release's own
checksums.txt - nginx terminating TLS on port
443, the only path to the application, with a certificate generated on your instance's first boot - An administrator account created on your instance's first boot with a 28 character password
- A dedicated Amazon EBS volume at
/var/lib/upsnapholding the device database, schedules, permissions and users - nmap, for the optional on-demand discovery pass over a subnet you supply
- An unauthenticated
/healthzendpoint for load balancer and monitoring probes
How this image is secured
A device power manager is a powerful thing to put on a network. UpSnap's shutdown feature runs a shell command that you define, and the upstream project is explicit in its README that UpSnap should not be published to the open internet. This image is built to match that advice rather than ignore it.
- The application is bound to
127.0.0.1:8090only. It is never bound to a public interface. nginx is the only way in, and the build verifies with the services running that nothing unexpected is listening on a non-loopback address. - The service runs as a dedicated non-root account (
upsnap) holding exactly one Linux capability,CAP_NET_RAW, which it needs for privileged ping and discovery. The shutdown command you define therefore cannot become a root shell on the instance. - Authentication is mandatory and cannot be disabled. Upstream removed the ability to turn it off in version 3.1.0. Every device, wake, shutdown and discovery route is permission checked, and this guide proves it below.
- Your administrator password is unique to your instance. It is generated on the first boot of your own instance, is 28 characters, and is written to a file only
rootcan read. Nothing is baked into the image and no two instances share a credential. - The database is created on your instance, not shipped in the image, so the token signing keys behind your sessions are yours alone.
- Nothing is preloaded. The image ships with no device, no scan range, no wake or shutdown command and no schedule. It performs no action against any network until you configure one.
A note on what this product does
UpSnap sends Wake on LAN magic packets, pings devices, can run a shutdown command you define, and can run an on-demand discovery pass over a CIDR you supply. Only ever point it at networks you own or are authorised to manage. The image ships with no scan range configured and will not touch a single machine until you tell it to.
Because the console controls power for real machines, treat it as management-plane infrastructure: restrict the security group to addresses you trust, and prefer reaching it over a VPN or AWS Direct Connect rather than publishing it to the internet.
Prerequisites
Before you deploy this image you need:
- An AWS account with permission to launch EC2 instances from the AWS Marketplace
- An EC2 key pair for SSH access
- A security group allowing inbound TCP 22 (SSH) and TCP 443 (the console) from the addresses you will connect from
- A route from the instance to the network whose devices you intend to manage. Magic packets are broadcast within a subnet, so the instance must sit on, or be bridged to, that broadcast domain
The recommended instance type is m5.large.
Launching the instance
From the AWS Marketplace console
- Open the product page and choose Continue to Subscribe, then Continue to Configuration.
- Choose your Region and the Ubuntu 24.04 delivery option.
- Choose Continue to Launch, then Launch through EC2.
- Pick the m5.large instance type, your key pair, and a security group allowing TCP 22 and TCP 443 from your own address.
- Launch, and wait for the instance to reach 2/2 checks passed.
From the AWS CLI
# Restrict both ports to YOUR public IP, not 0.0.0.0/0
MYIP=$(curl -s https://checkip.amazonaws.com)/32
aws ec2 create-security-group \
--group-name upsnap-sg \
--description "UpSnap console + SSH"
aws ec2 authorize-security-group-ingress \
--group-name upsnap-sg --protocol tcp --port 22 --cidr "$MYIP"
aws ec2 authorize-security-group-ingress \
--group-name upsnap-sg --protocol tcp --port 443 --cidr "$MYIP"
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name <your-key-pair> \
--security-groups upsnap-sg
Connecting to your instance
Connect over SSH as the login user for the OS variant you launched.
| OS variant | SSH login user |
|---|---|
| Ubuntu 24.04 LTS | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Confirm first boot has finished
First boot creates the database, the administrator account and the TLS certificate. It normally completes within a minute of the instance reaching 2/2 checks.
sudo test -f /var/lib/cloudimg/upsnap-firstboot.done \
|| { echo "FATAL: first boot has not finished yet - check: systemctl status upsnap-firstboot"; exit 1; }
echo "first boot completed at $(sudo cat /var/lib/cloudimg/upsnap-firstboot.done)"
Retrieve your administrator credentials
The credentials generated for your instance live in a root-only file. Read it on your own instance with:
sudo cat /root/upsnap-credentials.txt
The command below confirms the file is present and correctly populated without printing your password:
sudo grep -E '^UPSNAP_(URL|ADMIN_EMAIL|APP_LISTEN)=' /root/upsnap-credentials.txt
sudo grep -qE '^UPSNAP_ADMIN_PASSWORD=.{24,}$' /root/upsnap-credentials.txt \
|| { echo "FATAL: no per-instance administrator password was generated"; exit 1; }
echo "administrator password present: 28 characters, unique to this instance"
PERMS=$(sudo stat -c '%a %U:%G' /root/upsnap-credentials.txt)
[ "$PERMS" = "600 root:root" ] \
|| { echo "FATAL: credentials file permissions are $PERMS, expected 600 root:root"; exit 1; }
echo "credentials file permissions: $PERMS"
Verifying the deployment
Services are running
systemctl is-active upsnap.service nginx.service > /tmp/upsnap-svc.txt 2>&1
grep -c '^active$' /tmp/upsnap-svc.txt | grep -q '^2$' \
|| { echo "FATAL: expected both services active, got:"; cat /tmp/upsnap-svc.txt; exit 1; }
echo "upsnap.service and nginx.service are both active"
The application is bound to loopback only
This is the property that keeps the console from being reachable without authentication. The application must appear on 127.0.0.1:8090 and nowhere else.
BOUND=$(sudo ss -Hltn | awk '{print $4}' | grep ':8090$' | paste -sd' ' -)
echo "application bindings: ${BOUND:-none}"
[ -n "$BOUND" ] || { echo "FATAL: the application is not listening"; exit 1; }
for ep in $BOUND; do
case "$ep" in
127.*|'[::1]:8090') ;;
*) echo "FATAL: the application is bound to $ep, which is not loopback"; exit 1 ;;
esac
done
echo "confirmed: the application is reachable only from inside the instance"
The service runs as a non-root account
PID=$(systemctl show -p MainPID --value upsnap.service)
USER=$(ps -o user= -p "$PID" | tr -d ' ')
[ "$USER" = "upsnap" ] \
|| { echo "FATAL: the service runs as $USER, expected the unprivileged upsnap account"; exit 1; }
echo "service runs as: $USER (pid $PID)"
grep -E '^CapBnd:' "/proc/$PID/status"
echo "capability bounding set is CAP_NET_RAW only (0x2000)"
Only SSH and the console are reachable from outside
The image ships a fail-closed audit of every listening socket. It rejects anything unexpectedly bound to a non-loopback address.
sudo /usr/local/sbin/upsnap-exposure-check.sh '22 80 443' \
|| { echo "FATAL: an unexpected non-loopback listener exists"; exit 1; }
The health endpoint answers, and the console is served
HZ=$(curl -sk -o /dev/null -w '%{http_code}' https://127.0.0.1/healthz)
[ "$HZ" = "200" ] || { echo "FATAL: /healthz returned $HZ, expected 200"; exit 1; }
echo "/healthz: HTTP $HZ (unauthenticated, for load balancer probes)"
SHELL_CODE=$(curl -sk -o /dev/null -w '%{http_code}' https://127.0.0.1/)
[ "$SHELL_CODE" = "200" ] || { echo "FATAL: the console returned $SHELL_CODE, expected 200"; exit 1; }
echo "console: HTTP $SHELL_CODE"
Unauthenticated callers are refused
This is the check that matters most on a device power manager. Every privileged route must refuse a caller with no session. A refusal here is the expected and correct result.
CREATE=$(curl -sk -o /dev/null -w '%{http_code}' -H 'Content-Type: application/json' \
--data-binary '{"name":"x","ip":"127.0.0.1","mac":"00:11:22:33:44:55","netmask":"255.255.255.0"}' \
https://127.0.0.1/api/collections/devices/records)
case "$CREATE" in
400|401|403) echo "device create without a session: HTTP $CREATE (correctly refused)" ;;
*) echo "FATAL: an unauthenticated caller could create a device (HTTP $CREATE)"; exit 1 ;;
esac
for route in wake shutdown; do
CODE=$(curl -sk -o /dev/null -w '%{http_code}' "https://127.0.0.1/api/upsnap/$route/anything")
case "$CODE" in
401|403) echo "$route without a session: HTTP $CODE (correctly refused)" ;;
*) echo "FATAL: the $route route answered an unauthenticated caller (HTTP $CODE)"; exit 1 ;;
esac
done
SCAN=$(curl -sk -o /dev/null -w '%{http_code}' https://127.0.0.1/api/upsnap/scan)
case "$SCAN" in
401|403) echo "discovery without a session: HTTP $SCAN (correctly refused)" ;;
*) echo "FATAL: the discovery route answered an unauthenticated caller (HTTP $SCAN)"; exit 1 ;;
esac
LIST=$(curl -sk https://127.0.0.1/api/collections/devices/records | jq -r '.totalItems // -1')
[ "$LIST" = "0" ] \
|| { echo "FATAL: the device list returned $LIST record(s) to a caller with no session"; exit 1; }
echo "device list without a session: 0 records"
Your administrator account authenticates, and a wrong password does not
This block reads your password from the root-only credentials file at run time. It never prints it.
AE=$(sudo grep -m1 '^UPSNAP_ADMIN_EMAIL=' /root/upsnap-credentials.txt | cut -d= -f2-)
AP=$(sudo grep -m1 '^UPSNAP_ADMIN_PASSWORD=' /root/upsnap-credentials.txt | cut -d= -f2-)
OK=$(curl -sk -o /dev/null -w '%{http_code}' -H 'Content-Type: application/json' \
--data-binary "{\"identity\":\"$AE\",\"password\":\"$AP\"}" \
https://127.0.0.1/api/collections/_superusers/auth-with-password)
[ "$OK" = "200" ] || { echo "FATAL: the administrator account did not authenticate (HTTP $OK)"; exit 1; }
echo "administrator sign in: HTTP $OK"
BAD=$(curl -sk -o /dev/null -w '%{http_code}' -H 'Content-Type: application/json' \
--data-binary "{\"identity\":\"$AE\",\"password\":\"not-the-real-password\"}" \
https://127.0.0.1/api/collections/_superusers/auth-with-password)
[ "$BAD" = "400" ] || { echo "FATAL: a wrong password returned $BAD, expected a refusal"; exit 1; }
echo "wrong password: HTTP $BAD (correctly refused)"
The device database is on its own EBS volume
mountpoint -q /var/lib/upsnap \
|| { echo "FATAL: /var/lib/upsnap is not a separate mount"; exit 1; }
findmnt -no SOURCE,TARGET,FSTYPE,SIZE /var/lib/upsnap
DDEV=$(df -P /var/lib/upsnap/pb_data | awk 'NR==2{print $1}')
RDEV=$(df -P / | awk 'NR==2{print $1}')
[ "$DDEV" != "$RDEV" ] \
|| { echo "FATAL: the database is on the OS disk - the data volume is not in use"; exit 1; }
echo "device database on $DDEV, separate from the OS disk ($RDEV)"
Signing in
Open https://<public-ip>/ in a browser. The certificate is generated on your instance and is self signed, so your browser will warn once; accept it, or replace the certificate with your own as described below.
Sign in with the UPSNAP_ADMIN_EMAIL and UPSNAP_ADMIN_PASSWORD values from /root/upsnap-credentials.txt.

Change the administrator password from the account page as soon as you have signed in.
The device dashboard
Every registered device gets a card showing its name, IP address, MAC address and a power button. The dashboard pings each device on a schedule, so the status is live.

Adding your first device
Choose New, and stay on the Manual tab. A device needs a name, an IP address, a MAC address and a netmask. The MAC address is what the magic packet is addressed to; the IP address and netmask are what UpSnap pings to decide whether the device is up.

You can also add devices from the API. Substitute your own values:
curl -sk -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
--data-binary '{"name":"Build Runner 01","ip":"<device-ip>","mac":"<device-mac>","netmask":"255.255.255.0","port":9}' \
https://127.0.0.1/api/collections/devices/records
Prove a magic packet is actually sent
This block registers a temporary device, sends it a magic packet through the wake endpoint, then removes it again, leaving the device list exactly as it was. It proves the headline feature works end to end on your instance.
AE=$(sudo grep -m1 '^UPSNAP_ADMIN_EMAIL=' /root/upsnap-credentials.txt | cut -d= -f2-)
AP=$(sudo grep -m1 '^UPSNAP_ADMIN_PASSWORD=' /root/upsnap-credentials.txt | cut -d= -f2-)
TOKEN=$(curl -sk -H 'Content-Type: application/json' \
--data-binary "{\"identity\":\"$AE\",\"password\":\"$AP\"}" \
https://127.0.0.1/api/collections/_superusers/auth-with-password | jq -r '.token // empty')
[ -n "$TOKEN" ] || { echo "FATAL: could not obtain a session token"; exit 1; }
DEV=$(curl -sk -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
--data-binary '{"name":"guide-check","ip":"127.0.0.1","mac":"00:11:22:33:44:55","netmask":"255.255.255.0","port":9}' \
https://127.0.0.1/api/collections/devices/records | jq -r '.id // empty')
[ -n "$DEV" ] || { echo "FATAL: could not register the temporary device"; exit 1; }
echo "temporary device registered: $DEV"
WAKE=$(curl -sk -o /dev/null -w '%{http_code}' -H "Authorization: $TOKEN" \
"https://127.0.0.1/api/upsnap/wake/$DEV")
[ "$WAKE" = "200" ] || { echo "FATAL: the wake endpoint returned $WAKE, expected 200"; exit 1; }
echo "magic packet sent: HTTP $WAKE"
DEL=$(curl -sk -o /dev/null -w '%{http_code}' -X DELETE -H "Authorization: $TOKEN" \
"https://127.0.0.1/api/collections/devices/records/$DEV")
[ "$DEL" = "204" ] || { echo "FATAL: could not remove the temporary device (HTTP $DEL)"; exit 1; }
echo "temporary device removed: HTTP $DEL"
Scheduling power on and shutdown
Each device carries an optional wake cron and shutdown cron, plus the shutdown command to run. The shutdown command runs on this instance as the unprivileged upsnap account, so it is normally an SSH command against the target machine. Key based SSH is strongly preferred over putting a password in the command.
The ping interval that drives the live status is set globally under Settings.

Discovering devices on your own subnet
Rather than typing MAC addresses by hand, UpSnap can run a single on-demand discovery pass over a CIDR you supply, and offer the results for you to add. Choose New, then the Network Scan tab.
The image ships with no scan range configured - the screenshot below shows exactly that state on a freshly launched instance. You must supply the range yourself, and you must only scan networks you own or are authorised to scan.

Set the range to your own subnet, for example:
<your-subnet-cidr>
Reaching the network you intend to manage
A magic packet is a broadcast frame. It only reaches devices in the same broadcast domain as the sender, so where you place this instance decides what it can wake.
- Devices in the same VPC subnet work directly.
- Devices on your own premises need a path from the instance into that LAN: a Site-to-Site VPN or AWS Direct Connect, with the instance's traffic routed into the target subnet.
- Directed broadcast across a router is disabled on most equipment by default and is not recommended; putting the instance inside the target broadcast domain is the reliable approach.
Ports 22 and 443 should stay restricted to the addresses you administer from:
aws ec2 authorize-security-group-ingress \
--group-id <your-sg-id> --protocol tcp --port 443 --cidr <your-vpc-cidr>
Replacing the TLS certificate
First boot issues a self signed certificate naming your instance's address. To use your own certificate, replace these two files and reload nginx:
/etc/nginx/tls/upsnap.crt (certificate chain, 0644 root:root)
/etc/nginx/tls/upsnap.key (private key, 0600 root:root)
sudo install -o root -g root -m 0644 your-chain.crt /etc/nginx/tls/upsnap.crt
sudo install -o root -g root -m 0600 your-private.key /etc/nginx/tls/upsnap.key
sudo nginx -t && sudo systemctl reload nginx
Backing up
Everything that matters lives on the dedicated EBS volume mounted at /var/lib/upsnap, so an EBS snapshot of that volume is a complete backup of your devices, schedules, permissions and users.
echo "back up this volume:"
findmnt -no SOURCE,TARGET /var/lib/upsnap
sudo du -sh /var/lib/upsnap/pb_data
Troubleshooting
The console does not load. Check both services and the proxy configuration:
systemctl is-active upsnap.service nginx.service
sudo nginx -t
First boot did not complete. Read its journal:
sudo journalctl -u upsnap-firstboot.service --no-pager -n 40
A device never shows as online. UpSnap pings it from this instance. Confirm the instance can reach the device at all, and remember that the ping is what drives status, not the magic packet:
echo "the ping interval is configured under Settings in the web interface"
echo "confirm routing from this instance to the device subnet with: ip route"
ip route
A device does not wake. Wake on LAN must be enabled in the target machine's firmware and its network adapter must be configured to allow it while powered off. Confirm the MAC address is the one belonging to the wired adapter, and that this instance is in the same broadcast domain.
Support
cloudimg provides 24/7 technical support for this image by email and live chat.
- Email: support@cloudimg.co.uk
- Live chat: available 24/7 at cloudimg.co.uk
UpSnap is distributed under the MIT License. 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.