Przejdź do treści
Lekcja 89 z 117

Kurs › Moduł 10 › Lekcja 89

Apache: konfiguracja

⏱ ok. 12 min czytania· moduł 10: Usługi i serwery

Po tej lekcji: kursant zna układ plików konfiguracji Apache na Ubuntu i RHEL, stawia kilka stron na jednym serwerze przez VirtualHosty, włącza moduły, używa .htaccess i mod_rewrite, sprawdza konfigurację przed restartem i czyta logi.

Wstęp

Masz jeden serwer. Za pięć złotych miesięcznie albo pod biurkiem. A chcesz na nim postawić stronę kawiarni, stronę piekarni i swojego bloga. Trzy różne adresy, trzy różne strony. Kupujesz trzy serwery?

Nie. Jeden Apache obsłuży je wszystkie i jeszcze zostanie mu miejsce na czwartą. Tak działa większość tanich hostingów na świecie: setki stron na jednej maszynie. Dziś zrobisz to sam i przy okazji nauczysz się czytać konfigurację Apache, zamiast kopiować ją z forów i modlić się, żeby zadziałało.

Gdzie leży konfiguracja

Tu dwie rodziny rozjeżdżają się najbardziej. Ten sam Apache, dwie filozofie układania papierów.

[Ubuntu] Wszystko w /etc/apache2:

ls /etc/apache2
apache2.conf
conf-available
conf-enabled
envvars
magic
mods-available
mods-enabled
ports.conf
sites-available
sites-enabled
  • apache2.conf — główny plik, regulamin całego hotelu.
  • ports.conf — na jakich portach słuchać (Listen 80, a przy SSL Listen 443).
  • sites-available — wszystkie strony, jakie masz w szufladzie.
  • sites-enabled — strony, które są teraz włączone.
  • mods-available / mods-enabled — to samo dla modułów.
  • conf-available / conf-enabled — to samo dla dodatkowych kawałków konfiguracji.

Zobacz, co naprawdę leży w sites-enabled:

ls -l /etc/apache2/sites-enabled/
lrwxrwxrwx 1 root root 35 Nov  3 16:28 000-default.conf -> ../sites-available/000-default.conf

To nie kopie, tylko dowiązania symboliczne, strzałki do plików w sites-available. Szuflada available to magazyn szyldów, a enabled to ściana, na której wiszą te, które są aktualnie powieszone. Żeby powiesić albo zdjąć szyld, nie robisz dowiązań ręcznie. Debian dał Ci do tego narzędzia:

sudo a2ensite kawiarnia.test     # włącz stronę
sudo a2dissite kawiarnia.test    # wyłącz stronę
sudo a2enmod rewrite             # włącz moduł
sudo a2dismod rewrite            # wyłącz moduł
sudo a2enconf servername         # włącz kawałek konfiguracji

a2 to skrót od „apache2”, en od „enable”, dis od „disable”. Ktoś w Debianie bardzo nie lubił długich nazw.

[RHEL] Wszystko w /etc/httpd:

  • /etc/httpd/conf/httpd.conf — główny plik.
  • /etc/httpd/conf.d/ — tu wrzucasz swoje pliki .conf, w tym strony. Każdy plik kończący się na .conf jest wczytywany automatycznie.
  • /etc/httpd/conf.modules.d/ — moduły.

Nie ma a2ensite. Chcesz włączyć stronę, kładziesz plik kawiarnia.conf w conf.d. Chcesz wyłączyć, zmieniasz nazwę na kawiarnia.conf.wylaczone albo przenosisz go gdzie indziej. Proste jak drzwi do stodoły. Ubuntu to szafa z szufladami i etykietami, RHEL to jedno pudło, do którego wrzucasz to, co ma działać.

VirtualHost, czyli wiele szyldów na jednym budynku

Pamiętasz z poprzedniej lekcji nagłówek Host: w rozmowie przeglądarki z serwerem? To jest cały sekret. Przeglądarka nie tylko łączy się z adresem IP serwera, ale też mówi: „Dzień dobry, przyszedłem do kawiarni”. Wszystkie trzy nazwy prowadzą do tego samego budynku, ale recepcjonista patrzy, do kogo gość przyszedł, i kieruje go do odpowiedniego skrzydła.

Każde takie skrzydło to VirtualHost, wirtualny serwer. Zrobimy dwa: kawiarnię i piekarnię.

