Running the monitor as a service#
Running monitor in a foreground terminal is fine for trying it out, but
for anything ongoing you want it to survive reboots, restart itself if it
crashes, and log somewhere you can check later. Two straightforward ways to
get that: a systemd unit, or a Docker container. Pick whichever fits how you
already manage the machine it’ll run on.
Either way you’ll need: the monitor binary (or, for Docker, the published image),
your server’s URL, and an enrollment token from Monitors → Add a monitor
(see Adding your first monitor
if you haven’t done this yet).
Option A: systemd#
Create a dedicated user and directories for its local state (its SQLite database of mirrored checks and unsent results):
sudo useradd --system --home /var/lib/monitoring --shell /usr/sbin/nologin monitoring
sudo mkdir -p /var/lib/monitoring /etc/monitoring /opt/monitoring
sudo chown monitoring:monitoring /var/lib/monitoring
sudo chmod 750 /var/lib/monitoring
sudo cp monitor /opt/monitoring/monitor && sudo chmod 755 /opt/monitoring/monitorConfiguration file, filled in with your own values:
sudo tee /etc/monitoring/monitor.env <<'EOF'
SERVER_URL=https://tenpmuptime.com
ENROLLMENT_TOKEN=<paste from the UI>
MONITOR_NAME=My first monitor
REGION=us
COUNTRY=US
CITY=New York
DB_PATH=/var/lib/monitoring/monitor.db
EOF
sudo chown root:monitoring /etc/monitoring/monitor.env
sudo chmod 640 /etc/monitoring/monitor.envThe token is only needed for the first successful enrollment — after that
the agent holds its own API key in its database, and you can drop
ENROLLMENT_TOKEN from the file if you like.
Service unit, /etc/systemd/system/monitoring-agent.service:
[Unit]
Description=Monitoring agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=monitoring
Group=monitoring
EnvironmentFile=/etc/monitoring/monitor.env
ExecStart=/opt/monitoring/monitor
WorkingDirectory=/var/lib/monitoring
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetThen:
sudo systemctl daemon-reload
sudo systemctl enable --now monitoring-agent.service
sudo journalctl -u monitoring-agent -fRunning more than one monitor on the same host? Give each its own
monitor.env/unit pair (a -shared/-private suffix, say) — it’s the same
setup twice, not a different one.
Option B: Docker#
A ready-made image is published with every monitor release, for Linux amd64, arm64 and armv7 — Docker picks the right one for your machine:
docker run -d --name tenpmuptime-monitor \
--restart unless-stopped \
-v tenpmuptime-monitor-data:/data \
-e SERVER_URL=https://tenpmuptime.com \
-e ENROLLMENT_TOKEN=<paste from the UI> \
-e MONITOR_NAME="My first monitor" \
-e REGION=us -e COUNTRY=US -e CITY="New York" \
ghcr.io/tenpm-software/tenpm-uptime-monitor:latestOr the same thing with Docker Compose, as compose.yml:
services:
monitor:
image: ghcr.io/tenpm-software/tenpm-uptime-monitor:latest
container_name: tenpmuptime-monitor
restart: unless-stopped
environment:
SERVER_URL: https://tenpmuptime.com
ENROLLMENT_TOKEN: <paste from the UI>
MONITOR_NAME: My first monitor
REGION: us
COUNTRY: US
CITY: New York
volumes:
- monitor-data:/data
volumes:
monitor-data:docker compose up -dThe image contains exactly the binary attached to the matching
release,
runs as a non-root user, and keeps its database at /data/monitor.db.
:latest follows the newest release; use a version tag such as :v0.2.1
instead if you’d rather upgrade deliberately.
A few things worth knowing:
Use a named volume for
/data, as above, not a bind mount — it comes up with the right ownership automatically, where a fresh bind-mounted host directory often doesn’t.The monitor’s identity (its server-assigned id and API key) lives in that volume, not in the container or image. Recreating the container — to move to a new version, for instance — keeps the same monitor as long as the volume survives:
docker pull ghcr.io/tenpm-software/tenpm-uptime-monitor:latest docker stop tenpmuptime-monitor && docker rm tenpmuptime-monitor # then re-run the same `docker run` commandWith Compose,
docker compose pull && docker compose up -ddoes the same.Logs go to stdout/stderr, so
docker logs -f tenpmuptime-monitoris yourjournalctlequivalent.Run a one-off CLI flag (like
-list-checks) against the running container withdocker exec:docker exec tenpmuptime-monitor monitor -list-checks
On Windows#
The image is a Linux image, so on Windows it runs under Docker Desktop in its default Linux containers mode (the WSL2 backend). Docker picks the right build for your machine, amd64 on a standard PC and arm64 on Windows on ARM. It can’t run as a native Windows container: with Docker Desktop switched to “Windows containers”, the pull fails with a platform mismatch.
The commands above work the same from PowerShell, with two differences:
- PowerShell doesn’t use
\to continue a line. Put thedocker runcommand on one line, end each line with a backtick (`) instead, or use the Compose file, which is identical on every platform. - The container runs inside Docker Desktop’s Linux VM, so
localhostinside it means the container itself, not your PC. If your server, or a check’s target, is running on the Windows machine itself, usehost.docker.internalinstead oflocalhost.
The monitor only makes outbound connections, so no port mapping or firewall
rule is needed. --restart unless-stopped brings the container back when
Docker Desktop starts, so turn on Start Docker Desktop when you sign in
if the monitor should survive a reboot.
Other settings#
Everything below is optional. Each is a command-line flag and an environment
variable (the flag wins if you set both), so in the systemd and Docker setups
above it goes in monitor.env or a -e option.
| Environment variable / flag | What it does |
|---|---|
SYNC_INTERVAL_SEC / -sync-interval | How often, in seconds, the monitor asks the server for its check list. Default 45. |
REPORT_INTERVAL_SEC / -report-interval | How often it uploads results. Default 45. |
MAX_CONCURRENT_CHECKS / -max-concurrent-checks | The most checks it will run at the same time. Default 20. Raise it on capable hardware with many checks, or lower it on a small device with a low open-file limit. |
PROXY_URL / -proxy-url | A proxy the monitor uses to reach the Tenpm Uptime server — for a network with no direct route out. http://, https:// or socks5://host:port, optionally with a user name and password. It is never used for your checks, which always connect to their targets directly. |
PROBE_TARGETS / -probe-targets | Comma-separated host:port endpoints the monitor dials to tell “my own internet is down” from “the target is down”. Defaults to well-known public resolvers. With a proxy set, the monitor checks its path to the server instead. |
LOG_LEVEL / -log-level | debug, info (default), warn or error. |
The monitor also refuses to start on an unsupported PROXY_URL scheme rather
than quietly ignoring it.
monitor -version prints the build’s version and exits — it doesn’t need any
of the settings above. The version is also shown on the monitor’s own page in
the app.
The command-line-only tools (-test-check, -import-private-check,
-list-checks, -export-private-checks) are covered in
Private-definition checks.
Either way#
SERVER_URL, MONITOR_NAME, REGION, COUNTRY and CITY are required on
every start, not just the first — don’t drop them from your config once
enrollment succeeds, only ENROLLMENT_TOKEN becomes optional. If you ever
need to move a monitor to new hardware, or its local database is lost,
Reset enrollment on the Monitors page and re-run with a fresh token and
-id <the existing monitor's id> (or MONITOR_ID as an env var) to reclaim
its history rather than enrolling a new one — see
Managing monitors.