Dealownia
Menu
sprawdzono – jak sprawdzamy oferty

WordPress na własnym VPS: nginx, PHP-FPM i MariaDB krok po kroku

WordPress na Ubuntu 24.04: nginx, PHP 8.3-FPM, MariaDB 10.11, certyfikat Let's Encrypt, bezpieczne uprawnienia plików, WP-CLI, kopie bazy i aktualizacje.

Hosting i stronyzaktualizowano 7 min czytania

Redakcja serwisu Dealownia · ceny i fakty ze stron dostawców, jak sprawdzamy

WordPress pod adresem https://example.pl, na nginx, PHP-FPM i MariaDB, bez panelu hostingowego. Pliki strony należą do Twojego użytkownika, a serwer WWW może zapisywać tylko w katalogu z mediami, więc podatna wtyczka nie podmieni Ci rdzenia WordPressa. Aktualizacje i kopie robi WP-CLI z crona. Komendy są dla Ubuntu 24.04 LTS, bo jego pakiety spełniają wymagania WordPressa bez dodatkowych repozytoriów.

Czego potrzebujesz

  • VPS z Ubuntu 24.04 LTS przygotowany jak w poradniku od zera: użytkownik deploy z sudo, klucz SSH, ufw z otwartymi portami 80 i 443. Docker do WordPressa nie jest potrzebny.
  • Minimum: 1 vCPU, 1 GB RAM z plikiem swap, 15–20 GB dysku. WordPress nie podaje wymagań sprzętowych; podręcznik dla hostingów zaleca memory_limit 256 MB dla typowej strony i 512 MB lub więcej dla sklepu WooCommerce czy page buildera. Przy sklepie albo kilku stronach na jednym serwerze weź 2 GB RAM.
  • Domenę example.pl z rekordami A (i AAAA) dla example.pl i www.example.pl wskazującymi serwer. Jak je ustawić, opisuje poradnik o DNS.

Debian 12 ma PHP 8.2, czyli poniżej zalecanej wersji. Na Debianie wybierz wersję 13 – ma PHP 8.4 (gniazdo php8.4-fpm.sock, katalog /etc/php/8.4) i MariaDB 11.8; reszta komend jest taka sama.

Na ten zestaw wybierz plan z co najmniej 1 GB RAM (z plikiem swap), a wygodniej 2 GB.

Pełne porównanie planów jest w rankingu VPS.

Krok 1. Pakiety

Aktualne wymagania WordPressa to PHP 8.3 lub nowszy oraz MariaDB 10.11 lub MySQL 8.0. Ubuntu 24.04 ma dokładnie PHP 8.3 i MariaDB 10.11.

bash
sudo apt update
sudo apt -y install nginx mariadb-server php8.3-fpm php8.3-mysql php8.3-curl \
  php8.3-xml php8.3-mbstring php8.3-zip php8.3-intl php8.3-gd php8.3-opcache php-imagick

Rozszerzenia pochodzą z listy „wymagane i mocno zalecane” w podręczniku dla hostingów: mysqli, curl, dom/xml, mbstring, zip, intl, imagick, opcache. json, hash, exif i fileinfo są już w pakiecie podstawowym PHP.

Krok 2. Baza danych

bash
sudo mariadb-secure-installation

Na Ubuntu root bazy loguje się przez gniazdo systemowe (sudo mariadb), więc na pytanie o zmianę na unix_socket możesz odpowiedzieć „n”, a na usunięcie anonimowych kont, testowej bazy i zdalnego logowania roota – „Y”. Potem utwórz bazę i osobnego użytkownika:

bash
DB_PASS=$(openssl rand -hex 24); echo "$DB_PASS"
sudo mariadb <<EOF
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp'@'localhost' IDENTIFIED BY '${DB_PASS}';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wp'@'localhost';
FLUSH PRIVILEGES;
EOF

Zapisz hasło – za chwilę trafi do wp-config.php. MariaDB z Ubuntu słucha tylko na 127.0.0.1, więc portu 3306 nie otwieraj w ufw.

Krok 3. Ustawienia PHP

