5.10.202211 min

Michał Giza Administrator systemów / DevOps

Jak zainstalować Wordpress, żeby wycisnąć z niego maksa

Sprawdź, na czym polega wzorcowe wdrożenie WordPressa i o czym należy pamiętać, konfigurując go do swojego projektu.

Jak zainstalować Wordpress, żeby wycisnąć z niego maksa

W tym artykule chciałbym przedstawić dobre praktyki podczas wdrażania nowych lub rozbudowywania istniejących serwisów opartych o najpopularniejszy system zarządzania treścią, czyli WordPress.

W praktyce opisane porady można zastosować do dowolnego projektu, chociaż wiadomo, że będzie wymagane odpowiednie ich dostosowanie. Natomiast praktyczne zaprezentowanie przykładowego wdrożenia z pewnością ma wartość dodaną.


WordPress

To system zarządzania treścią napisany przede wszystkim w języku PHP. Jego pierwsza wersja została opublikowana w 2003 roku.Przez ten czas system był aktualizowany i przystosowywany do aktualnych standardów.

Typowa obsługa, czyli dodawanie treści czy zarządzanie użytkownikami jest stosunkowo prosta, więc podstawowy „próg wejścia” bywa niski. Nie pozostaje to bez znaczenia w kwestii popularności — według danych z 2022 roku 43% wszystkich witryn działa pod kontrolą WordPress.

Ogólne wymagania techniczne WordPress nie są szczególnie zaawansowane, bez większych przeszkód możemy uruchomić naszą witrynę na praktycznie każdym hostingu.  Jest to najodpowiedniejsza opcja dla osób bez szerszej wiedzy technicznej, chociaż warto mieć minimalną świadomość działania serwera WWW.

Bardzo często panele do zarządzania posiadają autoinstalatory aplikacji, w których wystarczy podać dane naszej przyszłej witryny (zwykle jej nazwę i dane konta administratora), co umożliwia szybkie i łatwe uruchomienie WordPress.


Kiedy wybrać WordPress?

W rzeczywistości dla prostych witryn charakteryzujących się relatywnie niskim ruchem i przyrostem zajętości (media, rekordy w bazie danych) hosting może być świetnym wyborem.

Za techniczną stronę działania w całości odpowiada dostawca, naszym zadaniem właściwie powinno być zadbanie o własne kopie zapasowe, aby nie polegać jedynie na tych wykonywanych na hostingu.

Jesteśmy jednak mocno ograniczeni, tzn. nie mamy możliwości większego dostosowania parametrów, np. bazy danych. Oprócz standardowej optymalizacji WordPress zwykle pozostaje nam wyłącznie plik .htaccess, w którym możemy np. zwiększyć wartości ustawień PHP czy dodać kompresję GZIP.

Dlatego duże serwisy powinny zostać odpowiednio wdrożone, aby mogły zachować sprawność i ciągłość działania. Nie można podać porad, które sprawdzą się w każdym przypadku. Tutaj przydaje się wiedza i doświadczenie, warto też sprawdzać oraz weryfikować różne konfiguracje w testowych środowiskach.

Poniżej opisuję poszczególne kroki przykładowego wdrożenia. Skupiam się raczej na samym procesie, pomijając podstawowe czynności. Duża szczegółowość i podejście „step-by-step" nie mają większego sensu, a potrafią znacząco wydłużyć tekst o treści, które raczej nie będą interesujące dla szerszego grona odbiorców.

Po prostu zakładam, że zostały one wykonane i maszyny są gotowe do właściwej pracy. Polecam mój poprzedni tekst, gdzie wspomniałem o pierwszych krokach podczas konfiguracji serwerów WWW na Linuxie od podstaw.


Serwer WWW

Mam taką zasadę, że unikam Apache, gdy wszystkie serwisy na serwerze są oparte na tym samym oprogramowaniu. Wdrażamy jedynie WordPress, więc najlepszym wyborem pod kątem wydajności naturalnie będzie NGINX + PHP-FPM. Konfiguracja NGINX dla WordPress jest prosta, oparta na oficjalnym przykładzie:

server {
	listen 80;
	server_name bulldogjob.lan;
	return 301 https://bulldogjob.lan$request_uri;
	server_tokens off;
}

