12.09.202211 min

Michał Giza Administrator systemów / DevOps

Serwer WWW na Linuxie - jak stworzyć stabilny hosting

Sprawdź, jak skonfigurować serwer oparty na Linuxie, by stał się hostingiem strony WWW dostępnym dla wielu użytkowników.

Serwer WWW na Linuxie - jak stworzyć stabilny hosting

Wydaje się, że uruchomienie witryny internetowej na wybranym Linuxie to proste zadanie. Można tak powiedzieć, natomiast takie domyślne środowiska poddają się przy bardziej złożonych projektach i zastosowaniach. Przede wszystkim brakuje separacji na poziomie użytkowników i pewnej elastyczności z tym związanej. Istotna jest też kwestia poziomu bezpieczeństwa. Zakładając, że wszystkie nasze projekty działają z prawami użytkownika www-data, umożliwiamy w ten sposób dostęp do tych wszystkich projektów. O ile w przypadku lokalnych, deweloperskich maszyn nie jest to może poważne zagrożenie, to jeśli tworzymy współdzielony hosting dla pracowników, problem staje się poważny. Dodatkowo udostępnianie dostępu SSH czy FTP dla użytkownika www-data zdecydowanie nie wygląda profesjonalnie.

Dlatego zwykle w przypadku hostingów stosuje się podejście, że każdy klient to osobny użytkownik w systemie. Ułatwia to zarządzanie, możemy np. łatwo ustalić limity zajętości miejsca (tzw. quota) i każdemu użytkownikowi przydzielić własną pulę PHP. Dodam też, że podobny schemat działania jest dobrą praktyką również na serwerach dedykowanych. Oczywiście to wszystko wymaga konfiguracji, której szczegóły zależą wyłącznie od naszego zastosowania.

Z reguły dostępne hostingi są już odpowiednio skonfigurowane, więc nasze zadanie ogranicza się do przypisania domen, ustawienia wersji PHP, utworzenia bazy i ewentualnych dalszych kroków. To wszystko wykonujemy w intuicyjnych panelach oferowanych przez usługodawcę. Najpopularniejsze to cPanel, DirectAdmin czy Plesk, chociaż większe firmy posiadają autorskie panele zarządzania, co w pewnym stopniu jest dla nich korzystne (m.in. stosunkowa łatwość rozszerzenia możliwości w dowolnej chwili i brak problemów z licencjonowaniem).

W tym tekście chciałbym skupić się jednak na lokalnych instancjach, które np. stawiamy w firmie dla naszych pracowników. Administratorzy odpowiedzialni za hosting mają własne „reguły” i zwykle wiedzą, jak prawidłowo przygotować cały system, na pewno używają też narzędzi do automatyzacji (provisioning np. za pomocą Ansible). Natomiast zamierzam przedstawić szereg ustawień, które całkowicie sprawdzają się w wewnętrznych zastosowaniach (chociaż należy mieć świadomość, że nie we wszystkich). Moje środowisko działa na Ubuntu 22.04, natomiast te ogólne porady można wdrożyć w innych systemach, różni się jedynie ilość i treść użytych poleceń.


Wstępna konfiguracja

Jeśli mamy gotową maszynę z czystym systemem, pierwszym krokiem będzie przypisanie stałego adresu IP. Jest to konieczne, ponieważ korzystanie z DHCP może wiązać się z otrzymywaniem różnych adresów (można co prawda zarezerwować adres, ale lokalne przypisanie adresu jest mimo wszystko lepszą praktyką). W lokalnym serwerze DNS odpowiednimi rekordami skierujemy nasze domeny na adres maszyny. Kiedy ten adres będzie zmieniony, nie połączymy się.

Data i czas powinny być zgodne ze strefą czasową, w której znajduje się serwer. Najskuteczniejszy sposób ustawienia to wykonanie dpkg-reconfigure tzdata i wybranie odpowiedniego obszaru geograficznego oraz miasta.

