Dealownia
Menu
sprawdzono – jak sprawdzamy oferty

Monitoring małego serwera: co mierzyć i jak ustawić alerty

Dostępność z zewnątrz, dysk, pamięć, certyfikaty, kontenery i kopie zapasowe. Prosty skrypt alertów bez spamu i heartbeat dla zadań cron na VPS.

Serwer i VPSzaktualizowano 8 min czytania

Redakcja serwisu Dealownia · ceny i fakty ze stron dostawców, jak sprawdzamy

Serwer, który zgłasza tylko to, co wymaga reakcji: niedostępną stronę, kończące się miejsce na dysku, brak pamięci, wygasający certyfikat, zatrzymany kontener i kopię zapasową, która się nie wykonała. Alerty przychodzą raz, gdy problem się pojawi, raz, gdy zniknie, i co kilka godzin, jeśli trwa – zamiast co pięć minut. Układ ma trzy warstwy: zewnętrzny monitor dostępności, skrypt sprawdzający zasoby co 5 minut i sygnały od zadań cron do usługi, która alarmuje, gdy sygnał nie przyjdzie.

Czego potrzebujesz

  • VPS z Ubuntu 24.04 LTS albo Debianem 12 lub 13, z nginx i opcjonalnie Dockerem – jak w poradniku od zera.
  • Kanał powiadomień. Skrypt z tego poradnika wysyła alerty na webhook Discorda; jak przerobić go na Slacka, piszemy pod skryptem. Telegram czy e-mail wymagają innego wywołania – najprościej przez Uptime Kumę albo Healthchecks.io, które mają je wbudowane.
  • Darmowe konta w dwóch usługach zewnętrznych: monitorze dostępności (np. UptimeRobot) i usłudze heartbeat (np. Healthchecks.io).

Krok 1. Dostępność z zewnątrz

Pierwszy monitor musi stać poza Twoim serwerem. Narzędzie na tym samym VPS-ie nie zgłosi, że VPS przestał odpowiadać – padnie razem z nim. Darmowy plan UptimeRobot daje 50 monitorów sprawdzanych co 5 minut i według cennika nadaje się także do projektów komercyjnych. Dodaj:

  • stronę główną każdej domeny (typ HTTP(S)),
  • monitor ze słowem kluczowym, jeśli serwer potrafi zwrócić 200 z pustą stroną – np. nazwę serwisu, która musi być w HTML,
  • panel narzędzi, jeśli z nich korzystasz.

Wewnętrzny monitor, np. Uptime Kuma, może sprawdzać częściej i więcej rzeczy, ale to warstwa druga. Przykładowy podział: Kuma sprawdza strony co minutę, a UptimeRobot z zewnątrz pilnuje stron i samego panelu Kumy.

Krok 2. Co mierzyć na serwerze

Co Próg alertu Dlaczego
Zajętość dysku / > 85% pełny dysk zatrzymuje bazy, logi i build
Wolne i-węzły > 85% zajętych miliony małych plików kończą dysk przy wolnym miejscu
Pamięć zabójstwa OOM w logu jądra proces znika bez śladu w aplikacji
nginx systemctl is-active ≠ active strony nie działają
Kontenery nie running albo unhealthy narzędzie leży, choć serwer działa
Certyfikaty < 14 dni do wygaśnięcia odnawianie zawiodło po cichu

Obciążenia procesora na małym serwerze zwykle nie warto alarmować: skok przy budowaniu strony czy kopii jest normalny. Patrz na nie, gdy szukasz przyczyny problemu. Pamięć też lepiej oceniać po zabójstwach OOM niż po procencie zajętości – Linux celowo zajmuje wolną pamięć na pamięć podręczną dysku.

Progi z tabeli to punkt wyjścia. Jeśli dysk rośnie powoli i przewidywalnie, 85% daje Ci kilka dni na reakcję; jeśli na serwerze lądują duże pliki (kopie, obrazy Dockera), obniż próg albo dodaj drugi, ostrzejszy. Na małym VPS-ie z 1–2 GB RAM plik swap nie zastąpi pamięci, ale daje czas, zanim jądro zacznie zabijać procesy.

Certyfikaty pilnuj sam: Let’s Encrypt 4 czerwca 2025 roku przestał wysyłać e-maile o wygasających certyfikatach.