Najpierw nazwy. Nie mamy prawdziwych domen, więc dopisujemy je do /etc/hosts na komputerze, z którego otwierasz strony. Na samym serwerze:

echo '127.0.0.1 kawiarnia.test piekarnia.test' | sudo tee -a /etc/hosts

Domena .test jest zarezerwowana do testów i nigdy nie będzie prawdziwa w internecie. Bezpieczniej niż wymyślać kawiarnia.pl i potem się dziwić, czemu wchodzisz na cudzą stronę.

Folder na stronę:

sudo mkdir -p /var/www/kawiarnia.test/public
echo '<h1>Kawiarnia: dzis espresso za 5 zl</h1>' | sudo tee /var/www/kawiarnia.test/public/index.html

I plik strony, [Ubuntu] /etc/apache2/sites-available/kawiarnia.test.conf, [RHEL] /etc/httpd/conf.d/kawiarnia.test.conf:

<VirtualHost *:80>
    ServerName kawiarnia.test
    ServerAlias www.kawiarnia.test
    DocumentRoot /var/www/kawiarnia.test/public

    ErrorLog ${APACHE_LOG_DIR}/kawiarnia_error.log
    CustomLog ${APACHE_LOG_DIR}/kawiarnia_access.log combined
</VirtualHost>

Linijka po linijce:

  • <VirtualHost *:80> — to skrzydło obsługuje gości na porcie 80, na każdym adresie.
  • ServerName — główna nazwa, po której recepcja rozpoznaje gościa.
  • ServerAlias — inne nazwy tej samej strony. Ludzie wpisują i z www, i bez.
  • DocumentRoot — folder, z którego bierzemy pliki.
  • ErrorLog i CustomLog — osobne księgi skarg i gości dla tej strony. Gdy masz dziesięć stron, nie chcesz szukać błędu kawiarni wśród wizyt w piekarni.

[RHEL] ${APACHE_LOG_DIR} to zmienna z Ubuntu. Na RHEL piszesz ścieżki wprost: ErrorLog /var/log/httpd/kawiarnia_error.log.

Włączamy i sprawdzamy:

sudo a2ensite kawiarnia.test
sudo apachectl configtest
sudo systemctl reload apache2
curl http://kawiarnia.test/
Enabling site kawiarnia.test.
To activate the new configuration, you need to run:
  service apache2 reload
Syntax OK
<h1>Kawiarnia: dzis espresso za 5 zl</h1>

apachectl configtest, czyli zmierz dwa razy, tnij raz

Zapamiętaj kolejność, bo to najważniejsza rzecz w tej lekcji: najpierw configtest, potem reload. Zawsze.

Dlaczego? Bo jak przeładujesz Apache z błędem w konfiguracji, możesz położyć nie tylko nową stronę, ale wszystkie strony na serwerze. Kawiarnia, piekarnia i blog lecą razem, bo pomyliłeś jedną literkę. configtest sprawdza całą konfigurację bez dotykania działającego serwera:

Syntax OK

To jest zdanie, które chcesz zobaczyć. A przy okazji pozbądźmy się ostrzeżenia z poprzedniej lekcji. Apache nie wie, jak się nazywa, więc mu powiemy:

echo 'ServerName localhost' | sudo tee /etc/apache2/conf-available/servername.conf
sudo a2enconf servername
sudo apachectl configtest
Syntax OK

Bez AH00558. Cisza i spokój. [RHEL] Tam dopisujesz ServerName w /etc/httpd/conf/httpd.conf.

A teraz mapa wszystkich stron:

sudo apachectl -S
*:80                   is a NameVirtualHost
         default server localhost (/etc/apache2/sites-enabled/000-default.conf:1)
         port 80 namevhost localhost (/etc/apache2/sites-enabled/000-default.conf:1)
         port 80 namevhost kawiarnia.test (/etc/apache2/sites-enabled/kawiarnia.test.conf:1)
                 alias www.kawiarnia.test

Zwróć uwagę na default server. Co się stanie, jak ktoś przyjdzie z nazwą, której nie znamy?

curl -s -H 'Host: nieznana.test' http://127.0.0.1/ | grep -o '<title>.*</title>'
<title>Apache2 Ubuntu Default Page: It works</title>

Trafia do pierwszego VirtualHosta na liście. Na Ubuntu to 000-default, i właśnie dlatego ma w nazwie trzy zera: żeby alfabetycznie był pierwszy. Jeśli go wyłączysz, domyślną stroną zostanie pierwsza alfabetycznie z Twoich. Gość, który pomylił drzwi, wyląduje w kawiarni. Czasem to dobrze, czasem bardzo źle.