W dalszej kolejności sugeruję utworzenie użytkownika z uprawnieniami root, który będzie służył do zarządzania systemem. Nazwa „admin” wydaje się odpowiednia, jasno opisuje zastosowanie, ale to tylko nazwa. Należy pamiętać o ustawieniu odpowiednio silnego hasła, jak również o dodaniu klucza publicznego do ~/.ssh/authorized_keys (np. za pomocą ssh-copy-id), aby nie wpisywać tego hasła przy logowaniu. Można ustawić, aby hasło nie było też potrzebne podczas wykonywania poleceń z sudo, aczkolwiek myślę, że nie warto ułatwiać atakującym zadania przy ewentualnym wycieku naszego klucza prywatnego.

Ważne jest dbanie o aktualność pakietów i systemu. Wiadomo, że zwłaszcza w środowiskach produkcyjnych obowiązuje zasada „działa, nie ruszać”, a aktualizacja może zmienić zawartość plików konfiguracyjnych i/lub ostatecznie uniemożliwić działanie aplikacji. Natomiast regularne aktualizacje to poważny element. Należy je wcześniej zaplanować i (co najważniejsze) posiadać działające kopie zapasowe całego systemu plików. Dlatego na tym etapie kiedy system nie jest jeszcze właściwie w żadnym stopniu skonfigurowany, upgrade zdecydowanie nie zaszkodzi.

Wspomniałem o kopiach zapasowych i teraz jest chyba odpowiedni moment na ich implementację. Rozwiązań jest wiele, nie można podać jednego, które sprawdzi się dla wszystkich. Trzeba zwrócić uwagę na możliwość centralnego zarządzania, aby wiedzieć, kiedy kopia nie została wykonana i naprawić błędy. Istotna jest też opcja tworzenia kopii przyrostowych, aby rozmiary kopii były mniejsze w stosunku do kopii pełnej. Podstawową rzeczą jest przechowywanie tych kopii na oddzielnych dyskach twardych.

Jeśli nasz serwer to maszyna wirtualna, sprawdzi się też system snapshotów czy „klonowania”. Po skończonej konfiguracji składników serwera możemy utworzyć klon naszej maszyny i w przyszłości tworzyć na jego podstawie nowe wirtualne instancje w razie potrzeby (zmieniając właściwie jedynie adres IP i hasło dla konta do zarządzania).

Myśląc o kopiach zapasowych, warto jednocześnie pamiętać o kwestii dysków. Zaleca się, aby katalogi /home i np. /var/log były przechowywane poza głównym dyskiem systemowym. W przypadku awarii czas przywrócenia działania będzie krótszy, ponieważ odzyskujemy jedynie system, pozostałe dyski tylko montujemy. Generalnie w tych katalogach odbywa się najwięcej operacji odczytu / zapisu, co też ma wpływ na przyrost zajętości. Jeśli użytkownik (o limitowaniu zajętości wspominam w sekcji Zarządzanie użytkownikami) lub logi wykorzystają 100% wolnego miejsca, a wszystkie dane przechowujemy na jednej partycji, system ma tendencję do generowania problemów podczas uruchamiania, a jego działanie również może zostać uniemożliwione.

Do /etc/fstab bezpieczniej jest dodać UUID dysku / partycji zamiast jego nazwy typu /dev/sdb. Dzięki temu mamy pewność, że podczas uruchamiania urządzenia zostaną poprawne zamontowane we właściwych ścieżkach.


Katalog /home został przeniesiony na drugi dysk (/dev/sdb1).

Ostatnie kluczowe zagadnienie na wstępie to monitoring. Najczęściej informacje o problemach z działaniem otrzymujemy w pierwszej kolejności od „klientów końcowych”, ale użytkownik nie ma dostępu do wszystkich danych. Alarmy wysyłane przez agenta do monitoringu pozwolą reagować na powstające problemy i tym samym zapobiec awariom czy przestojom.


Instalacja pakietów i dalsza konfiguracja

Proste strony pisane wyłącznie w HTML, CSS i JavaScript raczej nie są już tworzone, przynajmniej w skali, która wymaga instancji dla pracowników. Do działania większości projektów niezbędny jest interpreter języka PHP, który można bardzo łatwo zainstalować.

