Przejdź do treści
Lekcja 86 z 117

Kurs › Moduł 9 › Lekcja 86

Czas i język systemu

⏱ ok. 12 min czytania· moduł 9: Użytkownicy i bezpieczeństwo

Po tej lekcji: kursant ustawia strefę czasową i synchronizację zegara (timedatectl, chrony), czyta chronyc tracking i chronyc sources, rozumie różnicę między zegarem systemowym a sprzętowym (hwclock) oraz ustawia język i format danych systemu (localectl, locale, LANG), wiedząc, jak to wpływa na skrypty.

Wstęp

Awaria. Aplikacja na jednym serwerze, baza na drugim. Porównujesz logi, żeby zobaczyć, co było pierwsze. I nic się nie składa: według logów baza odpowiedziała na zapytanie siedem minut przed tym, zanim aplikacja je wysłała. Albo baza ma moce jasnowidzenia, albo zegary się rozjechały.

Zły zegar to nie kosmetyka. Certyfikaty z poprzedniej lekcji mają daty ważności. Jak zegar jest do tyłu, certyfikat wygląda na „jeszcze nieważny”. Jak do przodu, na „już wygasły”. Logowanie do domeny Active Directory z lekcji 81 opiera się na Kerberosie, który nie wpuści nikogo, kto ma zegar rozjechany o kilka minut. Dziś ustawiamy czas raz, porządnie, i przy okazji język systemu, który potrafi po cichu popsuć skrypty.

Dwa zegary w jednym komputerze

W komputerze są dwa zegary. Pierwszy to zegarek na ręce: zegar sprzętowy, RTC (Real Time Clock). Mała kostka na płycie głównej z baterią, chodzi nawet przy wyłączonym komputerze. Drugi to zegar ścienny w biurze: zegar systemowy, który prowadzi jądro. Przy starcie system patrzy na zegarek na ręce i ustawia według niego zegar ścienny. Potem już każdy chodzi swoim tempem i oba się spieszą albo spóźniają. Zwykły kwarc myli się o kilka sekund dziennie.

Dlatego jest trzecia rzecz: NTP, Network Time Protocol. To dzwonienie na zegarynkę. System co jakiś czas pyta serwery czasu w internecie, która jest godzina, i delikatnie poprawia swój zegar.

Zegar sprzętowy podglądasz przez hwclock:

sudo hwclock --show       # co mówi zegarek na ręce
sudo hwclock --systohc    # przestaw zegarek na ręce według ściennego
sudo hwclock --hctosys    # przestaw ścienny według zegarka na ręce

systohc czytaj „system to hardware clock”, hctosys odwrotnie. Na maszynie wirtualnej albo w kontenerze często zobaczysz tylko:

hwclock: Cannot access the Hardware Clock via any known method.

I to normalne, maszyna nie ma własnej kostki zegara albo nie wolno jej jej dotykać. Wtedy czasem rządzi gospodarz.

Strefa czasowa: timedatectl

Komputer w środku liczy czas jednakowo dla całego świata: w UTC, czasie uniwersalnym, bez zmian na letni i zimowy. Strefa czasowa to tylko okulary, przez które ten czas wyświetla. Ten sam moment wygląda tak:

TZ=UTC date -d @1793750400
TZ=Europe/Warsaw date -d @1793750400
TZ=America/New_York date -d @1793750400
TZ=Asia/Tokyo date -d @1793750400
Wed Nov  4 00:00:00 UTC 2026
Wed Nov  4 01:00:00 CET 2026
Tue Nov  3 19:00:00 EST 2026
Wed Nov  4 09:00:00 JST 2026

Jedna liczba sekund od 1970 roku, cztery różne godziny na ścianie. Zmienna TZ zmienia okulary tylko dla jednej komendy. Dla całego systemu służy timedatectl:

timedatectl
timedatectl list-timezones | grep Europe/W
sudo timedatectl set-timezone Europe/Warsaw
               Local time: Tue 2026-11-03 22:15:51 CET
           Universal time: Tue 2026-11-03 21:15:51 UTC
                 RTC time: Tue 2026-11-03 21:15:50
                Time zone: Europe/Warsaw (CET, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

(Układ pól jak w dokumentacji Red Hata, u Ciebie będą inne godziny.) Czytamy: czas lokalny, czas uniwersalny, czas zegarka na ręce, strefa. System clock synchronized: yes znaczy, że zegarynka odpowiada. NTP service: active, że ktoś do niej dzwoni. A RTC in local TZ: no to dobra wiadomość: zegar sprzętowy trzyma czas w UTC. Tak ma być na serwerze.

Pod spodem set-timezone robi jedną prostą rzecz: przestawia dowiązanie /etc/localtime na plik strefy z /usr/share/zoneinfo:

ls -l /etc/localtime
lrwxrwxrwx 1 root root 35 ... /etc/localtime -> ../usr/share/zoneinfo/Europe/Warsaw

[Ubuntu] Na Ubuntu jest jeszcze tekstowy /etc/timezone i kreator sudo dpkg-reconfigure tzdata, ale timedatectl załatwia wszystko.

Czy serwery trzymać w czasie lokalnym, czy w UTC? W wielu firmach serwery chodzą w UTC, żeby logi z Warszawy, Dublina i Singapuru dało się położyć obok siebie. Jedno jest pewne: wszystkie serwery jednego systemu w tej samej strefie. Inaczej wracamy do bazy-jasnowidza z haka.

Zmiana czasu: zegar, który przeskakuje

Strefa Europe/Warsaw zna nasze zmiany czasu. Popatrz, co się dzieje w nocy 29 marca 2026:

zdump -v Europe/Warsaw | grep 2026
Europe/Warsaw  Sun Mar 29 00:59:59 2026 UT = Sun Mar 29 01:59:59 2026 CET isdst=0 gmtoff=3600
Europe/Warsaw  Sun Mar 29 01:00:00 2026 UT = Sun Mar 29 03:00:00 2026 CEST isdst=1 gmtoff=7200
Europe/Warsaw  Sun Oct 25 00:59:59 2026 UT = Sun Oct 25 02:59:59 2026 CEST isdst=1 gmtoff=7200
Europe/Warsaw  Sun Oct 25 01:00:00 2026 UT = Sun Oct 25 02:00:00 2026 CET isdst=0 gmtoff=3600

Po 01:59:59 przychodzi od razu 03:00:00. Godzina 02:30 tej nocy po prostu nie istnieje. A w październiku godzina od 02:00 do 03:00 zdarza się dwa razy. Zapamiętaj to sobie na następna lekcja o cronie. Zadanie ustawione na 02:30 potrafi raz w roku nie odpalić się wcale, a raz w roku się zdublować. Kolejny powód, żeby poważne serwery chodziły w UTC, które nie zna zmiany czasu.

Synchronizacja: chrony

Teraz zegarynka. Na RHEL od wersji 8 i na Ubuntu od 25.10 domyślnie dzwoni do niej chrony. [Ubuntu] Na Ubuntu 24.04 domyślnie pracuje prostszy systemd-timesyncd. Możesz go zamienić na chrony jedną instalacją, pakiety się wykluczają:

sudo apt install chrony          # [Ubuntu], usuwa systemd-timesyncd
sudo dnf install chrony          # [RHEL], zwykle już jest
sudo timedatectl set-ntp true
systemctl status chrony          # [Ubuntu]
systemctl status chronyd         # [RHEL]

Zwróć uwagę na nazwy: na Ubuntu usługa to chrony, na RHEL chronyd. Plik ustawień: /etc/chrony/chrony.conf na Ubuntu, /etc/chrony.conf na RHEL. W środku najważniejsze są linijki z serwerami czasu:

pool ntp.ubuntu.com        iburst maxsources 4
pool 0.ubuntu.pool.ntp.org iburst maxsources 1
...
driftfile /var/lib/chrony/chrony.drift

pool to grupa serwerów pod jedną nazwą, server to jeden konkretny serwer. iburst znaczy „na starcie zapytaj kilka razy szybko”, żeby zegar złapał synchronizację w sekundy, a nie w minuty. W firmie zamiast publicznych serwerów wpisujesz własny serwer czasu albo kontroler domeny. Po zmianie: sudo systemctl restart chronyd (albo chrony na Ubuntu).

I dwie komendy, które musisz znać. Czy zegar jest dobry?

chronyc tracking
Reference ID    : 7F000001 (localhost)
Stratum         : 9
Ref time (UTC)  : Tue Nov 03 21:14:37 2026
System time     : 0.000002805 seconds fast of NTP time
Last offset     : +0.000000076 seconds
RMS offset      : 0.000006855 seconds
Frequency       : 0.014 ppm fast
...
Update interval : 5.1 seconds
Leap status     : Normal

To prawdziwy wynik z mojego laboratorium, w którym jeden chrony udawał serwer czasu dla drugiego. U Ciebie zamiast localhost będzie nazwa albo adres serwera z internetu. Najważniejsze linijki:

  • Reference ID — z kim się synchronizujesz.
  • Stratum — ile pięter od wzorca. Stratum 1 to serwer podłączony bezpośrednio do zegara atomowego albo GPS. Każdy kolejny serwer w łańcuchu dodaje jedno piętro. 9 w laboratorium, w internecie zwykle 2 albo 3.
  • System time — o ile Twój zegar się teraz myli. Tu: niespełna 3 mikrosekundy. Można z tym żyć.
  • Frequency — jak bardzo Twój kwarc się spieszy albo spóźnia, w milionowych częściach. chrony to zapamiętuje w pliku drift i poprawia na bieżąco.
  • Leap status: Normal — synchronizacja działa. Not synchronised znaczy kłopoty.

A z kim rozmawiasz?

chronyc sources -v
MS Name/IP address         Stratum Poll Reach LastRx Last sample               
===============================================================================
^* localhost                     8   0   377     3  +6500ns[+6576ns] +/-   10us

^ to serwer, * to „ten aktualnie wybrany jako najlepszy”. + przy innych to „używany pomocniczo”, ? to „nie odpowiada”. Kolumna Reach to ósemkowy zapis ostatnich ośmiu prób: 377 znaczy, że wszystkie osiem się udało. 0 znaczy, że zegarynka milczy. Wtedy pierwsze podejrzenie: zapora blokuje wychodzący UDP na porcie 123. Opcja -v dopisuje nad tabelą legendę wszystkich znaczków, więc nie musisz tego wkuwać.

chrony poprawia zegar łagodnie: trochę przyspiesza albo zwalnia, zamiast przeskakiwać, żeby czas w logach nie cofał się nagle. Jeśli zegar jest rozjechany o godziny, możesz kazać skoczyć od razu: sudo chronyc makestep.

Język i format: locale

Teraz druga połowa lekcji. Locale to zestaw ustawień regionalnych: język komunikatów, format daty, przecinek czy kropka w liczbach, kolejność sortowania liter. Jak tłumacz z kompletem przyzwyczajeń danego kraju.

locale
locale -a
LANG=pl_PL.UTF-8
LANGUAGE=
LC_CTYPE="pl_PL.UTF-8"
LC_NUMERIC="pl_PL.UTF-8"
...
C
C.utf8
en_US.utf8
pl_PL.utf8

LANG to ustawienie domyślne. Zmienne LC_* nadpisują pojedyncze dziedziny: LC_TIME daty, LC_NUMERIC liczby, LC_COLLATE sortowanie, LC_MESSAGES język komunikatów. A LC_ALL to młotek, który nadpisuje wszystko naraz. Kolejność ważności: LC_ALL, potem konkretne LC_*, na końcu LANG. C (albo POSIX) to locale „gołe”: angielskie komunikaty, kropka w liczbach, sortowanie po kodach znaków.

Popatrz, jak to samo zmienia wynik:

LC_ALL=C date -d @1793750400
LC_ALL=pl_PL.UTF-8 date -d @1793750400 '+%A, %d %B %Y'
LC_ALL=pl_PL.UTF-8 date +%x
LC_ALL=en_US.UTF-8 date +%x
Wed Nov  4 01:00:00 CET 2026
środa, 04 listopada 2026
03.11.2026
11/03/2026

I sortowanie:

LC_ALL=C sort slowa.txt
LC_ALL=pl_PL.UTF-8 sort slowa.txt
Ala lis zebra łoś Żaba
Ala lis łoś zebra Żaba

W C polskie litery lądują na końcu, bo mają dalsze kody. W polskim locale łoś stoi grzecznie po lisie. Jak w słowniku.

Ustawienie dla całego systemu:

localectl status
localectl list-locales
sudo localectl set-locale LANG=pl_PL.UTF-8
sudo localectl set-keymap pl

[RHEL] Locale zapisuje się w /etc/locale.conf, układ klawiatury konsoli w /etc/vconsole.conf. Brakujące języki doinstalowujesz pakietami: sudo dnf install glibc-langpack-pl. [Ubuntu] Locale leży w /etc/default/locale. Brakujące generujesz: sudo locale-gen pl_PL.UTF-8 albo przez kreator sudo dpkg-reconfigure locales.

Na serwerze wiele osób zostawia LANG=en_US.UTF-8 albo C.UTF-8, nawet w Polsce. Powód prosty: komunikat błędu po angielsku wklejasz w wyszukiwarkę i znajdujesz tysiąc odpowiedzi. Po polsku znajdziesz trzy, w tym jedną swoją sprzed roku.

Jak to działa pod spodem

Jądro trzyma czas jako liczbę sekund (i ułamków) od 1 stycznia 1970 UTC, tak zwaną epokę Uniksa. Nic nie wie o strefach. Zamianą na „środa, 01:00 CET” zajmuje się biblioteka C w każdym programie: czyta zmienną TZ, a jak jej nie ma, plik /etc/localtime. Dlatego zmiana strefy działa od razu dla nowych programów, a długo działające usługi czasem trzeba zrestartować, żeby zauważyły.

chronyd wysyła zapytania NTP po UDP na port 123, mierzy, ile trwała podróż pakietu tam i z powrotem, i z tego liczy, o ile myli się lokalny zegar. Poprawkę przekazuje jądru przez wywołanie systemowe, które każe zegarowi chodzić odrobinę szybciej albo wolniej. timedatectl to tylko pilot do usługi systemd-timedated. set-ntp true włącza tę usługę synchronizacji, którą pakiet zgłosił w /usr/lib/systemd/ntp-units.d/. Na Ubuntu z chrony jest tam plik 50-chrony.list z jedną linijką: chrony.service.

Locale też obsługuje biblioteka C. Program na starcie woła setlocale, ta patrzy w LC_ALL, LC_* i LANG i ładuje skompilowane dane z /usr/lib/locale. Jeśli locale nie ma, program dostaje C i ewentualnie narzeka. O tym narzekaniu za chwilę.

Ściąga flag

KomendaFlagaCo robi
timedatectl(bez argumentów)czas, strefa, stan synchronizacji
timedatectllist-timezoneslista stref
timedatectlset-timezone STREFAustawia strefę (/etc/localtime)
timedatectlset-ntp truewłącza synchronizację
timedatectlset-time 'RRRR-MM-DD GG:MM:SS'ręczne ustawienie (tylko przy wyłączonym NTP)
timedatectlset-local-rtc 0RTC w UTC
chronyctrackingjak dobry jest zegar
chronycsources -vserwery czasu z legendą
chronycsourcestatsstatystyki dla źródeł
chronycmakestepnatychmiastowy skok zegara
hwclock--show / --systohc / --hctosysodczyt RTC / RTC wg systemu / system wg RTC
date-d @SEKUNDY, +FORMATzamiana epoki na datę, własny format
zdump-v STREFAprzejścia na czas letni/zimowy
locale-adostępne locale
localectlset-locale LANG=...locale systemu
localectlset-keymap plukład klawiatury konsoli
locale-genpl_PL.UTF-8[Ubuntu] generuje locale

⚠ Typowy błąd

Dwa klasyki, oba z locale.

Pierwszy: łączysz się z Maca po SSH, robisz apt upgrade i dostajesz ścianę ostrzeżeń:

perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
	LANGUAGE = (unset),
	LC_ALL = "de_DE.UTF-8",
...
bash: warning: setlocale: LC_ALL: cannot change locale (de_DE.UTF-8): No such file or directory

Skąd niemieckie locale na polskim serwerze? Klient SSH ma w /etc/ssh/ssh_config linijkę SendEnv LANG LC_*, a serwer Ubuntu w sshd_config ma AcceptEnv LANG LC_*. Twój laptop grzecznie przesyła swoje ustawienia regionalne, a serwer grzecznie je przyjmuje, tylko że takiego locale nie ma. Jak gość, który przychodzi do restauracji z własnym menu w języku, którego nie zna kucharz. Naprawa: wygeneruj to locale na serwerze (locale-gen) albo nie wysyłaj go z laptopa.

Drugi, groźniejszy, bo cichy: skrypt działał latami, ktoś zmienił locale serwera na polskie i nagle sumy się nie zgadzają:

LC_ALL=C seq 0.5 0.5 1.5 | awk '{s+=$1} END{print s}'
LC_ALL=pl_PL.UTF-8 seq 0.5 0.5 1.5 | awk '{s+=$1} END{print s}'
LC_ALL=pl_PL.UTF-8 seq 0.5 0.5 1.5
3
2
0,5
1,0
1,5

Polskie locale mówi „u nas ułamki pisze się z przecinkiem”, więc seq wypisuje 0,5. A awk czyta liczby po swojemu, z kropką, i z 0,5 bierze tylko 0. Wynik 2 zamiast 3, bez żadnego błędu. Księgowa by się nie pomyliła, a skrypt tak. Zasada: skrypt, który przetwarza liczby, daty albo wynik innych programów, ustawia sobie na początku export LC_ALL=C. Wtedy działa tak samo na każdym serwerze, niezależnie od tego, kto i jak go skonfigurował.

✎ Zadanie

Ustaw strefę czasową Europe/Warsaw, włącz synchronizację czasu i sprawdź chronyc tracking. Potem sprawdź komendą openssl, do kiedy jest ważny certyfikat strony google.com.

Pokaż rozwiązanie

Strefa czasowa, synchronizacja czasu i sprawdzenie, czy zegar naprawdę chodzi:

sudo timedatectl set-timezone Europe/Warsaw
sudo timedatectl set-ntp true
timedatectl
chronyc tracking

W timedatectl patrzysz na dwie linijki: Time zone: Europe/Warsaw (CET, +0100) i System clock synchronized: yes. W chronyc tracking najważniejsze jest Leap status : Normal, czyli „mam z kim gadać i wszystko gra”, oraz System time, czyli o ile Twój zegar różni się od wzorca. Jak tam są milisekundy albo mniej, możesz spać spokojnie. Jak widzisz Not synchronised, to chronyc sources pokaże, czy serwer w ogóle widzi jakiekolwiek źródło czasu.

Druga część, data ważności certyfikatu Google:

openssl s_client -connect google.com:443 -servername google.com </dev/null 2>/dev/null \
  | openssl x509 -noout -dates

Dostajesz dwie linijki: notBefore= i notAfter=. Pierwsza mówi, od kiedy certyfikat jest ważny, druga do kiedy. U Google zobaczysz, że certyfikat żyje krótko, kilka miesięcy. Wielkie firmy wymieniają certyfikaty częściej niż niektórzy z nas szczoteczki do zębów. </dev/null jest po to, żeby s_client nie czekał w nieskończoność, aż coś do niego napiszesz.

I zapamiętaj ten moment: sprawdziłeś to ręcznie. A gdyby tak serwer sam sprawdzał to co noc i pisał do Ciebie, jak zostanie mniej niż dwa tygodnie? Do tego potrzebny jest budzik. I o budziku jest ta lekcja.

Co dalej

Zegar chodzi, strefa ustawiona. Czas to wykorzystać. W następnej lekcji zadania o wyznaczonej godzinie: cron, crontab, at, timery systemd i odwieczna zagadka admina: „skrypt ręcznie działa, a z crona nie”.