Własne ustawienia trzymaj w osobnym pliku, żeby aktualizacja pakietu ich nie nadpisała:

bash
sudo tee /etc/php/8.3/fpm/conf.d/99-wordpress.ini >/dev/null <<'EOF'
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 120
cgi.fix_pathinfo = 0
EOF
sudo systemctl restart php8.3-fpm

Domyślne upload_max_filesize w PHP to 2 MB, czyli za mało na zwykłe zdjęcie z telefonu. cgi.fix_pathinfo = 0 zaleca dokumentacja WordPressa dla nginx.

Krok 4. WP-CLI i pliki WordPressa

WP-CLI instalujesz jednym plikiem:

bash
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
chmod +x wp-cli.phar && sudo mv wp-cli.phar /usr/local/bin/wp

Pobierz WordPressa jako deploy, nie jako root:

bash
sudo mkdir -p /var/www/example.pl && sudo chown deploy:www-data /var/www/example.pl
cd /var/www/example.pl
wp core download --locale=pl_PL
wp config create --dbname=wordpress --dbuser=wp --dbpass='WKLEJ-HASŁO' --locale=pl_PL
wp config set DISALLOW_FILE_EDIT true --raw
wp config set DISALLOW_FILE_MODS true --raw

DISALLOW_FILE_EDIT wyłącza edytor plików w panelu, a DISALLOW_FILE_MODS – instalowanie i aktualizowanie z panelu. To świadomy wybór: wtyczki i aktualizacje robisz przez WP-CLI (krok 8), a proces PHP nie musi mieć prawa zapisu do kodu. Ta stała wyłącza też automatyczne aktualizacje w tle, dlatego w kroku 8 zastępuje je cron. Jeśli wolisz klikać w panelu, pomiń drugą linię i nadaj www-data zapis do całego katalogu – wygodniej, ale włamanie przez wtyczkę może wtedy zmienić każdy plik.

Krok 5. Uprawnienia plików

Dokumentacja WordPressa zaleca katalogi 755 lub 750, pliki 644 lub 640, a wp-config.php 440 lub 400. Właścicielem jest deploy, grupą www-data (użytkownik PHP-FPM i nginx):

bash
cd /var/www/example.pl
sudo chown -R deploy:www-data .
sudo find . -type d -exec chmod 755 {} +
sudo find . -type f -exec chmod 644 {} +
sudo chmod 440 wp-config.php
sudo mkdir -p wp-content/uploads
sudo chown -R www-data:www-data wp-content/uploads

PHP czyta kod, ale zapisuje tylko w uploads. wp-config.php z hasłem do bazy czyta wyłącznie właściciel i grupa www-data. Wybraliśmy 755/644 zamiast 750/640, bo pliki z aktualizacji robionych przez WP-CLI powstają z grupą deploy, a nie www-data – przy 640 nginx przestałby je widzieć. Jeśli jakaś wtyczka potrzebuje własnego katalogu z zapisem (cache, logi), daj go jej tak samo jak uploads, a nie cały wp-content.

Krok 6. nginx

Plik /etc/nginx/sites-available/example.pl:

nginx
server {
    listen 80;
    listen [::]:80;
    server_name example.pl www.example.pl;

    root /var/www/example.pl;
    index index.php;
    client_max_body_size 64m;

    location ~* /(?:uploads|files)/.*\.php$ {
        deny all;
    }

    location ~ /\.(?!well-known) {
        deny all;
    }

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

Kolejność ma znaczenie: nginx sprawdza lokalizacje z wyrażeniami regularnymi w kolejności zapisu i bierze pierwszą pasującą. Blokada PHP w uploads musi stać przed ogólnym \.php$, inaczej wrzucony tam skrypt by się wykonał. snippets/fastcgi-php.conf z pakietu Ubuntu zawiera try_files $fastcgi_script_name =404, więc nieistniejące pliki .php nie trafiają do PHP.

bash
sudo ln -s /etc/nginx/sites-available/example.pl /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

Krok 7. Certyfikat i instalacja

Najpierw HTTPS, potem instalacja – wtedy WordPress od początku zapisze adresy z https://:

bash
sudo apt -y install certbot python3-certbot-nginx
sudo certbot --nginx -d example.pl -d www.example.pl
cd /var/www/example.pl
wp core install --url=https://example.pl --title="Moja strona" \
  --admin_user=redakcja --admin_email=ty@example.pl

Bez --admin_password WP-CLI wygeneruje losowe hasło i wypisze je na ekranie. Nie używaj loginu admin – to pierwsze, co próbują boty. Sprawdź, czy przekierowanie z www i z HTTP działa: curl -sI http://www.example.pl | head -3.

Krok 8. Kopie i aktualizacje z crona

Skrypt /home/deploy/wp-maint.sh:

bash
#!/usr/bin/env bash
set -euo pipefail
SITE=/var/www/example.pl
BACKUP=/home/deploy/backup/example.pl
mkdir -p "$BACKUP" && chmod 700 "$BACKUP"
cd "$SITE"
wp db export "$BACKUP/db-$(date +%F).sql" --quiet
tar -czf "$BACKUP/uploads-$(date +%F).tar.gz" -C "$SITE/wp-content" uploads
find "$BACKUP" -type f -mtime +14 -delete
wp core update --minor --quiet
wp plugin update --all --quiet
wp theme update --all --quiet
wp language core update --quiet
bash
chmod +x /home/deploy/wp-maint.sh
/home/deploy/wp-maint.sh && ls -lh /home/deploy/backup/example.pl
( crontab -l 2>/dev/null; echo "15 4 * * * /home/deploy/wp-maint.sh" ) | crontab -

Katalog kopii ma prawa 700, bo zrzut bazy zawiera m.in. skróty haseł użytkowników – nie ustawiamy tu umask 077 dla całego skryptu, bo pliki z aktualizacji wtyczek musi czytać PHP. Kopia jest robiona przed aktualizacją, więc po nieudanej aktualizacji wtyczki masz bazę z tej samej nocy. --minor aktualizuje automatycznie tylko wydania poprawkowe; nową główną wersję (wp core update) instaluj ręcznie po przeczytaniu informacji o wydaniu. Pakiety systemowe, w tym PHP, aktualizuje unattended-upgrades opisany w liście kontrolnej zabezpieczeń. Kopia na tym samym dysku nie chroni przed awarią serwera – wyślij ją poza VPS, jak w poradniku o restic i rclone.

Najczęstsze problemy

  • WordPress prosi o dane FTP przy instalacji wtyczki. Tak reaguje, gdy nie może zapisywać plików. Przy układzie z tego poradnika to oczekiwane – instaluj przez wp plugin install nazwa --activate. Jeśli przycisk w panelu w ogóle się pokazuje, brakuje DISALLOW_FILE_MODS.
  • 502 Bad Gateway. nginx nie znajduje gniazda PHP-FPM: sprawdź ls /run/php/ i czy wersja w fastcgi_pass zgadza się z zainstalowaną (na Debianie 13 to php8.4-fpm.sock). Logi: sudo tail /var/log/nginx/error.log.
  • „Błąd podczas nawiązywania połączenia z bazą danych”. Literówka w haśle w wp-config.php albo użytkownik utworzony dla innego hosta. Sprawdź mariadb -u wp -p wordpress -e 'SELECT 1'.
  • Pętla przekierowań po włączeniu HTTPS. Adres w ustawieniach ma http:// albo wtyczka wymusza inną domenę. Popraw: wp option update home https://example.pl i to samo dla siteurl.
  • Nie da się wgrać większego pliku (413 albo błąd w panelu). client_max_body_size w nginx i upload_max_filesize/post_max_size w PHP muszą być co najmniej tak duże jak plik. Po zmianie PHP zrestartuj php8.3-fpm.

Co dalej

Przenosisz istniejącą stronę? Migracja z hostingu współdzielonego opisuje kolejność: kopia, przeniesienie, zmiana DNS. Dołóż monitoring dostępności z Uptime Kuma, żeby wiedzieć o awarii przed czytelnikami. Jeśli wahasz się, czy WordPress na VPS ma sens, porównaj koszty w poradniku hosting współdzielony czy VPS.

Powiązane poradniki