Nie jest to jednak trywialny wybór. To od naszego zastosowania zależy, które wersje konkretnie będą wymagane. Podniesienie wersji w przyszłości nie jest problematyczne, chodzi o kwestię bieżących projektów, które mogą być pisane dla wybranej. Można zainstalować wszystkie, ale jeśli np. wersja 5.6 nie będzie przez nas używana, nie ma większego sensu w jej pobieraniu.

PHP może działać w trybie PHP-FPM lub jako moduł Apache. Tutaj należy dodać pewne podsumowanie obu „trybów”:

  • PHP-FPM działa ad hoc (konieczna instalacja pakietu phpX-fpm i dodanie location) tylko z NGINX, do obsługi przez Apache potrzebny jest moduł fcgid;
  • PHP jako moduł Apache nie zadziała z NGINX, natomiast obsługa przez Apache ogranicza się do instalacji modułu;
  • PHP-FPM nie obsługuje zmiany wartości ustawień przez .htaccess (php_value), można dodać jedynie obsługę plików INI o określonej nazwie (user_ini.filename);
  • tylko PHP-FPM wspiera PHP pools, czyli możliwość działania procesów PHP jako wybrany użytkownik zamiast domyślnego www-data.


Osobiście preferuję PHP-FPM ze względu na PHP pools, co znacząco ułatwia sprawę z uprawnieniami (użytkownik nie musi być członkiem grupy www-data).

W przypadku Ubuntu i innych dystrybucji w systemowym repozytorium jest dostępna wyłącznie jedna wybrana wersja PHP. Dlatego należy dodać zewnętrzne repozytorium (ppa:ondrej/php dla systemów opartych na Debianie). PHP ma wiele rozszerzeń, ich lista też zależy od naszego zastosowania, ale takie najbardziej podstawowe to xml, mysql, pgsql, zip, gd, intl, mbstring, curl, xmlrpc, soap, imap, ldap, tidy, bcmath, imagick i sqlite3.

PHP ma wiele kluczowych ustawień typu memory_limit, max_execution_time, upload_max_filesize, post_max_size, max_input_vars. Można ustawić to wszystko w pliku php.ini, ale wartości wpisane w .htaccess czy .user.ini mają „pierwszeństwo”, stąd czasem lepiej pozostawić ten problem użytkownikom. Ale jak wszystko zależy to od naszego zastosowania.


Wpływ ustawień w pliku .user.ini na wartości PHP: w środkowej kolumnie widoczne są wartości lokalne, w prawej — wartości pobrane z serwera.

Musimy także wybrać serwer WWW. Tutaj wybór ogranicza się właściwie do NGINX i Apache, to tradycyjne rozwiązania i takowe spotkamy u wielu dostawców. Istotne dla nas jest to, że nawet proste operacje (dodanie przekierowania, ustawienie Basic Auth) w NGINX muszą być wykonane na poziomie jego konfiguracji, co wymaga uprawnień, jakich nie powinniśmy przydzielać zwykłym użytkownikom. Lepszym wyborem może być Apache, gdzie bardzo wiele możemy ustawić odpowiednimi wpisami w pliku .htaccess. Z kolei NGINX oferuje większą wydajność. Jeśli wszystkie nasze projekty są oparte na tym samym CMS czy frameworku, proponowałbym instalację NGINX. Wtedy dla każdej domeny będzie ta sama główna konfiguracja, ewentualne zmiany dla poszczególnych projektów można wprowadzić w określonym pliku. Oczywiście nic nie stoi na przeszkodzie, aby używać dwóch serwerów WWW, przykładowo NGINX jako zewnętrzne proxy dla lokalnie działającego Apache.

Jeśli wybraliśmy Apache, pewnie będziemy zmuszeni do aktywacji kilku modułów. Podstawowy to rewrite umożliwiający obsługę .htaccess (AllowOverride All też należy ustawić). Inne typowe to alias, filters, headers. Przydatny czasem będzie także ModSecurity.


