Cách cài Uptime Kuma bằng Docker
Hướng dẫn chạy Uptime Kuma trong Docker để giám sát website, port và DNS. Bạn sẽ biết cách cấu hình alert qua Telegram và tạo status page trên VPS riêng.
Những gì bạn đang xây dựng
Một container nhỏ gọn duy nhất để giám sát các server và website khác của bạn từ bên ngoài. Nó sẽ thông báo ngay lập tức qua email, Telegram, Discord hoặc webhook khi một dịch vụ ngừng phản hồi. Uptime Kuma là một tiến trình Node chạy kèm với một file SQLite, nên nó tiêu tốn rất ít tài nguyên, chỉ khoảng 256-512 MB RAM. Nó cung cấp cho bạn một dashboard trực tiếp, biểu đồ lịch sử và một trang trạng thái (status page) công khai. Việc cài đặt chỉ mất mười dòng với file Compose; phần quan trọng nhất là bạn chạy nó ở đâu và bạn đã test thử xem alert có hoạt động không chưa, vì một bộ giám sát mà bạn chưa bao giờ chứng minh được khả năng gửi thông báo thì còn tệ hơn là không có gì: nó khiến bạn lầm tưởng mình đã được bảo vệ trong khi thực tế chẳng có gì được giám sát cả.
Chạy bộ giám sát ở nơi mà sự cố không thể chạm tới
Quyết định này quyết định sự thành bại của toàn bộ hệ thống, nên nó phải được thực hiện đầu tiên. Đừng chạy Uptime Kuma trên cùng một máy với những thứ mà nó giám sát. Nếu bộ giám sát nằm trên chính server mà nó đang giám sát, thì khi sự cố xảy ra (như server chết hoặc hết bộ nhớ), bộ giám sát cũng chết theo và bạn sẽ không nhận được bất kỳ thông báo nào: sự im lặng từ một bộ giám sát đã chết cũng giống hệt như thông báo "mọi thứ vẫn ổn". Có một cái bẫy tinh vi hơn ngay cả khi server vẫn đang chạy: một bộ giám sát trỏ vào localhost sẽ phải chia sẻ CPU với các workload khác, nên khi CPU tăng cao sẽ khiến việc kiểm tra bị timeout và báo trạng thái down giả, trong khi người dùng thực tế vẫn truy cập bình thường.
Vì vậy, hãy chạy Uptime Kuma trên một VPS khác với VPS mà nó giám sát, lý tưởng nhất là ở một nhà cung cấp hoặc vùng (region) khác, kết nối tới các dịch vụ của bạn theo cách mà người dùng thực hiện: qua internet công cộng, bằng hostname. Một instance giá rẻ là đủ, và một VPS giám sát nhỏ có thể theo dõi tất cả các server của bạn. Để kiểm tra xem chính Kuma có bị chết hay không, hãy thêm một lệnh push heartbeat từ một cron ở nơi khác.
Điều kiện tiên quyết và cấu hình tài nguyên
- Một VPS Ubuntu 24.04 mới đã cài Docker Engine và plugin Compose v2, cài từ repository apt chính thức của Docker, chứ không phải package
docker.iocủa distro vì nó thường bị cũ. - 256 MB RAM đủ để chạy một vài bộ giám sát; 512 MB đến 1 GB là thoải mái cho hàng chục bộ giám sát cộng với reverse proxy, và CPU sẽ gần như ở trạng thái idle giữa các lần kiểm tra.
- Một domain và một bản ghi DNS
A(ví dụstatus.example.comtrỏ về VPS), chỉ khi bạn muốn dùng TLS và có trang trạng thái công khai. Một instance riêng tư có thể bỏ qua DNS và dùng VPN hoặc SSH tunnel. - Kết nối mạng outbound tới nơi nhận alert: SMTP tới mail provider của bạn, hoặc HTTPS tới Telegram và Discord.
File 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:Chạy nó và theo dõi quá trình khởi động đầ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-kumaMột quá trình khởi động đúng sẽ log Listening on 3001 và sau đó chạy im lặng. Có ba điểm trong file đó là có chủ đích.
127.0.0.1:3001:3001, chứ không phải 3001:3001. Docker publish các port bằng các rule DNAT được đánh giá trước khi ufw kịp thấy packet, nên nếu chỉ dùng 3001:3001 thì dashboard của bạn sẽ bị lộ lên internet bất kể firewall của bạn thế nào. Binding vào loopback sẽ giữ nó ở chế độ riêng tư, chỉ có reverse proxy là được expose; một instance riêng tư có thể bỏ qua proxy và truy cập 3001 qua một WireGuard VPN tự host thay thế.
Một named volume tại /app/data. Mọi thứ Uptime Kuma ghi nhớ, từ database SQLite, các bộ giám sát, cài đặt thông báo cho đến logo trang trạng thái, đều nằm ở đó. Nếu mất nó, bạn sẽ phải bắt đầu lại từ màn hình admin trống rỗng; đây là thứ duy nhất bạn bắt buộc phải backup.
Image được cố định ở một major tag, :2. Đó là dòng stable hiện tại; hãy kiểm tra Docker Hub để tìm major version mới nhất trước khi copy, và đừng bao giờ dùng các tag thay đổi liên tục như latest mà dự án đã loại bỏ. Việc nhảy version (major-version jump) trên image này là một quá trình migration database một chiều mà bạn nên chủ động thực hiện, chứ không nên để nó xảy ra ngẫu nhiên khi bạn thực hiện lệnh pull định kỳ.
Một lưu ý: /app/data phải nằm trên một filesystem hỗ trợ POSIX file locks. Một Docker volume cục bộ là ổn; trên NFS, database SQLite sẽ bị corrupt và bạn sẽ gặp lỗi SQLITE_BUSY và database disk image is malformed, vì vậy đừng bao giờ dùng network share.
Chạy lần đầu: tạo tài khoản admin
Truy cập vào instance thông qua proxy của bạn tại https://status.example.com, hoặc qua SSH tunnel: chạy ssh -L 3001:127.0.0.1:3001 user@your-vps và mở http://localhost:3001. Trang đầu tiên là form thiết lập username và password cho admin; không có login mặc định. Hãy chọn một password thật mạnh: dashboard này có thể thấy các địa chỉ nội bộ và token của mọi thứ bạn giám sát. Nếu quên sau này? Hãy reset từ host, chứ không phải từ trình duyệt:
sudo docker compose exec uptime-kuma npm run reset-passwordThêm các kênh thông báo trước, và test chúng
Hãy thiết lập alert trước khi thêm các bộ giám sát, để bạn có thể gắn một channel vào ngay khi tạo mỗi bộ giám sát. Vào Settings rồi Notifications rồi Setup Notification, và dùng nút Test của mỗi channel để xác nhận tin nhắn đã đến, vì một thông báo chưa được test là lý do phổ biến thứ hai khiến hệ thống thất bại một cách âm thầm.
Email (SMTP). Điền host, port, encryption, username, password, From và To. Hai tổ hợp hoạt động tốt là 465 với "Secure" là TLS/SSL, hoặc 587 với STARTTLS. Đối với Gmail và hầu hết các provider có 2FA, bạn phải tạo app password; dùng password tài khoản bình thường sẽ trả về lỗi Error: Invalid login: 535-5.7.8 Username and Password not accepted.
Telegram. Nhắn tin cho @BotFather, gửi /newbot, copy bot token. Để lấy chat ID, hãy nhắn tin cho bot mới một lần, mở https://api.telegram.org/bot<token>/getUpdates, và đọc chat.id từ JSON. Một bot mà bạn chưa bao giờ nhắn tin trước đó sẽ có getUpdates trống và không biết gửi tin về đâu.
Discord. Trong channel, mở Edit Channel rồi Integrations rồi Webhooks rồi New Webhook, copy URL, và dán nó vào phần thông báo Discord.
Generic webhook. Cho bất kỳ thứ gì khác, như Slack incoming webhook, một endpoint tùy chỉnh, hoặc hook nhà thông minh; kiểu Webhook sẽ POST một JSON payload tới URL bạn cung cấp, và tích hợp Apprise có sẵn đã bao quát hầu hết hơn 90 dịch vụ khác trong danh sách.
Thêm các bộ giám sát, từng loại một
Click Add New Monitor, chọn một loại, và thiết lập Friendly Name, Check Interval (60 giây là hợp lý), Retries (số lần lỗi liên tiếp trước khi báo "down"; nên để 2 hoặc 3 để tránh báo động giả khi chỉ mất một packet), và các thông báo cần gửi. Các loại bạn sẽ dùng:
- HTTP(s). Một URL đầy đủ. Trạng thái "up" nghĩa là nhận được status code hợp lệ (mặc định là 200-299; hãy mở rộng trong phần Accepted Status Codes nếu
301hoặc401là bình thường với bạn). Đây là công cụ chính cho website và API. - HTTP(s) - Keyword. Cùng một request đó, nhưng trạng thái "up" còn yêu cầu phải có một chuỗi ký tự nhất định (hoặc không có nếu không chọn Invert) trong body. Cách này giúp bắt được trường hợp website trả về
200 OKtrong khi vẫn hiển thị dòng "Error establishing a database connection", điều mà một check HTTP thông thường sẽ coi là vẫn ổn. - TCP Port. Một kết nối TCP thuần tới host và port, dùng cho các dịch vụ không phải HTTP: SSH trên port 22, Postgres trên 5432, server SMTP trên 25, hoặc game server.
- Ping. ICMP echo: kiểm tra khả năng kết nối và độ trễ. Nhưng nhiều mạng và cloud firewall sẽ chặn ICMP, nên một bộ giám sát ping báo đỏ có thể là do "host chết" hoặc do "provider chặn ping"; hãy xác nhận lại bằng một bộ giám sát TCP.
- DNS. Resolve một bản ghi (A, AAAA, MX, TXT, v.v.) tới một resolver bạn chỉ định, và có thể kiểm tra kết quả trả về, giúp phát hiện sớm sự cố từ nhà đăng ký domain hoặc DNS.
- Push. Bộ giám sát từ bên trong đẩy ra ngoài, sẽ nói ở phần tiếp theo.
Giám sát một cron job bằng bộ giám sát push (heartbeat)
Mọi bộ giám sát ở trên đều kết nối từ bên ngoài vào dịch vụ của bạn. Bộ giám sát push hoạt động ngược lại: Uptime Kuma chờ đợi, và job của bạn sẽ gọi nó để báo "Tôi đã chạy xong". Đây là cách duy nhất để giám sát một bản backup hoặc cron một cách trung thực: một check HTTP chỉ biết URL có phản hồi hay không, nhưng chỉ có chính job đó mới biết nó đã hoàn thành hay chưa.
Tạo một monitor kiểu Push. Uptime Kuma sẽ tạo ra một URL duy nhất như:
https://status.example.com/api/push/j8Xa2Kd9Qe?status=up&msg=OK&ping=Đặt Heartbeat Interval bằng tần suất chạy của job, cộng thêm một chút thời gian dự phòng. Sau đó thêm một dòng vào cuối script để nó chỉ gửi tín hiệu khi 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 thất bại, set -e sẽ dừng trước khi thực hiện curl; nếu server chết, lệnh cũng không chạy được. Dù thế nào thì heartbeat cũng sẽ dừng lại, và sau khi hết khoảng thời gian (interval + retries), 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 như một secret: bất kỳ ai có nó đều có thể giả mạo tín hiệu heartbeat.
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ị các dịch vụ nào đang chạy và lịch sử gần đây, mà không làm 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, ví dụ /status/main), kéo các monitor bạn muốn vào các group như "Websites" và "APIs", thêm logo và mô tả ngắn, rồi nhấn Save. Bạn cũng có thể bind trang này với domain riêng để status.example.com phục vụ nó trực tiếp.
Hai lưu ý: chỉ thêm những monitor mà bạn sẵn sàng công khai, vì trang trạng thái sẽ tiết lộ sự tồn tại của dịch vụ và tình trạng hoạt động của nó; và dashboard sẽ nằm sau lớp đăng nhập, trong khi trang trạng thái được thiết kế để công khai và không cần auth.
Đặt sau một reverse proxy có TLS, và lưu ý về websockets
Đối với một instance công khai, hãy đặt một reverse proxy phía trước container đang bind vào loopback để có TLS và hostname. Chi tiết khiến mọi người vấp phải là: UI của Uptime Kuma là một ứng dụng Socket.IO chạy thời gian thực, nên proxy phải hỗ trợ nâng cấp (upgrade) kết nối WebSocket. Nếu thiếu bước này, trang web sẽ load được nhưng không bao giờ kết nối được; dashboard sẽ treo ở trạng thái "Connecting...", các heartbeat trực tiếp không bao giờ cập nhật, và console trình duyệt sẽ báo lỗi WebSocket connection to 'wss://.../socket.io/...' failed.
Cài đặt nginx và certbot, sau đó viết vhost để proxy tới port loopback. Hãy để nó chạy ở port 80 trước và để certbot thêm TLS sau; các vấn đề về challenge, lịch gia hạn và lỗi của nó đã được đề cập trong cài đặt chứng chỉ Let's Encrypt với certbot và nginx.
sudo apt install -y nginx certbot python3-certbot-nginxLưu file này thành /etc/nginx/sites-available/status.example.com; hai dòng WebSocket là quan trọng nhất:
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, test cấu hình, sau đó để certbot ghi đè block để lắng nghe ở port 443, chèn chứng chỉ và thêm redirect HTTP-to-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.comCặp Upgrade và Connection "upgrade" là mấu chốt, và proxy_read_timeout 3600s giúp nginx không ngắt kết nối socket lâu dài; certbot sẽ copy cả hai vào block 443 mà nó tạo ra. Nếu bạn đã chạy nhiều container sau một proxy, routing chúng qua Traefik với automatic TLS cũng làm điều tương tự bằng cách dùng container labels và tự động forward WebSocket upgrades.
Đừng dùng basic-auth cho toàn bộ vhost, vì điều đó sẽ khóa luôn cả trang trạng thái công khai và endpoint /api/push. Hãy giữ tính năng login có sẵn của Uptime Kuma, và thêm fail2ban để theo dõi các lần đăng nhập thất bại lặp lại nếu nó tiếp xúc với internet; nếu dashboard không cần công khai, hãy bỏ qua proxy và truy cập qua VPN.
Giám sát hết hạn chứng chỉ, làm đúng cách
Một monitor HTTP(s) cũng có thể cảnh báo trước khi chứng chỉ TLS hết hạn: tích vào Certificate Expiry Notification và Uptime Kuma sẽ cảnh báo trước một số ngày nhất định. Có hai lỗi khiến việc này không hoạt động. Hãy giám sát bằng hostname, không phải IP, nếu không một request không có SNI sẽ lấy chứng chỉ mặc định của server và bạn sẽ thấy lỗi Hostname/IP does not match certificate's altnames. Và đừng tích vào Ignore TLS/SSL Error trên một monitor mà bạn muốn nhận cảnh báo hết hạn: tùy chọn đó dành cho các host nội bộ tự ký (unable to verify the first certificate, DEPTH_ZERO_SELF_SIGNED_CERT), nhưng nó sẽ khiến Uptime Kuma ngừng kiểm tra chứng chỉ hoàn toàn, bao gồm cả việc kiểm tra hết hạn.
Backup: tất cả nằm trong một thư mục
Vì mọi thứ nằm trong /app/data, nên một bản backup là bản copy của volume đó được thực hiện khi container đã dừng, để đảm bảo file SQLite 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 startTrước tiên hãy xác nhận tên thực của volume bằng docker volume ls | grep kuma, vì Compose sẽ thêm prefix là tên thư mục dự án. Sau đó hãy copy file tarball ra khỏi server, vì một bản backup nằm trên cùng một VPS chỉ là một bản copy, không phải là backup thực sự. Khôi phục (restore) thực hiện 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 lại.
Nâng cấp
Nâng cấp thực chất là một lệnh image pull:
cd /srv/uptime-kuma
sudo docker compose pull
sudo docker compose up -dContainer mới sẽ chạy bất kỳ quá trình migration database nào khi khởi động lần đầu; hãy theo dõi docker compose logs -f. Hãy thực hiện backup ở trên trước khi pull, và hãy giữ ở cùng một major tag: chuyển từ :1 sang :2 là một quá trình migration một chiều, nên hãy backup trước và kiểm tra release notes.
Các lỗi có thể xảy ra, kèm theo các thông báo bạn sẽ thấy
Báo "down" giả trên monitor trỏ vào localhost. Monitor báo đỏ với lỗi timeout of 48000ms exceeded hoặc connect ETIMEDOUT, trong khi dịch vụ vẫn phản hồi từ máy tính của bạn. Nếu nó trỏ vào cùng một host mà Uptime Kuma đang chạy, thì việc CPU hoặc bộ nhớ tăng cao đã làm nghẽn quá trình kiểm tra, chứ không phải do target. Hãy chuyển monitor sang một VPS riêng và trỏ vào hostname công khai.
connect ECONNREFUSED 127.0.0.1:443 (hoặc bất kỳ port nào). Không có gì đang lắng nghe ở port đó: có thể dịch vụ đã chết, hoặc bạn đang giám sát localhost từ bên trong container, nơi mà 127.0.0.1 chính là container, chứ không phải server của bạn. Hãy giám sát hostname công khai, đừng dùng loopback.
Invalid login: 535-5.7.8 Username and Password not accepted khi test email. Thông tin SMTP sai, hoặc provider yêu cầu app-specific password thay vì password tài khoản. Hãy tạo app password và dán nó vào.
connect ETIMEDOUT hoặc queryA ETIMEDOUT <host> khi test email. Sai port, hoặc provider chặn SMTP outbound. Hãy xác nhận 465 hoặc 587 khớp với cài đặt Secure/STARTTLS, và test từ host bằng nc -vz smtp.example.com 587. Nhiều provider chặn outbound 25 và một số chặn các port gửi thư cho đến khi bạn yêu cầu.
self signed certificate hoặc unable to verify the first certificate khi test email. Server SMTP của bạn dùng chứng chỉ mà Node không tin tưởng; hãy sửa chứng chỉ của mail server thay vì cố gắng bỏ qua nó.
Dashboard bị treo ở "Connecting...", console báo lỗi WebSocket connection ... failed. Reverse proxy chưa nâng cấp kết nối WebSocket. Hãy thêm các header Upgrade và Connection "upgrade" trên nginx, hoặc dùng một proxy hỗ trợ sẵn như Traefik hoặc Caddy. HTML vẫn load được vì đó là HTTP GET bình thường; chỉ có socket trực tiếp mới cần upgrade.
Monitor hết hạn cert không cảnh báo, hoặc cảnh báo sai. Có thể bạn đã tích Ignore TLS/SSL Error (làm vô hiệu hóa việc check cert), hoặc monitor đang trỏ vào một IP và lấy nhầm chứng chỉ do thiếu SNI, dẫn đến lỗi Hostname/IP does not match certificate's altnames. Hãy bỏ tích ignore và giám sát bằng hostname.
SQLITE_BUSY hoặc database disk image is malformed trong logs. Volume /app/data nằm trên một filesystem không hỗ trợ file locking (thường là NFS); hãy chuyển nó sang một Docker volume cục bộ và khôi phục từ bản backup.
FAQ
Tôi nên chạy uptime monitor ở đâu?
Trên một server khác với các server mà nó giám sát, lý tưởng nhất là ở một nhà cung cấp hoặc vùng khác, kết nối tới chúng bằng hostname qua internet công cộng giống như cách người dùng làm. Nếu bộ giám sát dùng chung máy với các target, sự cố làm sập server cũng sẽ làm sập luôn bộ giám sát, và một host bị quá tải sẽ báo "down" giả cho các dịch vụ vẫn đang chạy bình thường. Một VPS riêng nhỏ gọn sẽ tránh được cả hai vấn đề này.
Làm sao để nhận alert qua Telegram hoặc email?
Thêm channel trong phần Settings then Notifications, sau đó gắn nó vào từng monitor. Với Telegram, hãy tạo bot bằng @BotFather và đọc chat.id từ https://api.telegram.org/bot<token>/getUpdates; với email, hãy dùng 465 cho SSL hoặc 587 cho STARTTLS kèm app password nếu provider của bạn dùng 2FA. Hãy nhấn Test để xác nhận tin nhắn đã đến trước khi tin tưởng hoàn toàn vào nó.
Uptime Kuma có thể giám sát một cron job hoặc script backup không?
Có, đó chính là monitor kiểu Push: Uptime Kuma cung cấp cho bạn một URL và bạn curl nó ở cuối script để nó chỉ gửi tín hiệu khi thành công. Nếu job thất bại hoặc server chết, heartbeat sẽ không bao giờ tới, và bạn sẽ nhận được cảnh báo sau khi hết khoảng thời gian chờ. Đây là cách duy nhất để biết một job lập lịch thực sự đã chạy hay chưa, vì một check từ bên ngoài không thể nhìn thấy bên trong job đó.
Uptime Kuma vs Zabbix, tôi nên dùng cái nào?
Uptime Kuma trả lời câu hỏi "nó có đang chạy không, từ bên ngoài, và nó có báo cho tôi không" chỉ trong 10 phút với tài nguyên cực thấp, kèm theo một trang trạng thái. Nó không thu thập các chỉ số sâu như xu hướng CPU, bộ nhớ, đĩa cứng hay các ngưỡng (threshold) trên toàn hệ thống; để làm việc đó, một server giám sát Zabbix đầy đủ là công cụ nặng hơn và dựa trên agent, và nhiều người dùng cả hai. Nếu bạn vẫn đang phân vân, tổng hợp các dịch vụ tự host năm 2026 của chúng tôi sẽ giúp bạn có cái nhìn tổng quan hơn.