Uptime checks are often the first line of defense for small teams running multiple apps without a full observability stack. In practice, the challenge is not just installing a monitor, but deploying it in a way that is secure, restart-safe, and easy to maintain during routine server updates. In this guide, you will deploy Uptime Kuma on Ubuntu using a rootless Podman container managed by systemd, with Caddy handling TLS and reverse proxying. The result is a production-friendly setup that avoids running your application container as root and gives you predictable day-2 operations.
Architecture and flow overview
This deployment uses a simple but reliable flow:
- Uptime Kuma runs as a rootless Podman container under a dedicated service user.
- systemd (user service) ensures the container starts on boot and restarts on failure.
- Caddy terminates HTTPS and forwards traffic to Kuma on localhost.
- Persistent storage is mounted from the service user home directory so upgrades do not destroy monitor history or notification settings.
Why this pattern works in production: rootless Podman reduces blast radius, systemd gives clean lifecycle management, and Caddy keeps certificate management automatic.
Prerequisites
- Ubuntu 22.04 or 24.04 server with sudo access
- A DNS record (for example,
status.example.com) pointing to your server - Open ports 80 and 443 at firewall and cloud security-group level
- Email/Slack/Telegram webhook credentials ready for alerting setup
- At least 1 vCPU and 1 GB RAM (2 GB recommended for growth)
Step 1: Prepare OS packages and dedicated service account
Use a dedicated Linux user so container files, logs, and systemd user units stay isolated from your admin account.
sudo apt update
sudo apt -y upgrade
sudo apt -y install curl ca-certificates uidmap dbus-user-session slirp4netns fuse-overlayfs
sudo useradd -m -s /bin/bash kuma
sudo loginctl enable-linger kuma
If the copy button does not work in your browser, select the command block and copy manually.
Why linger matters: with lingering enabled, systemd user services can start at boot even when the kuma user has not logged in interactively.
Step 2: Install Podman and verify rootless runtime
sudo apt -y install podman
podman --version
sudo -iu kuma bash -lc 'podman info --debug | sed -n "1,40p"'
If the copy button does not work in your browser, select the command block and copy manually.
Confirm Podman reports rootless operation for the kuma user. If you see permission warnings, verify uidmap is installed and the user has subordinate UID/GID mappings in /etc/subuid and /etc/subgid.
Step 3: Create persistent data directories and pull image
Pinning to a known version reduces surprise breakage from unattended upgrades. You can move to latest after validation.
sudo -iu kuma bash -lc '
mkdir -p ~/containers/uptime-kuma/data
podman pull docker.io/louislam/uptime-kuma:1
'
If the copy button does not work in your browser, select the command block and copy manually.
Step 4: Run container once and generate systemd user unit
First run the container manually so you can validate bind mounts and port exposure, then convert it into a managed unit.
sudo -iu kuma bash -lc '
podman run -d \
--name uptime-kuma \
--restart=unless-stopped \
-p 127.0.0.1:3001:3001 \
-v ~/containers/uptime-kuma/data:/app/data:Z \
docker.io/louislam/uptime-kuma:1
podman ps
podman generate systemd --name uptime-kuma --files --new
mkdir -p ~/.config/systemd/user
mv container-uptime-kuma.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now container-uptime-kuma.service
systemctl --user status container-uptime-kuma.service --no-pager
'
If the copy button does not work in your browser, select the command block and copy manually.
Binding to 127.0.0.1:3001 means Kuma is not directly exposed to the internet. All public access will pass through Caddy over HTTPS.
Step 5: Install and configure Caddy for TLS and reverse proxy
Caddy keeps this setup operationally light because certificate provisioning and renewal are automatic.
sudo apt -y install debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null
sudo apt update
sudo apt -y install caddy
If the copy button does not work in your browser, select the command block and copy manually.
sudo tee /etc/caddy/Caddyfile >/dev/null <<'EOF'
status.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:3001
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
EOF
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl enable --now caddy
sudo systemctl status caddy --no-pager
If the copy button does not work in your browser, select the command block and copy manually.
Replace status.example.com with your real domain before starting Caddy.
Step 6: Secret handling and notification hardening
Uptime Kuma stores monitor and notification configuration in its data volume. Treat that volume as sensitive because it can include webhook tokens and integration details. For production teams, follow these controls:
- Restrict filesystem access to the
kumauser only. - Use destination-specific tokens with least privilege (for example, dedicated Slack webhook channel).
- Rotate compromised tokens immediately and update corresponding Kuma notifications.
- Back up
~/containers/uptime-kuma/datawith encryption at rest. - Keep host-level SSH access locked down with key-based auth and optional fail2ban.
sudo -iu kuma bash -lc '
chmod 700 ~/containers
chmod 700 ~/containers/uptime-kuma
chmod 700 ~/containers/uptime-kuma/data
'
# optional quick backup
sudo tar -czf /var/backups/uptime-kuma-data-$(date +%F).tar.gz -C /home/kuma/containers/uptime-kuma data
If the copy button does not work in your browser, select the command block and copy manually.
Step 7: Verification checklist
Before handing this service to your team, validate all layers:
- Container health: Podman container is running and bound to localhost.
- Service management: systemd user unit is enabled and survives reboot.
- TLS: HTTPS certificate is issued and browser shows secure lock.
- Alert path: test monitor triggers expected notification destination.
- Persistence: monitors remain after container restart.
# host checks
sudo -iu kuma bash -lc 'podman ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
sudo -iu kuma bash -lc 'systemctl --user is-enabled container-uptime-kuma.service'
# tls check
curl -I https://status.example.com
# restart simulation
sudo -iu kuma bash -lc 'systemctl --user restart container-uptime-kuma.service'
sleep 3
curl -I https://status.example.com
If the copy button does not work in your browser, select the command block and copy manually.
Common issues and fixes
1) Caddy cannot obtain certificates
Usually DNS is not pointing correctly, or ports 80/443 are blocked upstream. Verify A/AAAA records and cloud firewall rules first. Also confirm no other process is already binding 80/443.
2) systemd user unit does not start after reboot
Most often lingering was not enabled. Re-run sudo loginctl enable-linger kuma and confirm user service files are under /home/kuma/.config/systemd/user.
3) SELinux/Z mount errors on data volume
On environments with labeling, keep :Z in volume mounts. If your host is not SELinux-based, it is usually harmless; if you remove it, test write operations from inside Kuma.
4) Alerts not firing despite monitor failures
Check notification integration secrets, test endpoint connectivity, and verify rate-limiting behavior on the destination platform. Use a dedicated monitor for notification-path testing.
5) High latency on checks
Reduce monitor frequency for non-critical endpoints, distribute checks over intervals, and avoid running heavy host tasks during check spikes.
FAQ
Can I use Nginx instead of Caddy?
Yes. The core pattern is unchanged: keep Kuma on localhost and proxy from a hardened HTTPS frontend. Caddy is used here for faster certificate automation and simpler config.
Why rootless Podman instead of Docker?
Rootless Podman reduces privilege by default and integrates cleanly with user-level systemd units. It is a good fit for teams that want lower blast radius without a full orchestrator.
How do I upgrade Uptime Kuma safely?
Take a backup of the data directory, pull the target image tag, restart the user service, and verify monitor history and notifications. Avoid blind upgrades in business hours.
Can I run multiple Kuma instances on one host?
Yes. Use separate users or separate ports and data directories per instance. Front each instance with its own hostname and reverse proxy route to keep boundaries clear.
What backup frequency is recommended?
For production monitoring, daily encrypted backups are a practical baseline. Increase frequency if monitor definitions or on-call routing changes often.
How should I handle team access?
Start with strong admin credentials and restricted network access. For larger teams, pair this deployment with SSO-capable perimeter access controls and clear change-management practices.
Related internal guides
- Production Guide: Deploy Grafana Loki with Docker Compose + Caddy on Ubuntu
- Production Guide: Deploy Sentry with Docker Compose + Caddy + PostgreSQL + Redis on Ubuntu
- Production Guide: Deploy Keycloak with Docker Compose + Traefik + PostgreSQL on Ubuntu
Talk to us
If you want support designing or hardening your monitoring platform, we can help with architecture, migration planning, and production readiness.