Przykładowa konfiguracja virtual host Apache: ustawiono lokalne nasłuchiwanie na porcie 1000 i dodano obsługę PHP-FPM.


Zablokowanie próby wykonania funkcji phpinfo() przez domyślne reguły ModSecurity.

Ostatni problem w tym zakresie to dostęp dla użytkowników. Może to być FTP, wtedy dbamy tylko o aktywację chroot (czyli ograniczenie dostępu wyłącznie do wybranego katalogu i jego subkatalogów) oraz być może o szyfrowanie połączenia (FTP domyślnie nie jest protokołem szyfrowanym). Jeśli zależy nam na tych dwóch elementach, prawdopodobnie lepszym rozwiązaniem będzie SFTP (wtedy chroot należy aktywować w /etc/ssh/sshd_config i ustawić odpowiednie uprawnienia na katalogu użytkownika). Trudniejsza sprawa to dostęp SSH, który może być niezbędny, aby wykonać pewne podstawowe polecenia, używać systemu kontroli wersji Git czy uzyskać możliwość edycji cron’a. Należy w takim przypadku pamiętać o zablokowaniu możliwości odczytu pewnych danych i wykonania kilku poleceń.


Udostępniony katalog FTP, dostęp jest ograniczony wyłącznie do katalogu domowego użytkownika.

System operacyjny już na swoim poziomie ogranicza nieuprzywilejowanego użytkownika, tzn. nie wykona on poleceń wymagających wyższych uprawnień (shutdown, passwd dla innego konta, systemctl itd.), ale domyślnie kilka „wrażliwych” narzędzi jest dla niego dostępnych. To przede wszystkim netcat, który pozwoli na wystawienie nowego portu (o ile firewall nie zablokuje dostępu), who, czyli polecenie listujące aktualnie zalogowanych użytkowników, last, który wyświetla listę ostatnich logowań czy też netstat służący do wypisywania informacji o połączeniach. Zalecam ustawić odpowiedni chmod dla tych poleceń (np. 600). Dodatkowo musimy pamiętać o katalogach typu /etc, /home czy /var/log, gdzie są przechowywane różne kluczowe informacje, jak również o możliwości wyświetlania procesów innego użytkownika (to blokuje się poprzez mount -o remount,rw,hidepid=2 /proc). Niestety dostęp SSH dla każdego stanowi pewne wyzwanie dla administratora.


Próby wykonania poleceń who, last, nc i listowania zawartości katalogu /home. Polecenie ps wyświetla wyłącznie procesy należące do aktualnego użytkownika.


Baza danych

Dobrą praktyką jest wystawienie bazy danych na oddzielnej maszynie. Jeśli jednak zamierzamy wykorzystać jeden serwer, powinniśmy przenieść katalog z bazami danych na drugi dysk. Bazy zwykle cechują się szybkim przyrostem zajętości, stąd migracja może okazać się niezbędna. Inna rzecz to optymalizacja. Przykładowo MySQL domyślnie działa na bardzo skromnych ustawieniach, zdecydowanie warto podnieść wartości np. związane z silnikiem InnoDB.

Wiele osób zmienia domyślny sposób logowania użytkownika root, zamiast autoryzacji poprzez socket, ustawiają oni hasło. Nie wydaje mi się to bezpiecznym i szczególnie wygodnym rozwiązaniem, ale obie metody działają, dlatego porównywanie nie ma chyba większego sensu. Bazy najlepiej tworzyć z dedykowanym użytkownikiem dla każdego projektu, ale jeśli jedno konto zawiera kilka projektów, można nadać jednemu użytkownikowi uprawnienia do kilku baz.

Sprawdza się też udostępnienie phpMyAdmin czy phpPgAdmin, najlepiej pod osobną domeną. Dostępne są narzędzia typu MySQL Workbench, pgAdmin, natomiast dostęp przez przeglądarkę w pewnych sytuacjach będzie najlepszym i najszybszym wyborem. Dostęp można ograniczyć do wybranych adresów, jeśli to zewnętrzna maszyna dostępna z Internetu.


Dodatkowe narzędzia

