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/monitor

Configuration 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.env

The 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.target

Then:

sudo systemctl daemon-reload
sudo systemctl enable --now monitoring-agent.service
sudo journalctl -u monitoring-agent -f

Running 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:latest

Or 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 -d

The 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` command

    With Compose, docker compose pull && docker compose up -d does the same.

  • Logs go to stdout/stderr, so docker logs -f tenpmuptime-monitor is your journalctl equivalent.

  • Run a one-off CLI flag (like -list-checks) against the running container with docker 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 the docker run command 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 localhost inside it means the container itself, not your PC. If your server, or a check’s target, is running on the Windows machine itself, use host.docker.internal instead of localhost.

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 / flagWhat it does
SYNC_INTERVAL_SEC / -sync-intervalHow often, in seconds, the monitor asks the server for its check list. Default 45.
REPORT_INTERVAL_SEC / -report-intervalHow often it uploads results. Default 45.
MAX_CONCURRENT_CHECKS / -max-concurrent-checksThe 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-urlA 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-targetsComma-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-leveldebug, 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.