systemctl reload i systemctl restart kończą się podobnie brzmiącym komunikatem, ale dla klienta, który akurat pobiera plik, różnica jest zero-jedynkowa. Reload Apache zostawił wszystkie połączenia w toku, restart zerwał je po dziewięciu sekundach. Reload nie jest jednak bezpieczny z definicji: w dwóch scenariuszach systemd zgłosił Reloaded, a kilkanaście milisekund później Apache już nie działał.
W tym artykule sprawdzamy na Debianie 12 i 13 oraz Ubuntu 24.04 i 26.04, co systemd wywołuje przy reload Apache i nginx, jak dodać nowy vhost bez przerwy w działaniu, co dzieje się z nawiązanymi połączeniami przy reload i restart, które błędy konfiguracji reload odrzuca, a które kładą serwer, co reload daje przy aktualizacji binarki i jakie mechanizmy systemd warto przy tej okazji znać.
Spis treści
- Środowisko testu: Debian 12 i 13, Ubuntu 24.04 i 26.04 oraz wersje pakietów
- Co robi systemd po poleceniu systemctl reload apache2
- Jak Apache przeładowuje konfigurację: apachectl graceful i sygnał SIGUSR1
- Dodanie nowego VirtualHost w Apache przez systemctl reload
- Kiedy systemctl reload kładzie Apache mimo komunikatu Reloaded
- Co dzieje się z nawiązanymi połączeniami przy systemctl reload i systemctl restart
- Łagodny restart Apache przez drop-in systemd i jego koszt
- Przeładowanie konfiguracji nginx przez systemctl reload: błędy i połączenia w toku
- Aktualizacja binarki Apache i nginx a systemctl reload
- Mniej znane zachowania systemd przy reload i restart
- Częste błędy przy reload i restart Apache oraz nginx
- Checklista: reload i restart serwera WWW
Środowisko testu: Debian 12 i 13, Ubuntu 24.04 i 26.04 oraz wersje pakietów
Wszystkie pomiary pochodzą z czterech maszyn wirtualnych naszego labu, opisanego w artykule Lab na osobnym hoście: od czystego Debiana 13 do jedenastu maszyn z Terraform. Na każdej zainstalowaliśmy Apache z pakietów dystrybucji w konfiguracji domyślnej, a obok niego nginx na porcie 8080. Debiany w labie to obrazy chmurowe (jądro -cloud-amd64). Tak wyglądały wersje pakietów w dniu testów, 24 września 2026:
| System i jądro | systemd | apache2 | nginx |
|---|---|---|---|
| Debian 12.15, 6.1.0-53-cloud-amd64 | 252.39-1~deb12u2 | 2.4.68-1~deb12u1 | 1.22.1-9+deb12u10 |
| Debian 13.7, 6.12.107+deb13-cloud-amd64 | 257.13-1~deb13u1 | 2.4.68-1~deb13u1 | 1.26.3-3+deb13u9 |
| Ubuntu 24.04.5, 6.8.0-142-generic | 255.4-1ubuntu8.17 | 2.4.58-1ubuntu8.15 | 1.24.0-2ubuntu7.18 |
| Ubuntu 26.04.1, 7.0.0-34-generic | 259.5-0ubuntu3.4 | 2.4.66-2ubuntu2.4 | 1.28.3-2ubuntu1.11 |
Narzędzia, których zachowanie opisujemy albo których używaliśmy do pomiarów:
| System | needrestart | init-system-helpers | logrotate | curl | strace |
|---|---|---|---|---|---|
| Debian 12.15 | 3.6-4+deb12u3 | 1.65.2+deb12u1 | niezainstalowany | 7.88.1-10+deb12u15 | 6.1-0.1 |
| Debian 13.7 | 3.11-1 | 1.69~deb13u1 | niezainstalowany | 8.14.1-2+deb13u5 | 6.13+ds-1 |
| Ubuntu 24.04.5 | 3.6-7ubuntu4.5 | 1.66ubuntu1 | 3.21.0-2build1 | 8.5.0-2ubuntu10.13 | 6.8-0ubuntu2 |
| Ubuntu 26.04.1 | 3.11-1ubuntu2 | 1.69 | 3.22.0-1build1 | 8.18.0-1ubuntu2.5 | 6.19+ds-0ubuntu5 |
Dokumentację cytujemy w wersjach aktualnych na 24 września 2026. Strony systemd na freedesktop.org w wariancie latest opisywały wtedy systemd 262, czyli wersję nowszą niż każda z labu. Dokumentacja Apache dotyczy gałęzi 2.4, której najnowszym wydaniem był 2.4.68 z 8 czerwca 2026. Dokumentacja na nginx.org jest wspólna dla wszystkich wersji, a bieżące wydania to mainline 1.31.6 i stable 1.30.5 z 15 września 2026. Tam, gdzie zachowanie zależy od wersji, rozstrzygał przebieg na labie, a nie dokumentacja.
Apache działał wszędzie z MPM (od angielskiego Multi-Processing Module, czyli modułu, który decyduje, jak serwer rozkłada pracę na procesy i wątki) event. Połączenie w toku symulowaliśmy pobieraniem pliku 100 MiB z ograniczeniem prędkości do 5 MiB/s, czyli transferem trwającym około 20 sekund (curl --limit-rate 5M). Reload albo restart wykonywaliśmy w trzeciej sekundzie pobierania. Każdy test przeszedł na wszystkich czterech systemach, a tam, gdzie wyniki się różniły, piszemy o tym wprost.
Co robi systemd po poleceniu systemctl reload apache2
systemctl reload nie ma własnej logiki przeładowania. Uruchamia polecenie z dyrektywy ExecReload= jednostki i czeka, aż to polecenie się zakończy. Na wszystkich czterech systemach jednostka apache2.service ma te same trzy kluczowe linie:
ExecReload=/usr/sbin/apachectl graceful
ExecStop=/usr/sbin/apachectl graceful-stop
KillMode=mixed
Na tym podobieństwa się kończą. Debian 12, Debian 13 i Ubuntu 24.04 mają identyczną jednostkę z Type=forking i Restart=on-abort. Ubuntu 26.04 (apache2 2.4.66-2ubuntu2.4) ma Type=notify, Restart=on-abnormal, KillSignal=SIGWINCH z komentarzem Send SIGWINCH for graceful stop oraz kilkanaście dyrektyw ograniczających uprawnienia usługi, między innymi ProtectSystem=full, PrivateDevices=yes i InaccessiblePaths=/root. Do KillSignal= wrócimy przy restarcie.
Warto od razu rozdzielić dwa polecenia o mylących nazwach. Dokumentacja systemctl (freedesktop.org, wariant latest, 24 września 2026 odpowiadający systemd 262) używa zresztą Apache jako przykładu, a ten sam akapit jest w stronach podręcznika systemd 252, 255, 257 i 259 zainstalowanych w labie: reload przeładowuje konfigurację samej usługi, czyli pliki w /etc/apache2, a nie plik jednostki systemd. Zmiana w apache2.service albo w drop-inie wymaga systemctl daemon-reload, które z kolei nie dotyka działającego procesu.
Jak Apache przeładowuje konfigurację: apachectl graceful i sygnał SIGUSR1
apachectl na Debianie i Ubuntu to skrypt powłoki. Dla akcji graceful najpierw uruchamia test konfiguracji, a sygnał do procesu głównego wysyła dopiero wtedy, gdy test przejdzie. Tak wygląda ten fragment w Debianie 13 (pakiet apache2 2.4.68-1~deb13u1):
restart|graceful)
if $HTTPD ${APACHE_ARGUMENTS} -t 2> /dev/null ; then
$HTTPD ${APACHE_ARGUMENTS} -k "$ARGV"
else
$HTTPD ${APACHE_ARGUMENTS} -t
fi
Tym sygnałem jest SIGUSR1. Zgodnie z dokumentacją Apache o łagodnym restarcie (gałąź 2.4, najnowsze wydanie 2.4.68) proces główny ponownie czyta konfigurację i otwiera logi, a procesy potomne kończą pracę po obsłużeniu bieżącego żądania i są zastępowane nowymi, już z nową konfiguracją. Na Debianie 12 dziennik systemd i error.log Apache, ułożone według czasu, wyglądały tak:
09:55:43.653 systemd[1]: Reloading apache2.service - The Apache HTTP Server...
09:55:43.691 [mpm_event:notice] [pid 1409] AH00493: SIGUSR1 received. Doing graceful restart
09:55:43.701 [mpm_event:notice] [pid 1409] AH00489: Apache/2.4.68 (Debian) configured -- resuming normal operations
09:55:43.703 systemd[1]: Reloaded apache2.service - The Apache HTTP Server.
PID (od angielskiego process ID, czyli numer procesu) procesu głównego, który systemd pokazuje jako MainPID, był przed reload i po nim ten sam: 1409. Procesy potomne 1411 i 1412 zostały zastąpione przez 1482 i 1483. Samo systemctl reload trwało od 69 do 113 ms, zależnie od systemu.
Ten krótki czas ma swoją przyczynę i swoje skutki. apache2 -k graceful wysyła sygnał i od razu się kończy, nie czekając, aż proces główny przeczyta nową konfigurację. systemd uznaje więc przeładowanie za udane w chwili, gdy sygnał został wysłany. Dokumentacja dyrektywy ExecReload= (systemd 262) nazywa taki reload wprost operacją asynchroniczną, odradza go i zaleca Type=notify-reload albo polecenie, które synchronicznie czeka na koniec przeładowania. Skutki tej asynchroniczności widać w dalszej części.
Dodanie nowego VirtualHost w Apache przez systemctl reload
Dodanie nowego serwisu to scenariusz, do którego reload służy. Na każdym z czterech systemów utworzyliśmy plik /etc/apache2/sites-available/nowy.conf:
<VirtualHost *:80>
ServerName nowy.lab.test
DocumentRoot /var/www/nowy
ErrorLog ${APACHE_LOG_DIR}/nowy-error.log
CustomLog ${APACHE_LOG_DIR}/nowy-access.log combined
</VirtualHost>
i włączyliśmy go:
# a2ensite nowy
Enabling site nowy.
To activate the new configuration, you need to run:
systemctl reload apache2
# systemctl reload apache2
# curl -s -H 'Host: nowy.lab.test' http://127.0.0.1/
to jest nowy vhost
Przed reload to samo zapytanie zwracało domyślną stronę Apache, po reload zwróciło treść nowego serwisu, a MainPID się nie zmienił. Działa również nowy port: po dopisaniu Listen 8081 i zmianie nagłówka na <VirtualHost *:80 *:8081> kolejny reload otworzył gniazdo na porcie 8081, a ss -ltnp pokazał je w starym procesie głównym i w nowych procesach potomnych.
Przy nowych vhostach najczęściej psuje się to, na co vhost wskazuje na dysku. Sprawdziliśmy trzy takie błędy:
| Błąd w vhoście | apache2ctl configtest | Wynik reload |
|---|---|---|
ErrorLog w katalogu, którego nie ma | AH02291: Cannot access directory, test nieudany | odrzucony, serwer działa na starej konfiguracji |
SSLCertificateFile wskazuje nieistniejący plik | AH00526: ... does not exist or is empty, test nieudany | odrzucony, serwer działa na starej konfiguracji |
DocumentRoot, którego nie ma | AH00112: Warning, Syntax OK | udany, vhost odpowiada kodem 404 |
W dwóch pierwszych przypadkach systemctl reload zwrócił kod 1 i komunikat Job for apache2.service failed., a jednostka została w stanie active (running) ze starym MainPID i dalej odpowiadała kodem 200. systemctl status pokazuje wtedy pod zielonym nagłówkiem linię ExecReload=/usr/sbin/apachectl graceful (code=exited, status=1/FAILURE). Łatwo ją przeoczyć, a to jedyny ślad, że nowa konfiguracja nie weszła.
a2enmod po włączeniu modułu zaleca systemctl restart apache2, a a2ensite i a2enconf zalecają reload. Moduł headers włączony przez a2enmod zaczął jednak działać po samym reload: nagłówek ustawiony dyrektywą Header set pojawił się w odpowiedzi, a MainPID się nie zmienił. Przypadek, w którym zalecenie restartu trzeba traktować dosłownie, opisujemy niżej.
Kiedy systemctl reload kładzie Apache mimo komunikatu Reloaded
Test konfiguracji sprawdza składnię i istnienie wskazanych plików, ale nie otwiera gniazd sieciowych. Dopisaliśmy do konfiguracji Listen 22, czyli port zajęty przez sshd:
# apache2ctl configtest
Syntax OK
# systemctl reload apache2; echo rc=$?
rc=0
# systemctl is-active apache2
failed
Tak wyglądał dziennik systemd na Debianie 13:
09:57:35.550 systemd[1]: Reloading apache2.service - The Apache HTTP Server...
09:57:35.682 systemd[1]: Reloaded apache2.service - The Apache HTTP Server.
09:57:35.704 systemd[1]: apache2.service: Main process exited, code=exited, status=1/FAILURE
09:57:35.704 systemd[1]: apache2.service: Failed with result 'exit-code'.
A tak plik error.log Apache z tego samego momentu:
09:57:35.662 AH00493: SIGUSR1 received. Doing graceful restart
(98)Address already in use: AH00072: make_sock: could not bind to address [::]:22
(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:22
09:57:35.684 [mpm_event:alert] no listening sockets available, shutting down
09:57:35.684 AH00019: Unable to open logs, exiting
systemd zgłosił Reloaded, bo apachectl graceful zakończył się sukcesem: test przeszedł, sygnał został wysłany. 22 ms później proces główny Apache zakończył działanie, bo przy ponownym otwieraniu gniazd nie mógł zająć portu. Na czterech systemach odstęp między Reloaded a Main process exited wynosił od 12 do 27 ms, a systemctl reload za każdym razem zwrócił 0. Ostatni komunikat, Unable to open logs, dodatkowo myli trop, bo z logami nie było żadnego problemu.
Usługa nie wstała sama. Według dokumentacji dyrektywy Restart= (systemd 262) wartość on-abort (Debian, Ubuntu 24.04) obejmuje tylko zakończenie przez nieobsłużony sygnał, a on-abnormal (Ubuntu 26.04) sygnały, przekroczenia czasu i watchdog. Wyjście z niezerowym kodem nie mieści się w żadnej z nich, więc Apache leżał do ręcznej interwencji.
Tak samo skończyła się zmiana MPM. Po a2dismod mpm_event i a2enmod mpm_prefork reload zwrócił 0 i Reloaded, a w error.log pojawiło się:
AH00534: apache2: Configuration error: The MPM cannot be changed during restart.
Proces główny zakończył działanie na wszystkich czterech systemach. Dopiero systemctl restart uruchomił Apache z prefork. Tu zalecenie restartu wypisywane przez a2enmod jest dosłownie prawdziwe.
Czy drop-in z Restart=on-failure podnosi Apache po nieudanym reload
Oba scenariusze kończą się niezerowym kodem wyjścia procesu głównego, a Restart=on-abort z pakietu go nie obejmuje. Sprawdziliśmy więc, co zmienia drop-in z Restart=on-failure, który w artykule o usłudze, która nie wstaje po restarcie serwera proponujemy dla innej usługi. Test wykonaliśmy 29 września 2026 roku na Ubuntu 24.04 (apache2 2.4.58-1ubuntu8.15, systemd 255.4-1ubuntu8.17):
# /etc/systemd/system/apache2.service.d/restart.conf
[Unit]
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Restart=on-failure
RestartSec=2s
Po dopisaniu pliku potrzebne jest systemctl daemon-reload, a wartość efektywną sprawdza systemctl show -p Restart apache2. Przy zmianie MPM przez reload proces główny zakończył się tak jak wcześniej, ale systemd zapisał Scheduled restart job, restart counter is at 1 i po około 3 sekundach Apache działał już z mpm_prefork i odpowiadał kodem 200. Przy zajętym porcie restart nic nie daje, bo nowa konfiguracja dalej próbuje zająć ten sam port. Po czterech próbach systemd zapisał Start request repeated too quickly i zostawił jednostkę w stanie failed. Drop-in skraca więc przerwę w jednym z dwóch scenariuszy, ale nie zastępuje sprawdzenia po reload.
Dokumentacja Apache (gałąź 2.4) uprzedza, że test składni nie gwarantuje poprawnego przeładowania, i proponuje próbny start z konta bez uprawnień roota. W automatyzacji prostsze jest sprawdzenie po fakcie: stan usługi z krótkim opóźnieniem i realne zapytanie, a nie tylko kod wyjścia polecenia.
systemctl reload apache2 && sleep 2 \
&& systemctl is-active --quiet apache2 \
&& curl -fsS -o /dev/null http://127.0.0.1/ \
|| echo "Apache nie działa po reload"
Co dzieje się z nawiązanymi połączeniami przy systemctl reload i systemctl restart
Tu reload i restart różnią się najbardziej. W trzeciej sekundzie pobierania pliku 100 MiB wykonaliśmy raz reload, raz restart:
| reload | restart | |
|---|---|---|
Czas systemctl | 0,1 s | 10,6-10,7 s |
| Pobrany plik | 100 MiB, curl kończy się kodem 0 | 69,8-73,9 MiB, curl kod 18 (przerwany transfer) |
MainPID | bez zmian | nowy |
| Nowe połączenia w trakcie | obsługiwane, 0 błędów | odrzucane do startu nowej instancji |
Przy reload proces potomny obsługujący pobieranie nie został wymieniony od razu. ps pokazywał go dalej obok nowego procesu potomnego, a po zakończeniu transferu stary proces zniknął sam. Nowe połączenia sprawdziliśmy osobno: w trakcie dziesięciu kolejnych reloadów, wykonywanych co 0,3 s, wysłaliśmy od 269 do 428 zapytań, zależnie od systemu, i żadne nie dostało odpowiedzi innej niż 200. Ten sam test na nginx dał ten sam wynik.
Restart trwał ponad dziesięć sekund, choć ExecStop to apachectl graceful-stop, czyli łagodne zatrzymanie. Plik error.log z Debiana 12 pokazuje, co się działo:
09:56:23.794 AH00492: caught SIGWINCH, shutting down gracefully
09:56:27.162 AH00045: child process 1686 still did not exit, sending a SIGTERM
09:56:29.164 AH00045: child process 1686 still did not exit, sending a SIGTERM
09:56:31.166 AH00045: child process 1686 still did not exit, sending a SIGTERM
09:56:33.169 AH00046: child process 1686 still did not exit, sending a SIGKILL
Łagodne zatrzymanie po nieco ponad trzech sekundach zamieniło się w zabijanie procesów. Przyczynę pokazał strace podpięty do procesu głównego na czas restartu:
10:00:42.904366 --- SIGWINCH {si_pid=4147} --- <- apache2 -k graceful-stop z ExecStop
10:00:42.919008 --- SIGTERM {si_pid=1} --- <- systemd (PID 1), 15 ms później
10:00:52.281231 --- SIGCHLD {si_pid=4079, si_status=SIGKILL} ---
apachectl graceful-stop wysyła sygnał i kończy działanie, tak samo jak graceful przy reload. Dla systemd zakończenie ExecStop oznacza, że usługa jest zatrzymana, więc zgodnie z KillMode=mixed (systemd.kill, systemd 262) od razu wysyła procesowi głównemu KillSignal. Dokumentacja dyrektywy ExecStop= (systemd 262) ostrzega przed tym wprost: pozostałe procesy są zabijane natychmiast po zakończeniu polecenia, więc polecenie zatrzymujące powinno być synchroniczne. Na Debianie i Ubuntu 24.04 drugim sygnałem jest SIGTERM, a według dokumentacji Apache o łagodnym zatrzymaniu (gałąź 2.4) SIGTERM w tym stanie natychmiast kończy proces główny i wszystkie procesy potomne.
Ubuntu 26.04 próbuje tego uniknąć przez KillSignal=SIGWINCH, ale wynik był identyczny: drugi SIGWINCH od PID 1, SIGKILL po 9,4 sekundy i przerwane pobieranie. Wyjaśnienie jest w kodzie Apache. Cytujemy gałąź 2.4.x w stanie z commitu 85b4876 z 22 września 2026, a zachowanie, które z niego wynika, zaobserwowaliśmy na wszystkich trzech wersjach z labu: 2.4.58, 2.4.66 i 2.4.68. Pętla łagodnego zatrzymania w server/mpm/event/event.c przed czekaniem zeruje flagę shutdown_pending i czeka tylko do jej ponownego ustawienia. Funkcja sig_term() w os/unix/unixd.c obsługuje zarówno SIGTERM, jak i SIGWINCH, i każdy z nich ustawia tę flagę. Drugi sygnał, niezależnie który, przerywa więc czekanie. Potem działa harmonogram z server/mpm_unix.c: SIGTERM po 3, 5 i 7 sekundach, SIGKILL po 9. To dokładnie odstępy z error.log.
W czasie zatrzymywania nowe połączenia dostawały odmowę (curl zwracał kod HTTP 000), bo łagodne zatrzymanie zaczyna się od zamknięcia gniazd nasłuchujących.
Łagodny restart Apache przez drop-in systemd i jego koszt
Skoro problemem jest drugi sygnał, można go usunąć drop-inem. Pusty ExecStop= kasuje polecenie z pakietu, a KillSignal=SIGWINCH sprawia, że systemd wyśle tylko jeden, łagodny sygnał:
# /etc/systemd/system/apache2.service.d/graceful-stop.conf
[Service]
ExecStop=
KillSignal=SIGWINCH
Z tym drop-inem pobieranie przetrwało restart w całości na wszystkich czterech systemach. Cena była jednak wyraźna. systemctl restart trwał 17,1-17,3 s, bo systemd czekał, aż Apache obsłuży ostatnie połączenie, a przez cały ten czas usługa była w stanie deactivating i odrzucała nowe połączenia:
+1 s: stan=deactivating nowe połączenie http=000
+5 s: stan=deactivating nowe połączenie http=000
+9 s: stan=deactivating nowe połączenie http=000
systemctl restart trwał 17139 ms
curl: pobrano=104857600 B czas=19.55 s exit=0
Zwykły restart przerywa trwające połączenia i szybko wraca. Łagodny restart chroni trwające połączenia, ale do ich końca nie przyjmuje nowych. Czas odmowy zależy od najdłuższego trwającego połączenia, a górną granicę wyznaczają GracefulShutdownTimeout (Apache 2.4) po stronie Apache i TimeoutStopSec= (systemd 262) po stronie systemd, w tej jednostce 90 sekund. Oba rodzaje połączeń naraz zachowuje tylko reload.
Przeładowanie konfiguracji nginx przez systemctl reload: błędy i połączenia w toku
nginx obsługuje reload natywnie. Jednostkę nginx.service dostarcza pakiet nginx-common (wersje jak w tabeli pakietu nginx), a jej sekcja [Service] jest identyczna na wszystkich czterech systemach:
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed
nginx -s reload wysyła do procesu głównego (mastera) sygnał SIGHUP. Według dokumentacji nginx o zmianie konfiguracji (nginx.org, wrzesień 2026: mainline 1.31.6, stable 1.30.5) master sprawdza składnię, próbuje otworzyć logi i nowe gniazda, a gdy to się uda, uruchamia nowe procesy robocze (workery) i każe starym zakończyć pracę po obsłużeniu bieżących klientów. W labie, na nginx 1.22.1, 1.24.0, 1.26.3 i 1.28.3, pobieranie 100 MiB przetrwało reload w całości, a ps pokazywał stary proces jako nginx: worker process is shutting down. Nowy blok server z server_name nowy.lab.test i dodatkowym listen 8082 zaczął działać po reload bez zmiany PID mastera, tak samo jak vhost w Apache.
Różnice pojawiły się przy błędach:
| Sytuacja | Apache | nginx |
|---|---|---|
| Błąd składni | reload odrzucony, działa stara konfiguracja | reload odrzucony, działa stara konfiguracja |
| Port zajęty przez inny proces | reload zgłasza sukces, serwer kończy działanie | reload zgłasza sukces, serwer działa na starej konfiguracji |
| Restart z pobieraniem w toku | 10,6 s, transfer przerwany | 5,1-5,2 s, transfer przerwany |
Błąd składni nginx łapie, choć w ExecReload nie ma jawnego nginx -t: samo nginx -s reload wczytuje konfigurację, zanim wyśle sygnał, i kończy się błędem unknown directive. systemd zgłasza wtedy Reload failed, a master działa dalej.
Zajęty port to przypadek odwrotny do Apache. nginx -t zgłosił test is successful, systemctl reload zwrócił 0 i Reloaded, a master pięć razy zapisał w error.log bind() to 0.0.0.0:22 failed (98: Address already in use) i wycofał zmiany. Serwer działał dalej, ale nowa konfiguracja nie weszła: nagłówek dopisany w tym samym pliku nie pojawił się w odpowiedziach. Tak ma być według przewodnika nginx (nginx.org, wrzesień 2026), tyle że dla systemd i narzędzi wdrożeniowych wygląda to jak udane wdrożenie. Po reload nginx trzeba więc sprawdzać error.log albo efekt zmiany, a nie kod wyjścia.
Restart nginx przerwał pobieranie po około 5 sekundach. start-stop-daemon wysyła QUIT (łagodne zakończenie) i czeka do 5 sekund, a TimeoutStopSec=5 upływa w tym samym momencie. Na trzech z czterech systemów journal zapisał Stopping timed out. Terminating. i Failed with result 'timeout', a na Debianie 12 i Ubuntu 24.04 worker zostawił w error.log wpisy open socket ... left in connection 3 i aborting.
Aktualizacja binarki Apache i nginx a systemctl reload
Reload ma jedno ograniczenie wspólne dla obu serwerów: nie uruchamia ponownie pliku wykonywalnego. Proces główny, który przeczytał nową konfigurację, to wciąż ten sam proces z tym samym kodem w pamięci.
Reload Apache nie uruchamia nowej binarki po aktualizacji pakietu
Po apt-get install --reinstall apache2-bin na Debianie 12 (2.4.68-1~deb12u1) proces główny Apache działał dalej, a /proc/<PID>/exe wskazywał /usr/sbin/apache2 (deleted): dpkg podmienia plik na dysku, więc działający proces trzyma w pamięci plik, którego już nie ma. Po systemctl reload stan wyglądał tak:
po reload: MainPID=3739 exe=/usr/sbin/apache2 (deleted)
moduły: 19, odwzorowania usuniętych plików modułów: 0 (przed reload: 95)
nowy proces potomny: exe=/usr/sbin/apache2 (deleted)
Rdzeń serwera i nowe procesy potomne pochodzą ze starej binarki, ale moduły, które Apache ładuje z plików .so przy każdym czytaniu konfiguracji, są już nowe. Reload po aktualizacji daje więc proces złożony ze starego rdzenia i nowych modułów. W naszych testach Apache działał w tym stanie poprawnie, ale nie jest to ani stara, ani nowa wersja serwera.
Aktualizacja pakietu apache2 sama wykonuje restart usługi
Przy prawdziwej zmianie wersji do reload w ogóle nie dochodzi. Sprawdziliśmy to, cofając pakiety do poprzedniej wersji z repozytorium i aktualizując je z powrotem: na Debianie 12 z 2.4.67-1~deb12u3 do 2.4.68-1~deb12u1, na Debianie 13 z 2.4.67-1~deb13u3 do 2.4.68-1~deb13u1, na Ubuntu 24.04 z 2.4.58-1ubuntu8 do 2.4.58-1ubuntu8.15, a na Ubuntu 26.04 z 2.4.66-2ubuntu2 do 2.4.66-2ubuntu2.4. Skrypt postinst pakietu apache2 ma blok dopisany przez dh_installsystemd (w pakietach z labu z debhelper 13.11.4, 13.24.2, 13.14.1 i 13.31), który przy aktualizacji wykonuje restart:
if [ -n "$2" ]; then
_dh_action=restart
else
_dh_action=start
fi
deb-systemd-invoke $_dh_action 'apache2.service' >/dev/null || true
Na wszystkich czterech systemach MainPID zmienił się w trakcie apt-get install, a journal zapisał Stopping i Started. To zwykły restart ze wszystkimi skutkami opisanymi wyżej, więc trwające połączenia są przerywane po kilku sekundach. Sama reinstalacja apache2-bin tego restartu nie wywołuje, bo blok jest w pakiecie apache2.
needrestart na Debianie tylko wypisuje usługi, a na Ubuntu sam wykonuje restart
Na maszynach labu jest needrestart, który po każdej operacji APT (narzędzia, którym Debian i Ubuntu instalują pakiety) szuka procesów działających ze starych bibliotek i binarek. Po reinstalacji apache2-bin zachował się różnie. Na Debianie tylko wypisał listę:
Services to be restarted:
systemctl restart apache2.service
Na Ubuntu sam wykonał restart:
Restarting services...
systemctl restart apache2.service
Źródło różnicy jest w kodzie /usr/sbin/needrestart z Ubuntu, identycznym pod tym względem w wersjach 3.6-7ubuntu4.5 (Ubuntu 24.04) i 3.11-1ubuntu2 (Ubuntu 26.04). Debian ma needrestart 3.6-4+deb12u3 i 3.11-1 bez tej zmiany. Wywołany z APT przechodzi w tryb opisany komentarzem Ubuntu: adjust the defaults when called through APT i jeśli w konfiguracji nie ma jawnie ustawionego $nrconf{restart}, przyjmuje wartość 'a', czyli automatyczny restart. Z tego samego kodu wynika, że jawne ustawienie trybu, na przykład $nrconf{restart} = 'l'; w pliku w /etc/needrestart/conf.d/, albo zmienna środowiskowa NEEDRESTART_MODE=l przywracają samo wypisywanie listy.
Jak przesunąć restart Apache po aktualizacji do okna serwisowego: policy-rc.d
Jeśli restart ma się odbyć w wybranym momencie, a nie w środku apt upgrade, można go zablokować standardowym mechanizmem Debiana: plikiem /usr/sbin/policy-rc.d, który zwraca kod 101.
# printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d
# chmod +x /usr/sbin/policy-rc.d
# apt-get install apache2 apache2-bin apache2-data apache2-utils
...
invoke-rc.d: policy-rc.d denied execution of restart.
/usr/sbin/policy-rc.d returned 101, not running 'restart apache2.service'
# rm /usr/sbin/policy-rc.d
Na Debianie 13 (aktualizacja 2.4.67-1~deb13u3 do 2.4.68-1~deb13u1, invoke-rc.d z init-system-helpers 1.69~deb13u1) i Ubuntu 24.04 (2.4.58-1ubuntu8 do 2.4.58-1ubuntu8.15, init-system-helpers 1.66ubuntu1) Apache działał po takiej aktualizacji dalej, na usuniętej starej binarce, aż do ręcznego systemctl restart. Plik blokuje start i restart wszystkich usług (w tym samym przebiegu zablokował też apache-htcacheclean.service), więc trzeba go usunąć zaraz po aktualizacji. Reload w tym oknie oznacza opisane wyżej połączenie starego rdzenia z nowymi modułami.
Dokumentacja Apache (gałąź 2.4) wspomina, że graceful-stop pozwala uruchomić obok siebie kilka identycznie skonfigurowanych instancji, i nazywa to przydatnym przy łagodnych aktualizacjach. W ramach jednej jednostki systemd tego nie wykorzystamy, bo restart to najpierw zatrzymanie, a dopiero potem start. Aktualizacja Apache bez przerwania żadnego połączenia wymaga więc drugiej instancji albo drugiego serwera za rozdzielaczem ruchu, a nie sprytniejszego reload.
Podmiana binarki nginx w locie sygnałem USR2 przy aktualizacji pakietu
nginx ma mechanizm podmiany pliku wykonywalnego w locie (nginx.org, wrzesień 2026). Sygnał USR2 każe masterowi uruchomić nową binarkę jako drugi master, który dziedziczy gniazda nasłuchujące, a stary master po sygnale QUIT kończy pracę, gdy obsłuży swoich klientów. Pakiety Debiana i Ubuntu robią to automatycznie. Skrypt postinst pakietu nginx wywołuje:
invoke-rc.d nginx upgrade || invoke-rc.d nginx restart
systemd nie zna akcji upgrade. invoke-rc.d w takiej sytuacji uruchamia bezpośrednio skrypt /etc/init.d/nginx (komentarz w kodzie invoke-rc.d z init-system-helpers 1.69~deb13u1: We try to run non-standard actions by running the init script directly), a jego funkcja upgrade_nginx() wysyła USR2, czeka na plik nginx.pid.oldbin i wysyła QUIT do starego mastera. Na Debianie 13 (reinstalacja nginx 1.26.3-3+deb13u9, needrestart 3.11-1) z pobieraniem w toku wyglądało to tak:
przed: master=6711 MainPID=6711
APT: Services to be restarted:
systemctl restart nginx.service
t+2 s: MainPID=6711 nginx.pid=6784 stary master żyje, nowy master ppid=6711
...
t+14 s: MainPID=6711 nginx.pid=6784 stary master żyje, nowy master ppid=6711
t+16 s: MainPID=6784 nginx.pid=6784 stary master zakończony, nowy master ppid=1
curl: pobrano=104857600 B exit=0
APT wypisuje przy tym Upgrading binary: nginx., a nowy master zapisuje w error.log using inherited sockets from "5;6;". Stary master żył tak długo, jak trwało pobieranie, a nowy master był w tym czasie jego procesem potomnym i obsługiwał nowe połączenia. needrestart w trybie Debiana zauważył stary proces i tylko dopisał nginx do listy usług do restartu. Kiedy stary master się zakończył, systemd bez żadnego wpisu w journalu przełączył MainPID na nowy proces, zapisany w /run/nginx.pid wskazanym przez PIDFile= (systemd 257 w labie, dokumentacja: systemd 262). Kolejne systemctl stop i start działały normalnie. Pobieranie przetrwało podmianę binarki w całości.
Na Ubuntu ten sam mechanizm zadziałał, ale zaraz po nim needrestart w trybie automatycznym znalazł stary master, wciąż działający ze starej binarki, bo obsługiwał pobieranie, i wykonał systemctl restart nginx.service. Pobieranie zostało przerwane po 48,1 MiB na Ubuntu 24.04 (nginx 1.24.0-2ubuntu7.18, needrestart 3.6-7ubuntu4.5) i 40,8 MiB na 26.04 (nginx 1.28.3-2ubuntu1.11, needrestart 3.11-1ubuntu2). Bez połączeń w toku stary master kończył się od razu, needrestart nie miał czego restartować i aktualizacja przeszła bez przerwy. Na serwerze z długimi pobraniami domyślny tryb needrestart w Ubuntu niweczy więc najmocniejszą stronę nginx.
Mniej znane zachowania systemd przy reload i restart
systemctl reload na zatrzymanej usłudze oraz reload-or-restart i try-reload-or-restart
# systemctl stop apache2
# systemctl reload apache2
apache2.service is not active, cannot reload. # kod 1
# systemctl try-reload-or-restart apache2 # kod 0, usługa dalej zatrzymana
# systemctl reload-or-restart apache2 # kod 0, usługa uruchomiona
Na działającej usłudze oba warianty wykonują reload, bez zmiany MainPID. Różnicę opisuje dokumentacja systemctl (systemd 262; try-reload-or-restart istnieje według niej od wersji 229, więc we wszystkich wersjach z labu): try-reload-or-restart nic nie robi z usługą, która nie działa, a reload-or-restart ją uruchamia.
systemctl reload na jednostce bez ExecReload: Job type reload is not applicable
Na wszystkich czterech systemach obok Apache działa apache-htcacheclean.service, który reload nie obsługuje:
# systemctl reload apache-htcacheclean
Failed to reload apache-htcacheclean.service: Job type reload is not applicable for unit apache-htcacheclean.service.
# systemctl show -p CanReload apache-htcacheclean
CanReload=no
Polecenie kończy się kodem 3. reload-or-restart wykonuje w takiej sytuacji restart i proces dostaje nowy numer.
Nowy drop-in bez ostrzeżenia o daemon-reload w systemd 252 i 255
Po zmianie pliku jednostki systemd ustawia właściwość NeedDaemonReload=yes i przy systemctl reload ostrzega: Warning: The unit file, source configuration file or drop-ins of apache2.service changed on disk. Run 'systemctl daemon-reload' to reload units. Sprawdziliśmy, kiedy to działa:
| Zmiana | systemd 252 i 255 | systemd 257 i 259 |
|---|---|---|
Nowy katalog apache2.service.d z pierwszym plikiem | NeedDaemonReload=no, bez ostrzeżenia | yes, ostrzeżenie |
| Zmiana istniejącego drop-inu | yes | yes |
| Nowy plik w istniejącym katalogu | yes | yes |
| Usunięcie katalogu | yes | yes |
Pierwszy drop-in dla usługi, która wcześniej żadnego nie miała, to na Debianie 12 i Ubuntu 24.04 zmiana zupełnie niewidoczna: bez ostrzeżenia i bez efektu, dopóki ktoś nie wykona daemon-reload.
systemctl daemon-reload nie zmienia Environment= działającego procesu
Dopisaliśmy w drop-inie Environment=LAB_MARKER=1, wykonaliśmy daemon-reload i reload. systemctl show -p Environment pokazywał już nową zmienną, ale w /proc/<MainPID>/environ jej nie było. Pojawiła się dopiero po restarcie, razem z nowym MainPID. Środowisko procesu ustala się przy jego uruchomieniu, a reload nie uruchamia procesu głównego od nowa.
Reload z potwierdzeniem od usługi: Type=notify-reload i ExecReloadPost=
systemd oferuje reload, który nie ma problemu opisanego w tym artykule. Przy Type=notify-reload (systemd 262) systemd sam wysyła sygnał (domyślnie SIGHUP, zmieniany przez ReloadSignal=) i czeka, aż usługa zgłosi RELOADING=1, a potem READY=1. Reload kończy się więc wtedy, gdy usługa naprawdę skończyła. Ani Apache, ani nginx z tego nie korzystają, ale w labie korzystają z tego między innymi systemd-logind, systemd-networkd i systemd-udevd na Debianie 13 oraz obu Ubuntu. Według tej samej dokumentacji towarzysząca mu dyrektywa ReloadSignal= pojawiła się w wersji 253. systemd 252.39 z Debiana 12 tego typu nie zna: systemd-analyze verify zgłasza Failed to parse service type, ignoring: notify-reload, czyli dyrektywa jest pomijana z samym ostrzeżeniem.
Najnowsza w labie dyrektywa to ExecReloadPost=, polecenie wykonywane po udanym przeładowaniu. Według dokumentacji (systemd 262) pojawiła się w wersji 259 i w labie przyjmuje ją tylko Ubuntu 26.04. Debian 12, Debian 13 i Ubuntu 24.04 zgłaszają Unknown key i ją ignorują.
Przenoszenie reload na inne jednostki: PropagatesReloadTo= i ReloadPropagatedFrom=
Dyrektywy PropagatesReloadTo= i ReloadPropagatedFrom= (według dokumentacji systemd 262 dostępne od wersji 201) łączą przeładowania jednostek. Jednostka z ReloadPropagatedFrom=apache2.service była przeładowywana przy każdym systemctl reload apache2, na wszystkich czterech systemach:
Reloading apache2.service - The Apache HTTP Server...
Reloading lab-probe.service...
Reloaded lab-probe.service.
Reloaded apache2.service - The Apache HTTP Server.
To wygodny sposób, żeby usługa zależna od konfiguracji serwera WWW (na przykład eksporter metryk czytający te same pliki) przeładowywała się razem z nim bez dopisywania czegokolwiek do skryptów wdrożeniowych.
Ręczne apachectl graceful omija systemd na Debianie i Ubuntu 24.04
Na Debianie 12, 13 i Ubuntu 24.04 (apache2 2.4.68-1~deb12u1, 2.4.68-1~deb13u1, 2.4.58-1ubuntu8.15) apachectl graceful uruchomione z konsoli wysyła sygnał bezpośrednio, więc w dzienniku jednostki apache2.service nie zostaje żaden ślad przeładowania. Na Ubuntu 26.04 (2.4.66-2ubuntu2.4) skrypt przekazuje polecenie do systemd i wypisuje Invoking 'systemctl reload apache2'. Komentarz w skrypcie powołuje się na zgłoszenia Launchpad #1832182 i Debian #927302, które opisują skutki uboczne sterowania Apache z pominięciem systemd: systemd, który nie widzi działającego serwera, i Apache działający w innej grupie kontrolnej niż jego jednostka.
logrotate codziennie wykonuje reload Apache, o ile jest zainstalowany
Plik /etc/logrotate.d/apache2 dostarcza pakiet apache2, więc jest na wszystkich czterech systemach i kończy się tym samym:
postrotate
if pgrep -f ^/usr/sbin/apache2 > /dev/null; then
invoke-rc.d apache2 reload 2>&1 | logger -t apache2.logrotate
fi
endscript
Plik ma ustawione daily i rotate 14, ale sam niczego nie uruchamia. Na obu Ubuntu jest zainstalowany logrotate (3.21.0-2build1 i 3.22.0-1build1) z aktywnym logrotate.timer (OnCalendar=daily), więc Apache dostaje tam reload codziennie. Na Debianach w naszym labie pakietu logrotate nie było (w repozytorium dostępne są wersje 3.21.0-1 i 3.22.0-1), więc ten reload się tam nie odbywał. Warto to sprawdzić na własnych obrazach, zanim przyjmie się, że rotacja działa.
Tam, gdzie rotacja działa, to reload, a nie restart, więc nie przerywa pobierań. Jednocześnie to właśnie ten nocny reload jako pierwszy trafi na zmianę konfiguracji, której nikt nie przeładował ręcznie, na przykład na port zajęty w międzyczasie przez inną usługę.
Częste błędy przy reload i restart Apache oraz nginx
- Traktowanie kodu 0 z
systemctl reloadjako dowodu, że działa nowa konfiguracja. Apache potrafi zakończyć działanie kilkanaście milisekund później, a nginx po cichu zostać przy starej konfiguracji. - Restart tam, gdzie wystarczy reload. Nowy vhost, nowy port i włączenie modułu działały po reload bez przerywania połączeń.
- Reload tam, gdzie potrzebny jest restart. Zmiana MPM kładzie Apache, zmienne w
Environment=nie docierają do działającego procesu, a nowa binarka nie jest uruchamiana. - Wiara, że
ExecStop=apachectl graceful-stopchroni połączenia przy restarcie. Drugi sygnał od systemd przerywa łagodne zatrzymanie po około trzech sekundach, a po dziewięciu Apache zabija pozostałe procesy. - Drop-in bez
daemon-reload. Na systemd 252 i 255 pierwszy drop-in nie wywołuje nawet ostrzeżenia. - Założenie, że
apt upgradenie ruszy serwera WWW. Pakietapache2sam wykonuje restart, a needrestart na Ubuntu restartuje także nginx, jeśli stary master jeszcze obsługuje połączenia. - Ręczne
apachectl gracefulna Debianie. Działa, ale omija systemd i nie zostawia śladu w dzienniku jednostki.
Checklista: reload i restart serwera WWW
- Czy przed przeładowaniem konfiguracja przeszła
apache2ctl configtestalbonginx -t? - Czy zmiana dotyczy tylko konfiguracji serwera (vhost, port, moduł)? Jeśli tak, wystarczy reload.
- Czy po reload sprawdzamy stan usługi z opóźnieniem, realne zapytanie HTTP i, w przypadku nginx,
error.log? - Czy zmiana MPM, zmiany w jednostce i nowa binarka są zaplanowane jako restart w oknie serwisowym?
- Czy po każdej zmianie drop-inu wykonujemy
systemctl daemon-reloadi sprawdzamy wartość przezsystemctl show? - Czy wiemy, kto zrestartuje usługę przy aktualizacji: pakiet (
apache2), needrestart (domyślnie na Ubuntu), czy nikt (reinstalacja samegoapache2-binna Debianie)? - Czy coś z zewnątrz zauważy, że serwer nie działa po „udanym" reload?
Kod 0 z systemctl reload nie oznacza, że serwer WWW działa
Wdrażamy zmiany konfiguracji serwerów WWW ze sprawdzeniem po fakcie, a aktualizacje planujemy tak, żeby restart nie wypadał w środku ruchu.
Zobacz też