SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-25

Uptime Kuma: tự host status monitoring bằng Docker

Chạy Uptime Kuma bằng Docker trên VPS riêng để giám sát website, port, DNS và cron job; gửi alert qua email hoặc Telegram, kèm status page public.

Bạn đang xây dựng gì

Một container nhỏ duy nhất để giám sát các server và website khác từ bên ngoài, rồi báo cho bạn ngay khi một dịch vụ ngừng phản hồi qua email, Telegram, Discord hoặc webhook. Uptime Kuma là một tiến trình Node sử dụng một file SQLite, nên chạy ổn định với 256-512 MB RAM, đồng thời cung cấp dashboard trực tiếp, biểu đồ lịch sử và trang trạng thái public. Việc cài đặt chỉ cần một file Compose gồm mười dòng; điều thực sự quan trọng là bạn chạy nó ở đâualert đã từng được kích hoạt trong một lần kiểm thử hay chưa. Một monitor mà bạn chưa từng xác minh là có thể liên lạc với mình còn tệ hơn không có monitor nào: nó tạo cho bạn cảm giác hệ thống đang được bảo vệ, trong khi thực tế không giám sát được gì.

Chạy monitor ở nơi sự cố không thể ảnh hưởng đến nó

Quyết định này ảnh hưởng trực tiếp đến toàn bộ hệ thống, nên cần thực hiện trước tiên. Không chạy Uptime Kuma trên cùng máy chủ với các dịch vụ mà nó giám sát. Nếu monitor chạy trên chính server được giám sát, sự cố bạn cần phát hiện, chẳng hạn server bị tắt hoặc hết memory, cũng sẽ làm monitor dừng theo. Khi đó bạn không nhận được alert nào: monitor đã chết sẽ im lặng giống hệt trạng thái “mọi thứ vẫn bình thường”. Có một vấn đề khó nhận thấy hơn ngay cả khi server vẫn hoạt động: monitor trỏ đến localhost sẽ dùng chung CPU với workload. Khi tải tăng đột biến, chính lượt kiểm tra của monitor có thể timeout và chuyển target sang trạng thái down, tạo ra cảnh báo giả trong khi user thực tế vẫn truy cập được dịch vụ.

Vì vậy, hãy chạy Uptime Kuma trên một VPS khác với VPS được giám sát. Tốt nhất là dùng provider hoặc region khác, rồi truy cập các dịch vụ theo cách user truy cập: qua public internet và bằng hostname. Một instance giá rẻ là đủ. Một VPS monitoring nhỏ có thể giám sát tất cả server của bạn. Việc tách riêng này đặc biệt quan trọng với các app nặng mà bạn host. Ví dụ, thư viện ảnh PhotoPrism hoặc Immich có thể chiếm CPU trong nhiều giờ khi index một lần import mới. Nếu monitor dùng chung phần cứng, nó có thể báo dịch vụ down dù dịch vụ chỉ đang bận. Để phát hiện chính Uptime Kuma bị dừng, hãy thêm một push heartbeat từ cron trên một máy khác.

Điều kiện tiên quyết và sizing

  • Một VPS Ubuntu 24.04 mới cài, có Docker Engine và plugin Compose v2 được cài từ apt repository của Docker, không dùng package docker.io của distro vì package này thường chậm cập nhật.
  • 256 MB RAM đủ chạy một số monitor; 512 MB đến 1 GB sẽ thoải mái hơn nếu chạy hàng chục monitor cùng reverse proxy, còn CPU gần như không tải khi không thực hiện check.
  • Một domain và bản ghi DNS A (ví dụ status.example.com trỏ đến VPS), chỉ khi bạn muốn dùng TLS và public status page. Instance private có thể bỏ qua DNS và dùng VPN hoặc SSH tunnel.
  • Kết nối mạng outbound đến nơi nhận alert: SMTP đến mail provider hoặc HTTPS đến Telegram và Discord.

Tệp Compose

Đặt nội dung này vào /srv/uptime-kuma/compose.yaml.

