Apache z HTTPS
Po tej lekcji: kursant włącza HTTPS w Apache, rozumie, co robi certbot --apache, ustawia przekierowanie z 80 na 443, sprawdza certyfikat i odnawianie, a na RHEL umie nadać plikom strony kontekst SELinux httpd_sys_content_t.
Wstęp
przeglądarka z adresem http://kawiarnia.test i czerwonym napisem „Niezabezpieczona”
Twoja strona działa. Ale przeglądarka obok adresu pisze „Niezabezpieczona”. Klient kawiarni widzi to i myśli: „aha, tu mi ukradną kartę”. Nawet jeśli na stronie jest tylko menu i zdjęcie sernika.
I przeglądarka ma rację. Wszystko, co leci po HTTP, leci otwartym tekstem. Hasło do panelu, formularz kontaktowy, ciasteczko z sesją. Każdy po drodze, w kawiarnianym Wi-Fi, u dostawcy internetu, może to przeczytać. HTTP to pocztówka: listonosz, sąsiad i pies sąsiada mogą przeczytać, co piszesz.
Dziś zamieniamy pocztówkę na list w zaklejonej kopercie. HTTPS, kłódka w przeglądarce, certyfikat za darmo i automatyczne odnawianie, żebyś nie musiał o tym pamiętać.
HTTPS w jednym zdaniu
O tym, jak działa szyfrowanie i certyfikaty, mówiliśmy w lekcji o certyfikatach. Szybkie przypomnienie: HTTPS to HTTP w zaklejonej kopercie TLS. Serwer pokazuje certyfikat, czyli dowód osobisty strony podpisany przez urząd, któremu ufa przeglądarka. Przeglądarka sprawdza dowód, uzgadnia z serwerem tajny klucz i dalej rozmawiają szyfrem. HTTPS działa na porcie 443, HTTP na 80.
W Apache za HTTPS odpowiada moduł mod_ssl.
[Ubuntu] Jest w pakiecie apache2, trzeba go tylko włączyć:
sudo a2enmod ssl
sudo systemctl restart apache2[RHEL] Osobny pakiet:
sudo dnf install mod_ssl
sudo systemctl restart httpdNa RHEL mod_ssl od razu dorzuca /etc/httpd/conf.d/ssl.conf z przykładową konfiguracją HTTPS. Na Ubuntu przykład czeka w sites-available/default-ssl.conf. I na obu: otwórz port 443 w zaporze.
sudo ufw allow 'Apache Full' # Ubuntu: 80 i 443
sudo firewall-cmd --permanent --add-service=https # RHEL
sudo firewall-cmd --reloadNa Ubuntu w ports.conf siedzi to:
Listen 80
<IfModule ssl_module>
Listen 443
</IfModule>Czyli Apache otwiera okienko 443 dopiero wtedy, kiedy masz włączony moduł SSL. Ładne: nie płacisz za okienko, którego nie używasz.
Certbot, czyli darmowy urząd certyfikacji z kurierem
Skąd wziąć certyfikat? Kiedyś się go kupowało, wypełniało formularze i ręcznie wklejało pliki. Dziś jest Let's Encrypt, urząd certyfikacji, który wydaje certyfikaty za darmo, automatycznie. A certbot to kurier, który chodzi do tego urzędu w Twoim imieniu: zamawia certyfikat, udowadnia, że domena jest Twoja, odbiera certyfikat, wkłada go do Apache i pilnuje odnawiania.
Instalacja:
sudo apt install certbot python3-certbot-apache # Ubuntu
sudo dnf install certbot python3-certbot-apache # RHEL, z repozytorium EPELNa RHEL certbota nie ma w podstawowych repozytoriach, trzeba włączyć EPEL, czyli dodatkowe pakiety budowane przez społeczność Fedory.
Warunek, bez którego nic nie zadziała: prawdziwa domena z rekordem DNS wskazującym na Twój serwer i otwarty port 80 z internetu. Na kawiarnia.test z /etc/hosts certbot nie zadziała, bo urząd w internecie nie ma pojęcia, gdzie jest Twoje /etc/hosts. Na maszynie wirtualnej w domu bez publicznego adresu też nie. To ćwiczenie na serwer w chmurze z domeną.
A teraz cała magia, jedna komenda:
sudo certbot --apache -d kawiarnia.pl -d www.kawiarnia.plCo się wtedy dzieje:
- Certbot znajduje VirtualHosta z
ServerName kawiarnia.pl. - Let's Encrypt mówi: „udowodnij, że to Twoja domena. Połóż pod adresem
http://kawiarnia.pl/.well-known/acme-challenge/...taki plik”. Certbot na chwilę dokłada regułę do Apache, urząd przychodzi przez port 80 i sprawdza. Jak listonosz, który zostawia awizo i sprawdza, czy ktoś je podniósł spod tego adresu. - Certyfikat ląduje w
/etc/letsencrypt/live/kawiarnia.pl/:fullchain.pemto certyfikat razem z łańcuchem pośrednich urzędów,privkey.pemto klucz prywatny. - Certbot tworzy nowy plik strony, na Ubuntu
kawiarnia.pl-le-ssl.conf, z<VirtualHost *:443>i ścieżkami do certyfikatu, i włącza go. - I dorzuca przekierowanie z 80 na 443. W tej wersji certbota przekierowanie jest domyślnie włączone przy
--apache, a wyłączasz je flagą--no-redirect.
Przy pierwszym uruchomieniu zapyta jeszcze o adres email (powiadomienia o problemach) i zgodę na regulamin. Do automatów są flagi --email, --agree-tos i -n, czyli bez zadawania pytań.
Zrób to sam: HTTPS z własnym certyfikatem
Skoro na .test certbot nie zadziała, zróbmy w domu to, co certbot robi za nas. Najpierw certyfikat podpisany samemu sobie, tak zwany self-signed. To jak legitymacja, którą sam sobie wydrukowałeś. Szyfrowanie jest prawdziwe, ale nikt poza Tobą jej nie uzna.
sudo mkdir -p /etc/ssl/kawiarnia
sudo openssl req -x509 -newkey rsa:2048 -nodes -days 30 \
-keyout /etc/ssl/kawiarnia/klucz.pem -out /etc/ssl/kawiarnia/cert.pem \
-subj '/CN=kawiarnia.test' \
-addext 'subjectAltName=DNS:kawiarnia.test,DNS:www.kawiarnia.test'
sudo chmod 600 /etc/ssl/kawiarnia/klucz.pemKlucz prywatny 600, bo to on jest kluczem do koperty. Apache czyta go jako root przy starcie, więc pracownicy www-data nie muszą mieć do niego dostępu i nie powinni.
Teraz drugi VirtualHost, tym razem na porcie 443, plik kawiarnia.test-ssl.conf:
<VirtualHost *:443>
ServerName kawiarnia.test
ServerAlias www.kawiarnia.test
DocumentRoot /var/www/kawiarnia.test/public
SSLEngine on
SSLCertificateFile /etc/ssl/kawiarnia/cert.pem
SSLCertificateKeyFile /etc/ssl/kawiarnia/klucz.pem
<Directory /var/www/kawiarnia.test/public>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/kawiarnia_error.log
CustomLog ${APACHE_LOG_DIR}/kawiarnia_access.log combined
</VirtualHost>Trzy nowe linijki: SSLEngine on włącza szyfrowanie dla tego skrzydła, a dwie kolejne mówią, gdzie leży certyfikat i klucz. Przy certyfikacie z certbota byłyby to /etc/letsencrypt/live/kawiarnia.pl/fullchain.pem i privkey.pem.
sudo a2ensite kawiarnia.test-ssl
sudo apachectl configtest && sudo systemctl reload apache2Test:
curl https://kawiarnia.test/curl: (60) SSL certificate problem: self-signed certificateI bardzo dobrze. curl zachowuje się jak przeglądarka: „tej legitymacji nikt nie podpisał, nie wierzę”. Mamy dwa wyjścia:
curl -k https://kawiarnia.test/ # nie sprawdzaj, tylko na testy!
curl --cacert /etc/ssl/kawiarnia/cert.pem https://kawiarnia.test/karta # zaufaj temu jednemu<h1>Kawiarnia: dzis espresso za 5 zl</h1>
<h1>Menu: espresso, latte, sernik</h1>-k to wyłączenie sprawdzania, czyli „wpuszczam każdego, kto pokaże cokolwiek”. Do testów w laboratorium, nigdy w skryptach na produkcji. --cacert jest uczciwszy: „ufam dokładnie temu certyfikatowi”. I zauważ: .htaccess z przekierowaniem /karta działa też po HTTPS, bo to ten sam folder.
Sprawdzamy certyfikat tak, jak w lekcji o certyfikatach:
echo | openssl s_client -connect kawiarnia.test:443 -servername kawiarnia.test 2>/dev/null \
| openssl x509 -noout -subject -datessubject=CN = kawiarnia.test
notBefore=Nov 3 15:34:22 2026 GMT
notAfter=Dec 3 15:34:22 2026 GMT30 dni, tak jak zamówiliśmy.
Przekierowanie z 80 na 443
Mamy dwie wersje strony: zwykłą i szyfrowaną. Ale ludzie dalej wpisują kawiarnia.test bez https:// i lądują na pocztówce. Więc na porcie 80 zostawiamy już tylko portiera, który mówi: „proszę, wejście od drugiej strony budynku”.
Najprostsza wersja, w VirtualHoście na porcie 80:
Redirect permanent / https://kawiarnia.test/A tak robi to certbot. Zajrzałem do jego kodu źródłowego, dokładnie te linijki dopisuje do VirtualHosta na porcie 80:
RewriteEngine on
RewriteCond %{SERVER_NAME} =kawiarnia.test [OR]
RewriteCond %{SERVER_NAME} =www.kawiarnia.test
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]Po ludzku: „jeśli gość przyszedł do kawiarni albo do www.kawiarni, to cokolwiek chce (^ pasuje do wszystkiego), odeślij go pod ten sam adres, tylko przez https, kodem 301”. %{REQUEST_URI} to ścieżka, o którą prosił, więc nikt nie ląduje na stronie głównej zamiast w menu. NE to „nie psuj znaków specjalnych w adresie”.
Wstawiłem to u siebie i sprawdziłem:
curl -sI 'http://kawiarnia.test/karta?stolik=5' | grep -E 'HTTP|Location'
curl -sLk http://kawiarnia.test/kartaHTTP/1.1 301 Moved Permanently
Location: https://kawiarnia.test/karta?stolik=5
<h1>Menu: espresso, latte, sernik</h1>Ścieżka i parametry przeżyły przeprowadzkę. -L w curlu każe iść za przekierowaniem, jak przeglądarka.
Odnawianie
Certyfikat z Let's Encrypt jest dziś ważny 90 dni. I będzie krócej: Let's Encrypt ogłosił skracanie do 64 dni od lutego 2027 i do 45 dni od lutego 2028. Odnawianie ręczne odpada, bo kiedyś zapomnisz, a zapomina się zawsze w piątek wieczorem.
Dobra wiadomość: pamiętasz budziki z lekcji 87? Pakiet certbota sam instaluje budzik. Na Ubuntu to timer systemd:
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=43200
Persistent=trueDwa razy dziennie, z losowym opóźnieniem do 12 godzin, odpala certbot -q renew. Certbot sprawdza wszystkie certyfikaty i odnawia tylko te, którym zostało mało czasu. Na systemach bez systemd ten sam pakiet ma zapasowy wpis w /etc/cron.d/certbot.
Sprawdzasz tak:
systemctl list-timers | grep certbot
sudo certbot certificates # jakie mam certyfikaty i do kiedy
sudo certbot renew --dry-run # próba generalna odnowienia--dry-run przechodzi całą procedurę na serwerze testowym i niczego nie zapisuje. Rób to po każdej zmianie w konfiguracji Apache, bo odnawianie przez --apache potrzebuje działającego portu 80.
[RHEL] Certbot z EPEL przynosi timer certbot-renew.timer, ale domyślnie wyłączony. Bez tego jednego kroku certyfikat po prostu wygaśnie, a Ty dowiesz się o tym od klienta:
sudo systemctl enable --now certbot-renew.timerSELinux: strażnik z listą etykiet
Na Ubuntu to koniec. Na Red Hacie jest jeszcze jeden strażnik. Pamiętasz, że uprawnienia to klucze do pokojów? SELinux to dodatkowy ochroniarz, który oprócz klucza sprawdza naklejkę na drzwiach. Apache ma przepustkę „mogę czytać tylko pokoje z naklejką httpd_sys_content_t”. Jak na drzwiach jest inna naklejka, ochroniarz nie wpuści, choćby klucz pasował idealnie.
⚠️ Ćwicz to na maszynie wirtualnej z RHEL, Rocky albo Almą. Nie wyłączaj SELinuxa „bo przeszkadza”. To jak wyjęcie baterii z czujnika dymu, bo piszczy przy smażeniu.
Naklejki podglądasz flagą -Z:
ls -Z /var/www/html/index.htmlW wyniku zobaczysz typ httpd_sys_content_t. Wszystko, co powstaje w /var/www, dostaje tę naklejkę automatycznie, bo system ma regułę „w tym miejscu takie naklejki”.
A teraz piekarnia z poprzedniej lekcji, w /srv/piekarnia. Na RHEL dasz Require all granted, prawa 644, i dalej 403. Bo pliki w /srv nie mają naklejki dla Apache. Naprawa w dwóch krokach, dokładnie tak, jak w dokumentacji Red Hata:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/piekarnia(/.*)?"
sudo restorecon -Rv /srv/piekarnia/Pierwsza komenda dopisuje regułę do spisu: „folder /srv/piekarnia i wszystko pod nim ma mieć naklejkę httpd_sys_content_t”. (/.*)? to wyrażenie regularne na „i wszystko w środku”. Druga komenda przechodzi po plikach i nakleja to, co mówi spis. Samo semanage niczego nie zmienia na plikach, samo chcon zmienia, ale nie przetrwa pełnego przeetykietowania systemu. Dlatego zawsze para: semanage fcontext i restorecon.
Najczęstsza pułapka na RHEL: robisz stronę w domowym folderze i przenosisz ją mv do /var/www/html. mv zabiera ze sobą starą naklejkę, user_home_t, i Apache dostaje 403. cp tworzy nowy plik, który dostaje naklejkę miejsca docelowego. Jak już przeniosłeś:
sudo restorecon -Rv /var/www/html/Skąd wiedzieć, że to SELinux, a nie zwykłe prawa? Zajrzyj do dziennika audytu:
sudo ausearch -m AVC -ts recentWpis avc: denied { read } z comm="httpd" to ochroniarz, który kogoś zatrzymał. Do tego jeszcze dwie rzeczy, które się przydadzą:
- folder, do którego strona ma zapisywać (na przykład przesłane zdjęcia), dostaje typ
httpd_sys_rw_content_t; - jeśli Apache ma słuchać na nietypowym porcie, na przykład 8081, SELinux też musi o tym wiedzieć:
sudo semanage port -a -t http_port_t -p tcp 8081.
Jak to działa pod spodem
Kiedy przeglądarka łączy się z portem 443, zanim padnie choć jedno słowo HTTP, odbywa się uścisk dłoni TLS. Przeglądarka już w pierwszej wiadomości podaje nazwę strony, do której idzie. To rozszerzenie nazywa się SNI. Dzięki niemu Apache wie, który certyfikat pokazać, zanim rozszyfruje nagłówek Host. Bez SNI na jednym adresie IP dałoby się postawić tylko jedną stronę HTTPS. To dlatego w openssl s_client dajemy -servername.
Dalej mod_ssl używa biblioteki OpenSSL do szyfrowania, a po rozszyfrowaniu żądanie idzie dokładnie tą samą drogą co zwykły HTTP: VirtualHost, DocumentRoot, <Directory>, .htaccess. Szyfrowanie to tylko koperta, list w środku jest ten sam.
Certbot w /etc/letsencrypt/live/ trzyma dowiązania do najnowszej wersji plików, a stare wersje leżą w /etc/letsencrypt/archive/. Dlatego Apache zawsze wskazuje na live/...: po odnowieniu dowiązanie pokazuje nowy plik, a certbot przeładowuje Apache.
Ściąga flag
| Komenda | Flaga | Co robi |
|---|---|---|
a2enmod | ssl | włącz mod_ssl (Ubuntu) |
dnf install | mod_ssl | zainstaluj mod_ssl (RHEL) |
certbot | --apache -d DOMENA | zdobądź certyfikat i skonfiguruj Apache |
certbot | --redirect / --no-redirect | z przekierowaniem 80→443 albo bez |
certbot | --test-cert (--staging) | certyfikat testowy, bez limitów |
certbot | --hsts | dodaj nagłówek HSTS |
certbot | certificates | lista certyfikatów i daty ważności |
certbot | renew --dry-run | próba odnowienia |
openssl req | -x509 -nodes -days N | certyfikat self-signed do testów |
openssl x509 | -noout -dates | daty ważności certyfikatu |
curl | -k | nie sprawdzaj certyfikatu (tylko testy) |
curl | --cacert PLIK | ufaj temu certyfikatowi |
curl | -L | idź za przekierowaniami |
ls | -Z | pokaż kontekst SELinux |
semanage fcontext | -a -t TYP "ŚCIEŻKA(/.*)?" | dopisz regułę etykiet |
restorecon | -Rv | nadaj etykiety zgodnie z regułami |
ausearch | -m AVC -ts recent | ostatnie odmowy SELinuxa |
⚠ Typowy błąd
Piszesz pierwszy VirtualHost na 443, robisz configtest i:
AH00526: Syntax error on line 6 of /etc/apache2/sites-enabled/kawiarnia.test-ssl.conf:
Invalid command 'SSLEngine', perhaps misspelled or defined by a module not included in the server configurationStary znajomy z poprzedniej lekcji, tylko w innym przebraniu. Apache nie zna słowa SSLEngine, bo zna je dopiero mod_ssl, a ten jest wyłączony. Naprawa: [Ubuntu] sudo a2enmod ssl, [RHEL] sudo dnf install mod_ssl, potem configtest i restart.
Drugi klasyk: certbot kończy się błędem weryfikacji domeny. Prawie zawsze to jedna z trzech rzeczy: rekord DNS nie wskazuje jeszcze na serwer, port 80 jest zamknięty w zaporze (albo w panelu dostawcy chmury), albo na porcie 80 siedzi coś innego niż Apache. Sprawdzasz dig +short kawiarnia.pl, sudo ss -ltnp | grep ':80 ' i z zewnątrz curl -I http://kawiarnia.pl/. Port 80 musi zostać otwarty także po włączeniu HTTPS, bo przez niego idzie każde odnowienie.
✎ Zadanie
Postaw na jednym Apache dwie strony (dwa VirtualHosty) pod dwiema nazwami dopisanymi do /etc/hosts. Sprawdź apachectl configtest i curl każdej nazwy.
Pokaż rozwiązanie
Zadanie było takie: jeden Apache, dwie strony, dwie nazwy w /etc/hosts. Najpierw nazwy. Udajemy, że mamy domeny, a tak naprawdę mówimy komputerowi: „obie te nazwy to ty sam”.
echo '127.0.0.1 sklep.test blog.test' | sudo tee -a /etc/hosts
sudo mkdir -p /var/www/sklep /var/www/blog
echo '<h1>Sklep z kapciami</h1>' | sudo tee /var/www/sklep/index.html
echo '<h1>Blog o kapciach</h1>' | sudo tee /var/www/blog/index.htmlTeraz dwa pliki VirtualHost. [Ubuntu] w /etc/apache2/sites-available/sklep.conf, [RHEL] w /etc/httpd/conf.d/sklep.conf. Drugi plik identyczny, tylko z blog.
<VirtualHost *:80>
ServerName sklep.test
DocumentRoot /var/www/sklep
ErrorLog ${APACHE_LOG_DIR}/sklep_error.log
CustomLog ${APACHE_LOG_DIR}/sklep_access.log combined
</VirtualHost>[RHEL] Tam nie ma zmiennej APACHE_LOG_DIR, więc piszesz wprost logs/sklep_error.log, czyli /var/log/httpd/. I pamiętasz o SELinuksie z lekcji 90: pliki w /var/www/... mają mieć kontekst httpd_sys_content_t. Jak je tworzysz w /var/www, dostają go same.
sudo a2ensite sklep blog # [Ubuntu]; na RHEL pliki w conf.d działają od razu
sudo apachectl configtest
sudo systemctl reload apache2 # [RHEL] httpd
curl http://sklep.test
curl http://blog.testSyntax OK
<h1>Sklep z kapciami</h1>
<h1>Blog o kapciach</h1>A teraz smaczek. Wpisz curl http://127.0.0.1. Dostaniesz… bloga. Czemu? Bo jak Apache nie rozpozna nazwy, daje pierwszego VirtualHosta z listy. A pliki czyta alfabetycznie. blog jest przed sklep. Sprawdza to apachectl -S:
*:80 is a NameVirtualHost
default server blog.test (/etc/apache2/sites-enabled/blog.conf:1)
port 80 namevhost blog.test (/etc/apache2/sites-enabled/blog.conf:1)
port 80 namevhost sklep.test (/etc/apache2/sites-enabled/sklep.conf:1)Sklep z kapciami przegrał z blogiem przez kolejność w alfabecie. Jak w szkole przy odpytywaniu. Dlatego wielu adminów nazywa domyślną stronę 000-default. Żeby zawsze była pierwsza.
Co dalej
Apache to recepcjonista, który sam obsługuje gości. W następnej lekcji poznasz Nginx i nową rolę: odźwiernego, który sam nic nie gotuje, tylko przekazuje gości do kuchni za ścianą. Reverse proxy, rozkładanie ruchu na kilka serwerów i HAProxy.