server {
	server_tokens off;
	listen 443 ssl;
	server_name bulldogjob.lan;

	ssl_certificate /etc/nginx/ssl/bulldogjob.lan.crt;
	ssl_certificate_key /etc/nginx/ssl/bulldogjob.lan.key;

	root /home/domains/bulldogjob.lan/public_html;
	index index.php;

	location / { try_files $uri $uri/ /index.php?$args; }
	location ~ \.php$ {
		include fastcgi_params;
		fastcgi_intercept_errors on;
		fastcgi_pass unix:/run/php/bulldogjob-7.4.sock;
		fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
	}
	location ~ ^/(favicon.ico|robots.txt)/ { access_log off; }
		
	access_log /home/domains/bulldogjob.lan/logs/access.log;
	error_log /home/domains/bulldogjob.lan/logs/error.log error;
}


Nie zawiera ona żadnych skomplikowanych reguł. Wymuszono przekierowanie połączenia na wersję szyfrowaną, wskazano pliki certyfikatu, ustawiono ścieżkę do plików strony i dodano obsługę PHP. Zapytania do plików favicon.ico i robots.txt nie będą logowane.

Dyrektywa server_tokens off; wyłącza wysyłanie w nagłówku Server wersji NGINX i nazwy dystrybucji systemu (nie widać ich też na domyślnych stronach błędów).


Logrotate

Można zauważyć, że dla plików logów ustawiliśmy inną niż domyślną (/var/log/nginx) lokalizację. Logi są niezwykle istotne, pomagają wykryć i rozwiązać różne problemy z działaniem serwisu.

Zapis logów w dedykowanym katalogu w katalogu domowym to wygodna praktyka. Natomiast zapominając o ustawieniu mechanizmu logrotate sprawimy, że dane będą zapisywane w dwóch plikach (access.log, error.log), które po pewnym czasie osiągną znaczne rozmiary.

Utrudnia to przede wszystkim wyszukiwanie informacji w logach, również tych z określonego przedziału czasu.

Ustawienie logrotate może ograniczyć się do utworzenia kopii pliku /etc/logrotate.d/nginx i zmiany ścieżek. Inna opcja to dodanie własnego pliku, np. o następującej treści:

/home/domains/bulldogjob.lan/logs/*.log {
        su root bulldogjob
        rotate 14
        daily
        compress
}


Opcja daily sprawi, że skrypt będzie uruchamiał się o godzinie 6:25 każdego dnia. Czas wykonania możemy sprawdzić i ewentualnie zmienić w pliku /etc/crontab:

25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )


PHP

Aktualne wersje WordPress są kompatybilne z PHP 7.4 i nowszymi. Ta kompatybilność dotyczy samego WP, bywają problemy z działaniem niektórych motywów czy wtyczek z PHP 8.0 i PHP 8.1. Tutaj kwestia testowania i ewentualnie poprawiania kodu w celu zapewnienia zgodności z wersją PHP używaną przez nas.

Skoro za obsługę ruchu HTTP(S) odpowiada NGINX, nie mamy innego wyboru, jak użycie PHP-FPM. Oczywiste jest, że serwis nie będzie działał jako użytkownik www-data, dlatego standardowo tworzymy nową PHP pool:

[bulldogjob]
user = bulldogjob
group = bulldogjob
listen = /run/php/bulldogjob-7.4.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30


Domyślne wartości są zdecydowanie zbyt małe, pozostawione będą wąskim gardłem i powodem niedostępności nawet podczas minimalnego wzrostu ruchu w witrynie.

Dodam, że również NGINX umożliwia zmianę ustawień PHP poprzez fastcgi_param PHP_VALUE — wtedy obejmują one wyłącznie server block, w którym zostały zdefiniowane.


MySQL

WordPress domyślnie obsługuje bazy MySQL, minimalna wspierana wersja to 5.7. W miarę możliwości sugeruję wdrożenie bazy na osobnej instancji, najlepiej używając Percona Server for MySQL.

Interesujące porównanie obu rozwiązań można znaleźć w dokumentacji. Ogólnie Percona Server dla MySQL zapewnia większą wydajność, nie musimy poświęcać dużej ilości czasu na optymalizację działania serwera MySQL.

Wystawienie bazy danych na oddzielnej maszynie ma też znaczenie w kontekście bezpieczeństwa. Dostęp zewnętrzny może dotyczyć wyłącznie serwera WWW, instancja z bazą może działać wtedy całkowicie w sieci lokalnej.

Zdecydowanie warto pomyśleć również o utworzeniu klastra MySQL. Percona posiada własne rozwiązanie XtraDB Cluster, w którym stosunkowo łatwo można taki klaster „zbudować”.

Do load balancingu na poziomie MySQL służy np. ProxySQL. O ile efekty zastosowania ProxySQL są naprawdę zadowalające, to prostota wdrożenia nie do końca.

Przede wszystkim całość konfiguruje się poprzez wykonywanie zapytań do bazy danych i koniecznie trzeba pamiętać o zapisaniu danych. Przykładowo w ten sposób dodamy jeden z serwerów MySQL:

insert into main.mysql_servers (hostgroup_id, hostname, port) values (1, '10.0.0.110', 3306);
save mysql servers to disk;


Varnish

Varnish to serwer cache, który obsługuje całość ruchu do naszego serwisu. Ten temat jest niezwykle złożony, odpowiednia konfiguracja wymaga dobrej znajomości struktury witryny.

Nie wszystkie elementy nadają się do przechowywania w pamięci podręcznej. W przypadku statycznych stron jest znacznie łatwiej, biorąc również pod uwagę, że twórcy Varnish udostępniają przykładowe pliki VCL (format obsługiwany przez tę usługę) dla popularnych systemów zarządzania treścią, w tym WordPress.

Varnish nadaje się także do zastosowania jako rozwiązanie typu failover czy load balancer. Jeśli zainstalujemy ten serwer na odseparowanej maszynie (tak powinno być), to prawdopodobne jest, że w przypadku awarii instancji serwera WWW i/lub bazy danych, odwiedzający nie odczuje niedostępności w pełnym stopniu.

Część elementów będzie znajdować się w cache, więc zostanie pobrana z serwera Varnish, zamiast z właściwego serwera WWW. Podczas zwykłej pracy zmniejszamy w ten sposób także obciążenie maszyn.

W teorii load balancing można osiągnąć dodając dosłownie kilka linii do pliku VCL. W praktyce sytuacje, kiedy to zadziała, należą do rzadkości. Zobaczmy na ten fragment:

vcl 4.1;

import std;
import directors;

backend web01 {
    .host = "10.0.0.5";
    .port = "443";
}

backend web02 {
    .host = "10.0.0.20";
    .port = "443";
}

sub vcl_init {
    new vdir = directors.round_robin();
    vdir.add_backend(web01);
    vdir.add_backend(web02);    
}

sub vcl_recv {
    set req.backend_hint = vdir.backend();

# some stuff


Czyli do przykładowej konfiguracji dla WordPress importujemy moduł directors (odpowiada za load balancing), definiujemy oba serwery i w sekcji vcl_init wskazujemy obsługę ruchu z użyciem metody round-robin.

Teraz, jeśli domena wskazuje na serwer Varnish i ktoś połączy się protokołem HTTP, otrzyma kod błędu 400 (Bad Request) z taką treścią: The plain HTTP request was sent to HTTPS port.

Varnish przekazuje wszystkie zapytania na port 443 maszyn z NGINX. Rozwiązaniem niestety nie jest wyłącznie zmiana portu w konfiguracji na 80, ponieważ wtedy to przeglądarki zaczną zwracać błędy związane z SSL.

Rozwiązaniem natomiast będzie zainstalowanie NGINX na serwerze z Varnish. Czyli zamierzamy wprowadzić następujący układ:


Rozwiązanie problemu przekierowania zapytań z portu 443 maszyn z NGINX. Instalacja NGINX na serwerze z Varnish


Jeśli zdecydujemy się na takowe rozwiązanie, na serwerze z Varnish dodajemy poniższą konfigurację NGINX:

server {
        listen 80;
        server_name bulldogjob.lan;
        return 301 https://bulldogjob.lan$request_uri;
        server_tokens off;
}

server {
        server_tokens off;
        listen 443 ssl;
        server_name bulldogjob.lan;

        ssl_certificate /etc/nginx/ssl/bulldogjob.lan.crt;
        ssl_certificate_key /etc/nginx/ssl/bulldogjob.lan.key;

        location / {
                proxy_pass http://127.0.0.1:6081;
                proxy_read_timeout 7200;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
        }
}


W pliku VCL dla Varnish zmieniamy port z 443 na 80 i dodatkowo na serwerach web konfigurujemy wyłącznie obsługę HTTP. Ostatnia konieczna zmiana to dodanie poniższej linii do wp-config.php:

if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') { $_SERVER['HTTPS'] = 'on'; }


Powyższa konfiguracja Varnish jest poprawna, natomiast sprawia, że jego działanie jest ograniczone do roli load balancer. Aby osiągnąć rozwiązanie failover, wystarczy do definicji poszczególnych „backend” dodać tzw. healt check:

.probe = {
        .url = "/index.php";
        .timeout = 1s;
        .interval = 5s;
        .window = 5;
        .threshold = 3;
    }


Niestety nie jest to pomysł pozbawiony wad. Zdefiniowaliśmy hosty jako adresy IP, a WordPress działa pod nazwą domeny. Varnish wysyła więc w naszym przykładzie swoje zapytania pod 10.0.0.5/index.php. NGINX próbuje w takim wypadku odczytać ten plik z katalogu /var/www/html (default server block, w którym server_name ma wartość „_” — czyli obejmuje wszystkie adresy i domeny niezdefiniowane w pozostałych konfiguracjach dodanych przez administratora).

Nie do końca wzorowym, ale działającym rozwiązaniem jest ustawienie dowiązania symbolicznego do pliku podanego jako url (w sekcji .probe) w lokalizacji /var/www/html.

Jeśli ten plik przestanie „odpowiadać”, zwykle będzie to oznaczać problemy z danym hostem. W konsekwencji ruch zostanie przekierowany do pozostałych. Varnish w dalszym ciągu będzie weryfikował stan wszystkich maszyn. Jeśli problematyczny serwer poprawnie odpowie na health check, ruch do niego zostanie wznowiony.


NFS

Z użyciem wielu serwerów WWW wiąże się kwestia współdzielenia danych. Z kodem strony nie ma trudności, zmiany wystarczy wdrażać jednocześnie na każdym, najlepiej poprzez CI/CD pipelines. Jednak zawartości przesyłanej przez użytkowników nie możemy w ten sposób udostępniać pomiędzy kilkoma maszynami.

Tutaj z pomocą przychodzi NFS. Poleca się przeznaczyć dodatkową instancję jedynie jako storage dla danych, która będzie udostępniała odpowiednie katalogi (dla zwykłych wdrożeń WordPress to wyłącznie wp-content/uploads, chociaż można się zastanowić nad przeniesieniem również niektórych podkatalogów wp-content).

W tym celu na dedykowanej instancji instalujemy pakiet nfs-kernel-server. Tworzymy w dowolnej lokalizacji katalog na przesyłane dane. Dla porządku można utworzyć go w /uploads, gdzie możemy też zamontować dodatkowy dysk.

Aby uzyskać możliwie bezpieczne uprawnienia na udziale, należy ustawić jego właściciela jako UID użytkownika, na którym działa WordPress (UID pobierzemy poleceniem id, do zmiany właściciela służy chown). Następnie w /etc/exports dodajemy wpis w postaci:

/uploads <adres_sieci>/<maska_cidr>(rw)


na przykład: /uploads 10.0.0.0/24(rw)

Wykonujemy jeszcze exportfs -a i restartujemy usługę NFS: systemctl restart nfs-kernel-server. Teraz na docelowych hostach instalujemy pakiet nfs-common i tworzymy w /wp-content katalog uploads, w którym montujemy udział:

ubuntu@web02:/home/domains/bulldogjob.lan/public_html/wp-content$ sudo mount 10.0.0.10:/uploads uploads


Jeśli katalog uploads wcześniej istniał, to tymczasowo zmieniamy jego nazwę, montujemy udział i przenosimy dane. Poleceniem df -h możemy zweryfikować poprawność:

ubuntu@web02:~$ df -h
Filesystem                         Size  Used Avail Use% Mounted on
tmpfs                               97M  1,1M   96M   2% /run
/dev/mapper/ubuntu--vg-ubuntu--lv  8,1G  5,1G  2,6G  67% /
tmpfs                              485M     0  485M   0% /dev/shm
tmpfs                              5,0M     0  5,0M   0% /run/lock
/dev/sda2                          1,7G  245M  1,4G  16% /boot
tmpfs                               97M  4,0K   97M   1% /run/user/1000
10.0.0.10:/uploads                 8,1G  4,9G  2,8G  64% /home/domains/bulldogjob.lan/public_html/wp-content/uploads


Aby udział został zamontowany automatycznie po restarcie serwera, do /etc/fstab wystarczy dodać:

10.0.0.10:/uploads /home/domains/bulldogjob.lan/public_html/wp-content/uploads nfs defaults 0 0


Należy pamiętać, że NFS nie służy jako dodatkowy dysk. W żadnym wypadku całość serwisu nie może być przechowywana na NFS.

Podczas awarii serwera NFS w takim podejściu serwis przestanie całkowicie odpowiadać, nie będzie też dostępu do danych z poziomu klienta. Natomiast jeśli na NFS hostujemy wyłącznie storage naszej aplikacji, „jedynie” te dane nie będą mogły zostać pobrane, co oznacza mniejszy koszt takiego przestoju.


Redis

Budżetowe realizacje w celu uniknięcia tworzenia nowych instancji mogą użyć lokalnie działającego Redis. WordPress nie wspiera domyślnie tego narzędzia, jednak do dodania obsługi wystarczy wtyczka Redis Object Cache, w której dosłownie jednym kliknięciem aktywujemy Redis. Do działania konieczne jest rozszerzenie PHP redis.

Zapisane klucze dla witryny WordPress w RedisieZapisane klucze dla witryny WordPress.


Wskazówki optymalizacji Wordpress

Oprócz dobrych praktyk obejmujących zadania po stronie operacyjnej nie można zapomnieć o kilku prostych czynnościach do wykonania w samym WordPress. Nie ma potrzeby zachowywania przykładowych wpisów i stron. Domyślnie zainstalowane motywy i wtyczki również można usunąć.

Polecam stosować kompresję przesyłanych grafik, np. za pomocą jednej z wielu dostępnych wtyczek do tego służących. Pozwoli to zaoszczędzić przestrzeń dyskową oraz będzie miało pozytywny wpływ na szybkość ładowania strony.

Zmiany w motywach powinno wykonywać się z użyciem child-themes. Aby wygenerować motyw potomny, wystarczy utworzyć jego katalog w /wp-content/themes i dodać do niego poniższe pliki.

functions.php
<?php
add_action('wp_enqueue_scripts','enqueue_parent_styles');
function enqueue_parent_styles() { wp_enqueue_style( 'parent-style', get_template_directory_uri().'/style.css' ); }
?>

style.css
/*
Theme Name: <nazwa_naszego_motywu>
Template: <nazwa_oryginalnego_motywu>
*/