services:
  uptime-kuma:
    image: louislam/uptime-kuma:2
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - "127.0.0.1:3001:3001"
    volumes:
      - kuma-data:/app/data

volumes:
  kuma-data:

Khởi động container và theo dõi lần boot đầu tiên:

sudo mkdir -p /srv/uptime-kuma
# save the file above as /srv/uptime-kuma/compose.yaml, then:
cd /srv/uptime-kuma && sudo docker compose up -d
sudo docker compose logs -f uptime-kuma

Nếu khởi động đúng, log sẽ ghi Listening on 3001 rồi không phát sinh thêm log. Ba điểm trong tệp này là có chủ đích.

127.0.0.1:3001:3001, không phải 3001:3001. Docker publish port bằng các rule DNAT được xử lý trước khi ufw nhận packet, vì vậy một 3001:3001 đơn giản sẽ đưa dashboard lên Internet công khai bất kể firewall của bạn được cấu hình thế nào. Bind vào loopback giúp dashboard ở chế độ private và chỉ expose reverse proxy; instance private có thể bỏ qua proxy rồi truy cập 3001 qua WireGuard VPN tự host.

Named volume tại /app/data. Toàn bộ dữ liệu Uptime Kuma ghi nhớ, gồm database SQLite, monitor, cài đặt notification và logo của status page, đều nằm tại đó. Nếu mất volume này, bạn sẽ bắt đầu lại từ màn hình admin trống; đây là dữ liệu duy nhất bắt buộc phải backup.

Image được pin vào major tag :2. Đây là stable line hiện tại; hãy kiểm tra Docker Hub để tìm major mới nhất trước khi copy, và không bao giờ theo dõi một tag thay đổi như latest vì project đã deprecate tag đó. Việc nâng major version của image này sẽ thực hiện migration database một chiều. Bạn cần chủ động kích hoạt migration, không nên vô tình thực hiện trong một lần pull định kỳ.

Có một lưu ý: /app/data phải nằm trên filesystem hỗ trợ POSIX file lock. Docker volume local là phù hợp; trên NFS, database SQLite sẽ bị corrupt và bạn sẽ gặp SQLITE_BUSY cùng database disk image is malformed, vì vậy không bao giờ dùng network share.

Lần đầu chạy: tạo tài khoản admin

Truy cập instance qua proxy tại https://status.example.com hoặc qua SSH tunnel: chạy ssh -L 3001:127.0.0.1:3001 user@your-vps rồi mở http://localhost:3001. Trang đầu tiên là form thiết lập username và password cho admin; không có thông tin đăng nhập mặc định. Hãy chọn một password thực sự an toàn: dashboard này có thể thấy địa chỉ nội bộ và token của mọi thứ bạn monitor. Nếu sau này quên password, hãy reset trên host, không thực hiện trong browser:

sudo docker compose exec uptime-kuma npm run reset-password

Thêm các kênh thông báo trước và kiểm tra chúng

Thiết lập cảnh báo trước khi thêm monitor, để bạn có thể gắn một kênh khi tạo từng monitor. Vào Settings rồi Notifications rồi Setup Notification, sau đó dùng nút Test của từng kênh để xác nhận tin nhắn đến nơi. Một notification chưa được kiểm tra là nguyên nhân phổ biến thứ hai khiến cấu hình âm thầm không hoạt động.

Email (SMTP). Điền host, port, encryption, username, password, FromTo. Hai tổ hợp hoạt động là 465 khi đặt "Secure" thành TLS/SSL, hoặc 587 với STARTTLS. Với Gmail và hầu hết nhà cung cấp có bật xác thực hai yếu tố, bạn phải tạo app password; password tài khoản thông thường sẽ trả về Error: Invalid login: 535-5.7.8 Username and Password not accepted.

Telegram. Nhắn tin cho @BotFather, gửi /newbot rồi sao chép bot token. Để lấy chat ID, hãy nhắn cho bot mới tạo một lần, mở https://api.telegram.org/bot<token>/getUpdates và đọc chat.id từ JSON. Bot chưa từng nhận tin nhắn trước đó sẽ có getUpdates rỗng và không có nơi để gửi tin.