Flaga -H w curlu ustawia nagłówek ręcznie. Super sztuczka do testów: sprawdzasz VirtualHosta, zanim jeszcze ustawisz DNS.

Directory, czyli kto ma klucz do których drzwi

Druga strona, piekarnia. Tym razem z plikami w /srv, bo pamiętasz z planu biurowca, /srv to dane udostępniane przez serwer:

<VirtualHost *:80>
    ServerName piekarnia.test
    DocumentRoot /srv/piekarnia
    ErrorLog ${APACHE_LOG_DIR}/piekarnia_error.log
    CustomLog ${APACHE_LOG_DIR}/piekarnia_access.log combined
</VirtualHost>
sudo a2ensite piekarnia.test
sudo apachectl configtest && sudo systemctl reload apache2
curl -s -o /dev/null -w '%{http_code}\n' http://piekarnia.test/
403

Zakaz. Plik jest, prawa 644 są. Co jest grane? Log piekarni:

[authz_core:error] [client 127.0.0.1:55832] AH01630: client denied by server configuration: /srv/piekarnia/

„Odmówiono przez konfigurację serwera”. W apache2.conf siedzi taki fragment:

<Directory />
    Options FollowSymLinks
    AllowOverride None
    Require all denied
</Directory>

Czyli: cały dysk, od korzenia, jest zamknięty dla gości. Potem jest wyjątek „ale /var/www jest otwarte”. A /srv? Wyjątek dla niego jest w pliku, tylko zakomentowany. Apache domyślnie nie pokazuje niczego spoza wyznaczonych miejsc. I bardzo dobrze, bo inaczej jedna literówka w DocumentRoot mogłaby wystawić w świat /etc.

Naprawa: dajemy klucz do tego jednego folderu, w pliku strony:

    <Directory /srv/piekarnia>
        Require all granted
    </Directory>
<h1>Piekarnia: chleb od 6:00</h1>

<Directory> to zasady dla konkretnego folderu. Require all granted to „wpuszczaj wszystkich”, Require all denied to „nikogo”. Można też wpuszczać tylko wybrane adresy, na przykład Require ip 192.168.1.0/24 dla strony tylko dla biura.

[RHEL] Tu dochodzi SELinux: pliki w /srv nie mają domyślnie etykiety, którą Apache może czytać. O tym szczegółowo w następnej lekcji.

Moduły

Apache sam z siebie umie mało. Wszystko, co ciekawe, to moduły: szyfrowanie, przekierowania, kompresja, logowanie hasłem. Jak dodatki do odkurzacza: końcówka do szczelin, szczotka do mebli. Kupujesz jeden odkurzacz, a końcówki dokładasz.

apachectl -M            # lista załadowanych modułów
sudo a2enmod rewrite    # Ubuntu: włącz moduł
sudo systemctl restart apache2

Po włączeniu lub wyłączeniu modułu dawaj restart, a nie reload. Przy zmianie stron wystarczy reload.

[RHEL] Moduły włącza się w plikach w /etc/httpd/conf.modules.d/, a wiele popularnych, w tym mod_rewrite, jest załadowanych od razu. Sprawdzisz przez httpd -M | grep rewrite.

.htaccess i mod_rewrite

.htaccess to karteczka z zasadami, którą przykleja się w konkretnym pokoju, zamiast zmieniać regulamin całego hotelu. Plik o tej nazwie w folderze strony może zmieniać ustawienia dla tego folderu. Kochają go hostingi współdzielone, bo klient nie ma dostępu do głównej konfiguracji, ale może wrzucić .htaccess do swojego folderu.

A mod_rewrite to moduł do przepisywania adresów. Gość prosi o /karta, a my po cichu dajemy mu menu.html. Albo gość prosi o stary adres, a my odsyłamy go pod nowy.

echo '<h1>Menu: espresso, latte, sernik</h1>' | sudo tee /var/www/kawiarnia.test/public/menu.html

/var/www/kawiarnia.test/public/.htaccess:

RewriteEngine On
RewriteRule ^karta$ menu.html [L]
RewriteRule ^stare-menu$ /karta [R=301,L]

Pierwsza reguła: adres karta po cichu pokazuje menu.html, gość nawet nie wie. [L] to „last”, czyli na tym koniec. Druga reguła: stary adres odsyła pod nowy z kodem 301, „przeprowadziliśmy się na stałe”. Przeglądarki i wyszukiwarki zapamiętują nowy adres.