Krok 3. Skrypt sprawdzający

/usr/local/sbin/zdrowie.sh:

bash
#!/usr/bin/env bash
set -uo pipefail
WEBHOOK_FILE=/etc/zdrowie/webhook   # adres webhooka, plik 600
STATE=/var/lib/zdrowie/stan
mkdir -p "$(dirname "$STATE")"
problemy=()

dysk=$(df --output=pcent / | tail -1 | tr -dc '0-9')
[ "$dysk" -gt 85 ] && problemy+=("dysk / zajęty w ${dysk}%")

inody=$(df --output=ipcent / | tail -1 | tr -dc '0-9')
[ "${inody:-0}" -gt 85 ] && problemy+=("i-węzły / zajęte w ${inody}%")

oom=$(journalctl -k --since "-5min" --no-pager | grep -ci 'out of memory')
[ "$oom" -gt 0 ] && problemy+=("OOM: jądro zabiło proces ($oom)")

systemctl is-active --quiet nginx || problemy+=("nginx nie działa")

if command -v docker >/dev/null; then
  while read -r nazwa stan; do
    [ "$stan" = running ] || problemy+=("kontener $nazwa: $stan")
  done < <(docker ps -a --format '{{.Names}} {{.State}}')
  for nazwa in $(docker ps --filter health=unhealthy --format '{{.Names}}'); do
    problemy+=("kontener $nazwa: unhealthy")
  done
fi

for cert in /etc/letsencrypt/live/*/fullchain.pem; do
  [ -e "$cert" ] || continue
  openssl x509 -checkend 1209600 -noout -in "$cert" >/dev/null \
    || problemy+=("certyfikat $(basename "$(dirname "$cert")") wygasa w ciągu 14 dni")
done

teraz=$(printf '%s\n' "${problemy[@]:-}" | sort)
poprzednio=$(sed -n '2,$p' "$STATE" 2>/dev/null || true)
ostatni=$(head -1 "$STATE" 2>/dev/null || echo 0)

wyslij() {
  local tekst
  tekst=$(printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' | awk '{printf "%s\\n", $0}')
  curl -fsS -m 10 -H 'Content-Type: application/json' \
    -d "{\"content\":\"$tekst\"}" "$(cat "$WEBHOOK_FILE")" >/dev/null
}

if [ "$teraz" != "$poprzednio" ]; then
  if [ -n "$teraz" ]; then wyslij "$(hostname): problem
$teraz"; else wyslij "$(hostname): wszystko znów OK"; fi
  ostatni=$(date +%s)
elif [ -n "$teraz" ] && [ $(( $(date +%s) - ostatni )) -gt 21600 ]; then
  wyslij "$(hostname): problem nadal trwa
$teraz"
  ostatni=$(date +%s)
fi
printf '%s\n%s\n' "$ostatni" "$teraz" > "$STATE"
echo "${teraz:-OK}"

Skrypt zgłasza każdy zatrzymany kontener, więc usuń te, których nie używasz (docker container prune). Webhook Discorda przyjmuje JSON z polem content. Przychodzący webhook Slacka oczekuje pola text – przy Slacku zamień w skrypcie content na text i wklej do pliku adres webhooka Slacka. Telegramu ani e-maila ten skrypt nie obsługuje. Adres webhooka trzymaj w osobnym pliku z prawami 600 – kto go zna, może pisać na Twój kanał.

bash
sudo mkdir -p /etc/zdrowie && sudo chmod 700 /etc/zdrowie
sudo install -m 600 /dev/null /etc/zdrowie/webhook && sudo nano /etc/zdrowie/webhook
sudo chmod 700 /usr/local/sbin/zdrowie.sh
sudo /usr/local/sbin/zdrowie.sh

Adres webhooka (https://discord.com/api/webhooks/…) wklej w edytorze, a nie przez echo – wpisany w poleceniu zostałby w historii powłoki. Pierwsze polecenie tworzy plik z prawami 600 jeszcze przed wpisaniem sekretu.

Najważniejsza część to porównanie z poprzednim stanem: wiadomość idzie, gdy lista problemów się zmieni, a przy trwającym problemie – co 6 godzin (21600 s). Dzięki temu kanał alertów nie zamienia się w szum, który przestaje się czytać.

Alerty kieruj na osobny kanał, na którym nie ma nic poza nimi, i włącz dla niego powiadomienia w telefonie. Raporty typu „wdrożenie się udało” czy cotygodniowe podsumowania wysyłaj gdzie indziej. Jeśli na kanale alertów pojawia się coś, na co nie reagujesz, popraw próg albo usuń sprawdzenie, zamiast uczyć się ignorować powiadomienia.

Krok 4. Uruchamianie co 5 minut

/etc/cron.d/zdrowie:

tekst
*/5 * * * * root /usr/local/sbin/zdrowie.sh >>/var/log/zdrowie.log 2>&1