Discord. Trong channel, mở Edit Channel rồi Integrations rồi Webhooks rồi New Webhook, sao chép URL và dán URL đó vào notification Discord.

Generic webhook. Với các dịch vụ khác, chẳng hạn Slack incoming webhook, endpoint tùy chỉnh hoặc hook tự động hóa nhà, loại Webhook sẽ POST payload JSON đến URL bạn cung cấp. Tích hợp Apprise đi kèm hỗ trợ phần lớn trong số khoảng chín mươi dịch vụ còn lại trong danh sách. Nếu không muốn có bên thứ ba đứng giữa sự cố và điện thoại của bạn, hãy chọn loại ntfy tích hợp sẵn và trỏ nó đến một ntfy server do bạn tự vận hành. Server này sẽ đẩy thông báo đến điện thoại qua một channel mà bạn kiểm soát từ đầu đến cuối.

Thêm monitor, từng loại một

Nhấp Add New Monitor, chọn một loại rồi đặt Friendly Name, Check Interval (60 giây là hợp lý), Retries (số lần thất bại liên tiếp trước khi chuyển sang trạng thái "down"; dùng 2 hoặc 3 để một packet bị mất không gây page) và các notification cần kích hoạt. Bạn sẽ dùng các loại sau:

  • HTTP(s). Một URL đầy đủ. Trạng thái up nghĩa là nhận được status code được chấp nhận (mặc định là 200-299; mở rộng trong Accepted Status Codes nếu 301 hoặc 401 là bình thường với bạn). Đây là loại monitor chính cho website và API.
  • HTTP(s) - Keyword. Dùng cùng request đó, nhưng chỉ được xem là "up" khi body có một chuỗi chỉ định, hoặc không có chuỗi đó nếu bật Invert. Cách này phát hiện trường hợp website trả về 200 OK nhưng hiển thị "Error establishing a database connection", trong khi HTTP check thông thường vẫn xem là healthy. Đây cũng là check phù hợp cho frontend trên browser gọi đến một backend riêng, chẳng hạn skin cửa hàng video Halcyon chạy trên Jellyfin, khi page shell của nó vẫn vui vẻ trả về 200 dù media server phía sau không thể truy cập.
  • TCP Port. Chỉ thực hiện TCP connection đến một host và port, phù hợp với các dịch vụ không dùng HTTP: SSH trên 22, Postgres trên 5432, SMTP server trên 25 hoặc game server.
  • Ping. ICMP echo: kiểm tra khả năng truy cập và latency với chi phí thấp. Tuy nhiên, nhiều network và cloud firewall chặn ICMP, nên monitor ping màu đỏ có thể nghĩa là "host down" hoặc "provider chặn ping"; hãy xác nhận bằng TCP monitor.
  • DNS. Resolve một record (A, AAAA, MX, TXT và các loại khác) thông qua resolver bạn chỉ định, đồng thời có thể xác nhận câu trả lời. Cách này giúp phát hiện sớm sự cố tại registrar hoặc DNS.
  • Push. Monitor theo hướng từ bên trong ra ngoài, sẽ trình bày ở phần tiếp theo.

Theo dõi cron job bằng monitor push (heartbeat)

Tất cả monitor ở trên đều kết nối vào service của bạn từ bên ngoài. Monitor push hoạt động theo chiều ngược lại: Uptime Kuma chờ, còn job của bạn gọi đến để báo rằng “tôi đã chạy”. Đây là cách duy nhất đáng tin cậy để theo dõi backup hoặc cron: HTTP check chỉ biết một URL có phản hồi, còn chỉ job mới biết nó đã hoàn tất.

Tạo một monitor có loại Push. Uptime Kuma sẽ tạo một URL duy nhất như sau:

https://status.example.com/api/push/j8Xa2Kd9Qe?status=up&msg=OK&ping=

Đặt Heartbeat Interval bằng chu kỳ chạy của job, cộng thêm một khoảng đệm nhỏ. Sau đó thêm một dòng vào cuối script để dòng này chỉ chạy khi job thành công:

#!/usr/bin/env bash
set -euo pipefail
# ... your backup or job runs here; set -e aborts on any failure ...
curl -fsS --retry 3 "https://status.example.com/api/push/j8Xa2Kd9Qe?status=up&msg=backup+ok&ping="

Nếu job lỗi, set -e sẽ dừng trước khi chạy curl; nếu máy chủ bị tắt, job cũng không chạy. Trong cả hai trường hợp, heartbeat sẽ dừng. Khi khoảng thời gian gồm interval và số lần retry vượt qua, Uptime Kuma sẽ chuyển monitor sang trạng thái down và gửi cảnh báo. Hãy coi push token này là bí mật: bất kỳ ai có token đều có thể giả mạo heartbeat bình thường.

Xây dựng trang trạng thái công khai

Trang trạng thái là giao diện dành cho khách hàng: hiển thị những service nào đang hoạt động và lịch sử gần đây của chúng, nhưng không để lộ dashboard của bạn. Vào Status Pages rồi New Status Page, đặt tên và slug (đường dẫn công khai, chẳng hạn /status/main), kéo các monitor cần dùng vào những nhóm như "Websites" và "APIs", thêm logo cùng phần mô tả ngắn, rồi bấm Save. Bạn cũng có thể gắn trang này với domain riêng để status.example.com phục vụ trực tiếp trang đó.

Có hai điểm cần lưu ý: chỉ thêm những monitor mà bạn chấp nhận công khai, vì trang trạng thái cho biết một service có tồn tại hay không và hiện có hoạt động hay không; còn dashboard vẫn nằm sau bước đăng nhập, trong khi trang trạng thái được công khai có chủ đích và không yêu cầu auth.

Đặt phía sau reverse proxy có TLS và lưu ý WebSocket

Với một instance public, hãy đặt reverse proxy phía trước container chỉ bind vào loopback để xử lý TLS và hostname. Chi tiết thường gây lỗi là: UI của Uptime Kuma là ứng dụng Socket.IO tương tác theo thời gian thực, nên proxy phải upgrade kết nối WebSocket. Nếu bỏ qua bước này, trang vẫn tải nhưng không bao giờ kết nối; dashboard cứ hiển thị "Connecting...", heartbeat trực tiếp không cập nhật, và console của trình duyệt hiển thị WebSocket connection to 'wss://.../socket.io/...' failed.

Cài nginx và certbot, sau đó tạo vhost proxy đến cổng loopback. Trước mắt hãy cho vhost chạy trên port 80 rồi để certbot thêm TLS sau; phần challenge, renewal timer và các trường hợp lỗi đã được trình bày trong cấp chứng chỉ Let's Encrypt bằng certbot và nginx.

sudo apt install -y nginx certbot python3-certbot-nginx

Lưu nội dung này thành /etc/nginx/sites-available/status.example.com; hai dòng WebSocket là phần quan trọng:

server {
    listen 80;
    server_name status.example.com;

    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
    }
}

Enable site, kiểm tra cấu hình, sau đó để certbot sửa block để listen trên 443, thêm certificate và redirect HTTP sang HTTPS:

sudo ln -s /etc/nginx/sites-available/status.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d status.example.com

Cặp UpgradeConnection "upgrade" là phần cốt lõi, còn proxy_read_timeout 3600s ngăn nginx đóng socket kết nối lâu dài; certbot sẽ chép cả hai dòng vào block 443 mà nó tạo. Nếu bạn đã chạy nhiều container phía sau một proxy, định tuyến chúng qua Traefik với TLS tự động cũng làm được việc tương tự bằng container label và mặc định forward WebSocket upgrade.

Không dùng basic auth cho toàn bộ vhost, vì cách đó cũng chặn public status page và endpoint /api/push. Hãy giữ cơ chế login tích hợp của Uptime Kuma, thêm dùng fail2ban để monitor các lần login thất bại lặp lại nếu dịch vụ public trên Internet, và nếu dashboard không cần public thì bỏ proxy và truy cập qua VPN.

Theo dõi thời hạn chứng chỉ đúng cách