Nie jestem zwolennikiem instalacji narzędzi typu Composer, npm czy Yarn na produkcyjnych środowiskach, ale wiem, że przydają się na deweloperskich maszynach. Dlatego warto zainstalować je globalnie. Wiadomo też, że nie wszystkie aplikacje są pisane wyłącznie w PHP. Dlatego konieczna może okazać się obsługa Python’a czy ASP.NET przez serwer WWW. Wydaje mi się, że łatwiej to osiągnąć przy użyciu Apache (Python to w zasadzie dodatkowe moduły i odpowiednie ustawienie WSGI, do ASP.NET wystarczy Mono i dodanie dyrektyw MonoAutoApplication, MonoServerPath oraz MonoApplication do konfiguracji virtual host w Apache).


Django uruchomiony poprzez WSGI w Apache.

Niezwykle ważne jest zadbanie o to, aby po restarcie serwera wszystkie usługi (SSH, NGINX, Apache, PHP-FPM, bazy danych i inne) uruchomiły się automatycznie. W systemach z rodziny Debian nie ma z tym problemu, ale już w CentOS czy RedHat usługi nie uruchamiają się samodzielnie nawet po instalacji. Dlatego należy pamiętać o wykonaniu systemctl enable <nazwa_usługi>.


Zarządzanie użytkownikami

W tej kwestii nie ma raczej większej filozofii, ale istnieje kilka podejść do tematu. Domyślnie katalogi domowe są tworzone w /home i odpowiadają nazwie użytkownika. Można jednak ustalić inny schemat i przykładowo tworzyć te katalogi w lokalizacji /home/users. Kolejna rzecz to porządek w tych katalogach. Mocno sugeruję wymusić następujący układ:

  • /home/users/<użytkownik>/_domains/<pierwsza_domena>
  • /home/users/<użytkownik>/_domains/<druga_domena>


lub (gdy użytkownik ma przypisaną tylko jedną domenę):

  • /home/users/<użytkownik>/public_html


DocumentRoot nie powinien nigdy wskazywać bezpośrednio na katalog domowy, ponieważ może on zawierać wrażliwe pliki typu .bash_history czy .mysql_history. Dodatkowo układ z dedykowanymi katalogami dla każdej domeny zapobiega wpływowi .htaccess z jednego katalogu na drugi.


Podgląd schematu katalogów, dane serwisów znajdują się w odpowiednich katalogach w _domains, dodatkowo przeznaczono dedykowany katalog dla logów.

Sugeruję też wystawiać logi z działania serwera WWW dla danej domeny w katalogu właściciela tej domeny. Pozwala to oszczędzić miejsce w katalogu z logami, jak również umożliwia znalezienie błędów w działaniu z poziomu użytkownika.

Metoda dostępu dla użytkowników zależy od naszego ustawienia. Jeśli to tylko FTP nie będziemy mogli zbyt wiele ustawić, ale już w przypadku SSH warto wymusić autoryzację za pomocą kluczy (aczkolwiek nie warto od razu zmieniać PasswordAuthentication na wartość no).

Niezależnie od tego, czy tworzymy hosting dla klientów, czy serwer dla pracowników, ustawienie quota będzie rozsądnym pomysłem. W ten sposób zakładamy limit dozwolonej przestrzeni na dysku dla konkretnego konta. Po jego przekroczeniu zapis nie będzie możliwy (dane nie zostają nadpisane), należy zwiększyć limit lub usunąć część danych.


Limity zajętości dla poszczególnych użytkowników.


Podsumowanie

Działanie serwera z wieloma usługami i użytkownikami to ciekawy i obszerny temat. Na pewno nie poruszyłem wszystkich możliwych zagadnień. Starałem się jednak opisać moje podejście do podobnych realizacji, które generalnie jest spójne z podejściem stosowanym na hostingach. Oczywiste jest, że nie każdy będzie mógł wdrożyć wszystkie przedstawione sugestie, ale o to właśnie chodzi, aby tworzyć własne rozwiązania i pewne zestawy czynności do wykonania (oraz automatyzować ten proces).

<p>Loading...</p>