Ostatnia linia pliku musi kończyć się znakiem nowej linii, a plik nie może mieć końców linii CRLF (zapisany na Windowsie) – cron wtedy go pomija, a może też zatrzymać pozostałe zadania (więcej w poradniku o kopiach). Test alertu: zatrzymaj na chwilę jakiś kontener (docker stop nazwa), uruchom skrypt, sprawdź wiadomość, uruchom kontener i skrypt jeszcze raz – powinno przyjść „wszystko znów OK”.

Krok 5. Martwy przełącznik dla zadań cron

Kopia zapasowa, która się nie uruchomiła, nie wygeneruje błędu – po prostu nic się nie stanie. Dlatego zadanie po zakończeniu wysyła sygnał do usługi zewnętrznej, a usługa alarmuje, gdy sygnał nie przyjdzie w ustalonym czasie. To działa także wtedy, gdy cały serwer leży.

W Healthchecks.io (plan darmowy: 20 zadań) utwórz check z harmonogramem zgodnym z cronem i okresem karencji, np. 30 3 * * *, strefa Europe/Warsaw, karencja 2 godziny. Wywołanie w cronie:

tekst
30 3 * * * root curl -fsS -m 10 --retry 3 -o /dev/null https://hc-ping.com/TWÓJ-UUID/start; /usr/local/sbin/kopia.sh >>/var/log/kopia.log 2>&1; curl -fsS -m 10 --retry 3 -o /dev/null https://hc-ping.com/TWÓJ-UUID/$?
  • /start zaznacza początek – usługa zmierzy czas trwania i zaalarmuje, gdy zadanie utknie,
  • /$? wysyła kod wyjścia: 0 to sukces, każda inna wartość – błąd.

Tak samo podepnij nocne budowanie stron, odnawianie certyfikatów, jeśli robisz je własnym skryptem, i wysyłkę kopii poza serwer. Uptime Kuma ma podobny monitor typu Push, ale stoi na tym samym serwerze, więc nie wykryje awarii całej maszyny.

Najczęstsze problemy

  • Alert co 5 minut o tym samym. Skrypt nie zapisuje stanu albo nie ma uprawnień do pliku stanu. Sprawdź /var/lib/zdrowie/stan i log w /var/log/zdrowie.log.
  • Fałszywe alarmy „strona nie działa” z monitora zewnętrznego. Pojedyncze przekroczenia czasu w sieci. Ustaw w monitorze ponowienie przed alertem albo alarm dopiero po dwóch nieudanych sprawdzeniach.
  • Healthchecks zgłasza brak sygnału, choć kopia się wykonała. Harmonogram w usłudze w innej strefie czasowej niż serwer albo za krótka karencja. Ustaw strefę serwera (timedatectl) i karencję dłuższą niż najdłuższy przebieg.
  • Dysk zapełniają logi kontenerów. Domyślny sterownik json-file nie ma limitu. Ustaw max-size i max-file w /etc/docker/daemon.json, jak w poradniku od zera.
  • Alerty przestały przychodzić. Webhook usunięty albo kanał zarchiwizowany, a curl w skrypcie kończy się błędem bez śladu. Raz w miesiącu wywołaj test z kroku 4.

Co dalej

Ustaw kopie zapasowe poza serwerem z sygnałem z kroku 5 i przejrzyj listę kontrolną zabezpieczeń. Jeśli chcesz wykresy i stronę statusu, postaw Uptime Kuma. Serwer z zapasem na narzędzia porównasz w rankingu VPS.

Powiązane poradniki