Monitor HTTP(S) cũng có thể cảnh báo trước khi chứng chỉ TLS hết hạn: chọn Certificate Expiry Notification và Uptime Kuma sẽ cảnh báo trước số ngày bạn đặt. Hai lỗi cấu hình khiến monitor đọc sai. Hãy monitor bằng hostname, không phải IP. Nếu request không có SNI, server sẽ trả về chứng chỉ mặc định và bạn sẽ thấy Hostname/IP does not match certificate's altnames. Không chọn Ignore TLS/SSL Error trên monitor mà bạn muốn nhận cảnh báo hết hạn. Tùy chọn này dành cho các host nội bộ dùng chứng chỉ tự ký (unable to verify the first certificate, DEPTH_ZERO_SELF_SIGNED_CERT), nhưng nó khiến Uptime Kuma bỏ qua toàn bộ việc kiểm tra chứng chỉ, bao gồm cả thời hạn.

Sao lưu: mọi thứ nằm trong một thư mục

Vì mọi thứ nằm trong /app/data, bản sao lưu là bản sao của volume được tạo khi container đã dừng. Khi đó, file SQLite ở trạng thái nhất quán:

cd /srv/uptime-kuma
sudo docker compose stop
sudo docker run --rm \
  -v uptime-kuma_kuma-data:/data \
  -v /var/backups/kuma:/backup \
  alpine tar czf /backup/kuma-$(date -u +%Y%m%dT%H%M%SZ).tgz -C /data .
sudo docker compose start

Trước tiên, xác nhận tên thực của volume bằng docker volume ls | grep kuma, vì Compose thêm tiền tố là tên thư mục dự án. Sau đó, chép file tarball ra khỏi máy chủ, vì bản sao lưu trên cùng VPS chỉ là một bản sao, không phải bản sao lưu. Khôi phục thực hiện theo chiều ngược lại: dừng stack, giải nén vào một volume /app/data trống, rồi khởi động stack.

Nâng cấp

Nâng cấp là việc pull image:

cd /srv/uptime-kuma
sudo docker compose pull
sudo docker compose up -d

Container mới sẽ chạy migration database khi khởi động lần đầu; hãy monitor docker compose logs -f. Tạo bản backup ở trên trước khi pull và giữ nguyên trong cùng một major tag: chuyển từ :1 sang :2 là migration một chiều, vì vậy hãy backup trước và kiểm tra release notes.

Các lỗi thường gặp và chuỗi thông báo bạn sẽ thấy

Báo “down” sai trên monitor trỏ vào localhost. Monitor chuyển sang màu đỏ với timeout of 48000ms exceeded hoặc connect ETIMEDOUT, nhưng service vẫn phản hồi từ laptop của bạn. Nếu monitor nhắm đến cùng máy chủ đang chạy Uptime Kuma, CPU hoặc memory tăng đột biến đã làm quá trình kiểm tra không đủ tài nguyên, chứ không phải target bị lỗi. Chuyển monitor sang một VPS riêng và trỏ đến hostname public.

connect ECONNREFUSED 127.0.0.1:443 (hoặc bất kỳ port nào). Không có tiến trình nào đang listen trên port đó. Nguyên nhân có thể là service đang down, hoặc bạn đã monitor localhost từ bên trong container, nơi 127.0.0.1container, không phải server của bạn. Hãy monitor hostname public, không phải loopback.

Invalid login: 535-5.7.8 Username and Password not accepted khi kiểm tra email. SMTP credentials không đúng, hoặc provider yêu cầu app-specific password nhưng bạn lại nhập account password. Hãy tạo app password rồi dán mật khẩu đó vào.

connect ETIMEDOUT hoặc queryA ETIMEDOUT <host> khi kiểm tra email. Port không đúng, hoặc provider chặn SMTP outbound. Xác nhận 465 hoặc 587 khớp với thiết lập Secure/STARTTLS, rồi kiểm tra từ host bằng nc -vz smtp.example.com 587. Nhiều provider chặn outbound 25 và một số provider chặn các port submission cho đến khi bạn yêu cầu mở.