Test:

curl -s -o /dev/null -w '%{http_code}\n' http://kawiarnia.test/karta
404

404? Reguła jest, plik jest. A Apache ignoruje .htaccess w całości. Dlaczego? Bo domyślnie AllowOverride None. Właściciel hotelu nie pozwala przyklejać karteczek. Trzeba mu to wyraźnie dopuścić w pliku strony:

    <Directory /var/www/kawiarnia.test/public>
        AllowOverride All
        Require all granted
    </Directory>
sudo apachectl configtest && sudo systemctl reload apache2
curl -s -o /dev/null -w '%{http_code}\n' http://kawiarnia.test/karta
500

Gorzej! Teraz recepcjonista zemdlał. Ale log mówi dokładnie, czemu. O tym w typowym błędzie. Spoiler: brakuje modułu.

sudo a2enmod rewrite
sudo systemctl restart apache2
curl http://kawiarnia.test/karta
curl -sI http://kawiarnia.test/stare-menu | grep -E 'HTTP|Location'
<h1>Menu: espresso, latte, sernik</h1>
HTTP/1.1 301 Moved Permanently
Location: http://kawiarnia.test/karta

Działa. A czy ktoś może podejrzeć naszą karteczkę?

curl -s -o /dev/null -w '%{http_code}\n' http://kawiarnia.test/.htaccess
403

Nie. Apache domyślnie nie pokazuje plików zaczynających się od .ht.

Logi

Każda strona ma teraz swoje księgi. Kilka sztuczek na co dzień:

sudo tail -f /var/log/apache2/kawiarnia_access.log      # na żywo
sudo grep ' 404 ' /var/log/apache2/kawiarnia_access.log  # czego brakuje
sudo awk '{print $9}' /var/log/apache2/kawiarnia_access.log | sort | uniq -c | sort -rn

Ostatnia komenda to taśma w fabryce z modułu o tekstach: dziewiąte pole w logu to kod odpowiedzi, liczymy, ile było których:

      6 200
      4 301
      1 500
      1 404
      1 403

Od razu widać, że ktoś dostał 500. A na produkcji wystarczy jedna linijka 500, żeby ktoś się zdenerwował.

Logi rosną bez końca, więc system tnie je codziennie: na Ubuntu /etc/logrotate.d/apache2 ma daily i rotate 14, czyli dwa tygodnie historii, starsze lecą do kosza.

Jak to działa pod spodem

Przy starcie Apache czyta główny plik, a w nim dyrektywy Include i IncludeOptional, które doklejają kolejne pliki: na Ubuntu z mods-enabled, conf-enabled, sites-enabled, na RHEL z conf.modules.d i conf.d. Z tego wszystkiego powstaje jedna wielka konfiguracja w pamięci. Dlatego literówka w dowolnym pliku psuje całość.

Kiedy przychodzi żądanie, Apache patrzy na adres i port, na którym przyszło połączenie, wybiera grupę VirtualHostów pasującą do tego adresu, a w niej szuka takiego, którego ServerName albo ServerAlias zgadza się z nagłówkiem Host. Nie znalazł? Bierze pierwszy z grupy. Potem skleja DocumentRoot z adresem, przechodzi przez zasady z <Directory>, i jeśli AllowOverride pozwala, czyta po drodze .htaccess.

reload (w Apache: graceful) każe procesom roboczym dokończyć obsługę bieżących gości i wczytuje nową konfigurację, nie zrywając połączeń. restart zatrzymuje wszystko i startuje od nowa.

Ściąga flag

KomendaFlagaCo robi
a2ensite / a2dissiteNAZWAwłącz/wyłącz stronę (Ubuntu)
a2enmod / a2dismodNAZWAwłącz/wyłącz moduł (Ubuntu)
a2enconf / a2disconfNAZWAwłącz/wyłącz fragment konfiguracji (Ubuntu)
apachectlconfigtestsprawdź składnię przed przeładowaniem
apachectl-Slista VirtualHostów i domyślny serwer
apachectl-Mlista modułów
apachectlgracefulprzeładuj bez zrywania połączeń
systemctlreload apache2 / httpdprzeładuj konfigurację
systemctlrestart apache2 / httpdpełny restart (np. po zmianie modułów)
curl-H 'Host: nazwa'test VirtualHosta bez DNS
curl-w '%{http_code}\n'wypisz sam kod odpowiedzi
tail-fśledź log na żywo