W celu poprawnego wyświetlania podglądu w zakładce Wygląd -> Motywy należy skopiować dodatkowo plik screenshot.jpg z katalogu oryginalnego motywu.

Kolejna rzecz to kopie zapasowe. Nie trzeba chyba przypominać o ich znaczeniu. Poza kopią całego serwera warto jednak wykonywać kopie WordPress i osobno bazy danych (ułatwia odzyskiwanie w razie potrzeby).

Do ich tworzenia można użyć wtyczki (np. UpdraftPlus czy nawet Duplicator) lub napisać własny skrypt. Istotne kwestie to regularność wykonywania kopii i ich miejsce docelowe — nie powinno być powiązane z tą samą infrastrukturą.

Oprócz typowych zagadnień związanych z bezpieczeństwem, jak używanie silnych haseł czy dbanie o aktualizacje, możemy wdrożyć weryfikację dwuetapową. Działająca wtyczka umożliwiająca integrację z różnymi rozwiązaniami OTP to miniOrange 2 Factor Authentication.


wdrożenie weryfikacji dwuetapowej za pomocą wtyczki umożliwiającej integrację z różnymi rozwiązaniami OTP to miniOrange 2 Factor Authentication

Niektórzy preferują ustawienie własnego adresu logowania zamiast wp-login.php. Służy do tego wtyczka WPS Hide Login. Można ponadto ograniczyć dostęp do panelu logowania jedynie dla wybranych adresów IP na poziomie NGINX.


wtyczka WPS Hide Login do ustawienia własnego adresu logowania zamiast wp-login.php


Podsumowanie

Techniczne aspekty wdrożeń większych projektów potrafią być bardzo interesujące. Nie ulega wątpliwości, że wymagają one pracy i odpowiedniego doświadczenia. Niemniej to wszystko się „zwraca” w postaci stabilnego działania i naszej satysfakcji.

<p>Loading...</p>