self signed certificate hoặc unable to verify the first certificate khi kiểm tra email. SMTP server cung cấp certificate mà Node không trust. Hãy sửa certificate trên mail server thay vì bỏ qua lỗi này.

Dashboard bị kẹt ở “Connecting...”, console hiển thị WebSocket connection ... failed. Reverse proxy không nâng cấp kết nối WebSocket. Trên nginx, hãy thêm các header UpgradeConnection "upgrade", hoặc dùng proxy tự forward các header này theo mặc định như Traefik hoặc Caddy. HTML vẫn tải được vì đó là một HTTP GET thông thường; chỉ live socket mới cần nâng cấp kết nối.

Monitor kiểm tra thời hạn certificate không bao giờ cảnh báo hoặc cảnh báo sai. Có thể tùy chọn Ignore TLS/SSL Error đang được bật, khiến việc kiểm tra certificate bị tắt. Cũng có thể monitor trỏ đến IP và đọc nhầm certificate vì thiếu SNI, nên hiển thị Hostname/IP does not match certificate's altnames. Hãy tắt tùy chọn bỏ qua lỗi và monitor bằng hostname.

SQLITE_BUSY hoặc database disk image is malformed trong log. Volume /app/data nằm trên filesystem không hỗ trợ file locking đúng cách, thường là NFS. Hãy chuyển volume này sang Docker volume local và khôi phục dữ liệu từ backup.

FAQ

Nên chạy công cụ monitor uptime ở đâu?

Hãy chạy trên một server khác với các server được monitor, tốt nhất là ở provider hoặc region khác. Công cụ này nên truy cập các server đó bằng hostname qua public Internet, giống như cách người dùng truy cập. Nếu monitor dùng chung một máy với các target, sự cố làm server ngừng hoạt động cũng sẽ làm monitor ngừng theo. Host bị quá tải cũng có thể báo "down" cho những service thực tế vẫn hoạt động bình thường. Một VPS riêng cấu hình nhỏ sẽ tránh được cả hai vấn đề này.

Làm cách nào để nhận cảnh báo qua Telegram hoặc email?

Thêm channel trong Settings rồi Notifications, sau đó gán channel đó cho từng monitor. Với Telegram, tạo bot bằng @BotFather và đọc chat.id từ https://api.telegram.org/bot<token>/getUpdates. Với email, dùng 465 cho SSL hoặc 587 cho STARTTLS, cùng với app password nếu provider của bạn bật xác thực hai yếu tố. Nhấn Test và xác nhận đã nhận được tin nhắn trước khi phụ thuộc vào channel này.

Uptime Kuma có thể monitor cron job hoặc script backup không?

Có. Đó là monitor Push: Uptime Kuma cung cấp cho bạn một URL và bạn curl URL đó ở cuối script để chỉ gửi tín hiệu khi script chạy thành công. Nếu job thất bại hoặc máy chủ ngừng hoạt động, heartbeat sẽ không đến. Bạn sẽ nhận được cảnh báo sau khi hết interval. Đây là cách đáng tin cậy duy nhất để biết một job theo lịch đã thực sự chạy, vì một kiểm tra bên ngoài không thể nhìn vào bên trong job.

Nên chạy Uptime Kuma hay Zabbix?

Uptime Kuma trả lời câu hỏi "service có hoạt động không, nhìn từ bên ngoài, và hệ thống có gửi cảnh báo cho tôi không" trong mười phút, gần như không cần tài nguyên, đồng thời cung cấp status page. Nó không thu thập các metric chuyên sâu như xu hướng CPU, memory và disk, cũng không áp dụng threshold trên toàn bộ fleet. Với nhu cầu đó, một monitoring server Zabbix đầy đủ là công cụ nặng hơn, hoạt động dựa trên agent, và nhiều người chạy cả hai. Vẫn chưa quyết định nên chạy gì? bài tổng hợp về những gì nên self-host trong 2026 sẽ giúp đặt nhu cầu monitoring vào đúng bối cảnh.

#uptime-kuma#giám sát#Docker#tự lưu trữ#status-page