⚠ Typowy błąd

Ten 500 z .htaccess. Log kawiarni:

sudo tail -1 /var/log/apache2/kawiarnia_error.log
[core:alert] [client 127.0.0.1:43726] /var/www/kawiarnia.test/public/.htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration

„Nieznane polecenie RewriteEngine, może literówka albo moduł nie jest załadowany”. Apache nie zna słowa RewriteEngine, bo zna je dopiero mod_rewrite, a ten był wyłączony. Naprawa: sudo a2enmod rewrite i restart.

Ten sam komunikat dostaniesz przy prawdziwej literówce w głównej konfiguracji. Na przykład DocumentRot zamiast DocumentRoot:

sudo apachectl configtest
AH00526: Syntax error on line 4 of /etc/apache2/sites-enabled/kawiarnia.test.conf:
Invalid command 'DocumentRot', perhaps misspelled or defined by a module not included in the server configuration

Numer linii, nazwa pliku i błędne słowo. Lepszej podpowiedzi nie dostaniesz. I dlatego configtest przed reload: ten błąd zobaczył tylko Ty, a nie klienci kawiarni.

A jeszcze jedna pułapka: literówka w ścieżce DocumentRoot nie jest błędem składni:

AH00112: Warning: DocumentRoot [/var/www/kawiarnia.tset/public] does not exist
Syntax OK

Syntax OK na dole, a strona i tak nie zadziała. Czytaj też ostrzeżenia nad tym zdaniem, nie tylko ostatnią linijkę.

✎ Zadanie

  1. Zrób VirtualHost notatki.test z DocumentRoot w /var/www/notatki.test/public i osobnymi logami. Dopisz nazwę do /etc/hosts.
  2. W folderze strony połóż index.html i o-mnie.html. Przez .htaccess i mod_rewrite zrób tak, żeby adres /kim-jestem pokazywał o-mnie.html.
  3. Zrób celowo literówkę w pliku strony, uruchom apachectl configtest, przeczytaj komunikat i napraw.
  4. Sprawdź apachectl -S, która strona jest domyślna, i otwórz serwer przez curl -H 'Host: cokolwiek.test'.
Pokaż rozwiązanie

VirtualHost notatki.test, plik /etc/apache2/sites-available/notatki.test.conf ([RHEL] /etc/httpd/conf.d/notatki.test.conf, z logami w /var/log/httpd/):

<VirtualHost *:80>
    ServerName notatki.test
    DocumentRoot /var/www/notatki.test/public

    <Directory /var/www/notatki.test/public>
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/notatki_error.log
    CustomLog ${APACHE_LOG_DIR}/notatki_access.log combined
</VirtualHost>

.htaccess w folderze strony:

RewriteEngine On
RewriteRule ^kim-jestem$ o-mnie.html [L]
echo '127.0.0.1 notatki.test' | sudo tee -a /etc/hosts
sudo a2ensite notatki.test
sudo a2enmod rewrite
sudo apachectl configtest && sudo systemctl restart apache2
curl http://notatki.test/kim-jestem
Syntax OK
<h1>O mnie: admin w budowie</h1>

Pamiętaj o AllowOverride All, bo inaczej karteczka .htaccess wisi na drzwiach, a nikt jej nie czyta. Celowa literówka, ServerNme:

AH00526: Syntax error on line 2 of /etc/apache2/sites-enabled/notatki.test.conf:
Invalid command 'ServerNme', perhaps misspelled or defined by a module not included in the server configuration

I domyślny serwer:

sudo apachectl -S | grep 'default server'
curl -s -H 'Host: cokolwiek.test' http://127.0.0.1/ | grep -o '<title>.*</title>'
         default server localhost (/etc/apache2/sites-enabled/000-default.conf:1)
<title>Apache2 Ubuntu Default Page: It works</title>

Nieznany gość ląduje na stronie domyślnej. Czyli recepcja działa jak powinna: jak nie wie, do kogo przyszedłeś, wysyła Cię do holu.

Co dalej

Wszystkie nasze strony gadają otwartym tekstem, a każdy po drodze może podsłuchać. W następnej lekcji dokładamy kłódkę: HTTPS, certyfikat z Let's Encrypt przez certbota, przekierowanie z 80 na 443 i SELinux na Red Hacie, który potrafi zablokować stronę, mimo że prawa plików są idealne.