Przeniesienie WordPressa na nowy serwer bez przestoju
O powodzeniu decyduje kolejność: najpierw nowy serwer, potem kopia danych, test pod prawdziwą domeną przez plik hosts, certyfikat wystawiony z wyprzedzeniem, a DNS dopiero na końcu. Z drogą powrotną i prawdziwymi komunikatami błędów.
Przeprowadzka WordPressa rzadko rozbija się o pojedyncze polecenie. Rozbija się o kolejność. Kto najpierw przestawia DNS, a dopiero potem kopiuje dane, ten gwarantuje sobie okno, w którym odwiedzający zobaczą albo stronę błędu, albo instalację wypełnioną do połowy. Kto działa odwrotnie, może w spokoju dokończyć nowy serwer, przetestować go pod prawdziwą domeną i zmienić wpis DNS dopiero w tej chwili, w której ma dowód, że wszystko działa.
Ten artykuł opisuje dokładnie taką kolejność, a do tego miejsca, w których w praktyce robi się nieprzyjemnie: dane serializowane w bazie, porównywanie znaków przy przesiadce z MySQL na MariaDB, certyfikaty bez wskazującego wpisu DNS oraz droga powrotna, gdyby jednak coś umknęło.
Przebieg, który nie powoduje przestoju
Stara strona zostaje online do ostatniej chwili. Nic nie jest wyłączane, nic nie jest kasowane. Nowy serwer pracuje równolegle, przechodzi testy i przejmuje ruch dopiero wtedy, gdy wpis DNS zostanie przestawiony.
- Obniż TTL rekordów A i AAAA do 300 sekund, co najmniej 24 do 48 godzin wcześniej.
- Postaw nowy serwer: serwer WWW, PHP, baza danych, użytkownicy, katalogi.
- Skopiuj pliki, skopiuj bazę danych.
- Wystaw certyfikat dla domeny, mimo że DNS wskazuje jeszcze stary serwer.
- Dopisz wpis do pliku hosts na własnym komputerze i przeklikaj stronę pod prawdziwą domeną.
- Tuż przed przełączeniem wykonaj synchronizację różnicową, żeby ostatnie zmiany też trafiły na miejsce.
- Przestaw DNS. Stary serwer zostaw uruchomiony jeszcze przez kilka dni.
Jedyny punkt, w którym teoretycznie powstaje luka, to krok 6. O tym, jak mała ona będzie, decyduje krok 1.
Obniż TTL, zanim wydarzy się cokolwiek innego
TTL mówi resolverom na całym świecie, jak długo wolno im trzymać odpowiedź w pamięci podręcznej. Jeśli stoi na 86400, resolver może serwować twój stary adres IP jeszcze przez 24 godziny po przestawieniu. I jedna rzecz, o której chętnie się zapomina: obniżony TTL zaczyna działać dopiero wtedy, gdy wygaśnie stary. Przy zejściu z 86400 na 300 może minąć cała doba, zanim wszystkie resolvery poznają krótką wartość.
Dlatego to jest pierwsza czynność, a nie ostatnia. dig nie należy w żadnej dystrybucji do podstawowego wyposażenia, a w tym poradniku jest potrzebny nieustannie, zainstaluj go więc na samym początku:
apt install -y bind9-dnsutils
Na Debianie 11, Ubuntu 22.04 i Ubuntu 24.04 pakiet nazywa się jeszcze dnsutils. Od Debiana 12 dnsutils to już tylko atrapa wskazująca na bind9-dnsutils, a na Debianie 13 wręcz czysto wirtualny pakiet bez własnej wersji. Działa jedno i drugie, bo apt sam rozwiązuje jedynego dostawcę, ale bind9-dnsutils to nazwa, która zostanie. Potem sprawdź aktualną wartość:
dig +noall +answer example.com A
dig +noall +answer example.com SOA
Liczba w drugiej kolumnie odpowiedzi A to pozostały TTL. Serwer autorytatywny odpytaj bezpośrednio, inaczej zobaczysz tylko resztkę z cache'u własnego resolvera:
dig @a.ns14.net example.com A +noall +answer
Po przeprowadzce podnieś TTL z powrotem, na 3600 albo więcej. Trwale krótkie wartości TTL generują niepotrzebne obciążenie i wydłużają awarie twojego dostawcy DNS.
Nowy serwer wyposażony pod istniejącą stronę
Tu powstaje najwięcej przykrych niespodzianek, bo dystrybucja na nowym serwerze dostarcza co innego niż stara. Wszystkie polecenia pakietowe w tym poradniku zakładają Debiana albo Ubuntu; na AlmaLinuksie, Rocky Linuksie i Oracle Linuksie nie ma apt, a nazwy pakietów są inne. Stan na lipiec 2026 wygląda tak:
| System | PHP | Baza danych | nginx |
|---|---|---|---|
| Debian 13 | 8.4 | MariaDB 11.8 (brak mysql-server) | 1.26.3 |
| Debian 12 | 8.2 | MariaDB 10.11 | 1.22.1 |
| Ubuntu 24.04 | 8.3 | MySQL 8.0.46 albo MariaDB 10.11 | 1.24.0 |
| Ubuntu 22.04 | 8.1 | MySQL 8.0.46 albo MariaDB 10.6 | 1.18.0 |
Wynikają z tego dwie konsekwencje. Po pierwsze: na Debianie nie dostaniesz mysql-server, tam zawsze pracuje MariaDB. Jeśli stara strona chodziła na MySQL 8, przeprowadzka na Debiana jest jednocześnie zmianą silnika bazy, z bardzo konkretną pułapką opisaną niżej. Po drugie: skok z PHP 8.1 na 8.4 nie dzieje się sam z siebie. Starsze motywy i wtyczki reagują na usunięte funkcje komunikatem Fatal error: Uncaught Error: Call to undefined function albo białą stroną. Jeśli stara instalacja chodziła na PHP 8.1, a ty nie chcesz łączyć przeprowadzki z aktualizacją PHP, spokojniejszym wyborem będzie Ubuntu 22.04 albo wersja PHP z zewnętrznego repozytorium. W tabeli świadomie brakuje Debiana 11: tam php-cli dostarcza jeszcze PHP 7.4.33, czyli wersję bez wsparcia bezpieczeństwa i poniżej tego, co zaleca WordPress. Jako cel przeprowadzki Debian 11 tym samym odpada, chyba że wcześniej podepniesz repozytorium Sury.
Pakiety podstawowe dla typowej instalacji z nginx i PHP-FPM:
apt update
apt install -y nginx php-fpm php-mysql php-xml php-curl php-mbstring php-zip php-gd php-intl
apt install -y mariadb-server mariadb-client rsync curl
Szczegóły o serwerze WWW znajdziesz w poradniku o nginx, a podstawowe zabezpieczenie bazy danych w tekście zabezpieczanie MariaDB i MySQL. Jeśli serwer jest zupełnie świeży, warto wcześniej zajrzeć do listy kontrolnej dla nowych serwerów root.
Załóż bazę danych i użytkownika, z tym samym zestawem znaków co na źródle:
CREATE DATABASE wp_nowy CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_nowy'@'localhost' IDENTIFIED BY 'dlugie-haslo';
GRANT ALL PRIVILEGES ON wp_nowy.* TO 'wp_nowy'@'localhost';
FLUSH PRIVILEGES;
Przeniesienie plików bez rozwalenia uprawnień
Kopiuje się bezpośrednio z serwera na serwer, a nie przez domowe łącze. Najprościej przez rsync ze starego na nowy serwer, z kluczem SSH zamiast hasła:
rsync -az --delete --exclude 'wp-content/cache/' --exclude 'wp-content/uploads/backup*' \
-e 'ssh -p 22' /var/www/html/ root@nowy.ip:/var/www/html/
Pierwszy przebieg może potrwać. Właśnie dlatego robi się go na kilka dni przed przełączeniem, a tuż przed nim jeszcze raz to samo polecenie, które przenosi już tylko różnice.
Po skopiowaniu trzeba wyprostować właściciela i uprawnienia, bo identyfikatory UID różnią się między systemami, a rsync przenosi liczby, nie nazwy:
chown -R www-data:www-data /var/www/html
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;
chmod 640 /var/www/html/wp-config.php
Dwa pliki wylatują przed pierwszym uruchomieniem, o ile w ogóle istnieją: wp-content/object-cache.php oraz wp-content/advanced-cache.php. Oba to drop-iny wtyczek cache'ujących, wskazujące na socket Redisa albo Memcached, którego na nowym serwerze jeszcze nie ma. Efektem byłaby biała strona bez użytecznego komunikatu.
Przeniesienie bazy danych i pułapka porównywania znaków
Zrzut zrobisz przez WP-CLI albo klasycznie. WP-CLI ma tę zaletę, że bierze zestaw znaków i prefiks z pliku wp-config.php:
cd /var/www/html
wp db export /root/wp-dump.sql --add-drop-table
Bez WP-CLI, na aktualnym systemie z MariaDB narzędzie nazywa się mariadb-dump. Stara nazwa mysqldump to od MariaDB 11.0 już tylko przestarzały symlink, który wita komunikatem Deprecated program name. It will be removed in a future release:
mariadb-dump --single-transaction --default-character-set=utf8mb4 wp_stary > /root/wp-dump.sql
Teraz miejsce, na którym przeprowadzka z Ubuntu z MySQL 8 na Debiana z MariaDB regularnie się wykłada. MySQL 8 używa domyślnie porównywania utf8mb4_0900_ai_ci, którego w MariaDB w ogóle nie ma. Import przerywa działanie komunikatem:
ERROR 1273 (HY000) at line 42: Unknown collation: 'utf8mb4_0900_ai_ci'
Naprawa przed importem, bezpośrednio w zrzucie:
sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g; s/utf8mb4_0900_as_cs/utf8mb4_unicode_ci/g' /root/wp-dump.sql
grep -c utf8mb4_unicode_ci /root/wp-dump.sql
Potem import:
mariadb --default-character-set=utf8mb4 wp_nowy < /root/wp-dump.sql
Jeśli po imporcie wszędzie widnieje wiÄ™cej zamiast więcej, zestaw znaków zgubił się przy zrzucie albo przy imporcie. Tego nie naprawia się szukaniem i zamianą, tylko powtórzeniem zrzutu oraz importu z --default-character-set=utf8mb4. Właśnie dlatego stara baza danych jest w tym momencie nadal nietknięta.
Następnie dopasuj dane dostępowe w pliku wp-config.php na nowym serwerze: DB_NAME, DB_USER, DB_PASSWORD, i co najważniejsze DB_HOST, jeśli stara instalacja wskazywała na zdalny serwer bazy danych. Gdy zostanie tam stary adres, zobaczysz Error establishing a database connection, choć lokalnie wszystko działa. Jeśli połączenie nie chce wstać mimo poprawnych danych, pomoże naprawa błędu Access denied for user.
Podmiana domeny i dlaczego REPLACE w SQL rozkłada stronę
Najpierw dobra wiadomość: jeśli zmienia się tylko serwer, a domena zostaje ta sama, w bazie danych nie musisz zamieniać niczego. To właśnie jeden z powodów, dla których test przez plik hosts bije test przez domenę zastępczą. Zamiana staje się konieczna dopiero wtedy, gdy domena faktycznie się zmienia, gdy łączysz przeprowadzkę z przejściem z http na https albo gdy jednak pracujesz na domenie testowej i potem musisz zamienić z powrotem.
A teraz powód, dla którego nigdy nie robi się tego zwykłym poleceniem SQL. WordPress zapisuje opcje, ustawienia widżetów i konfiguracje motywów jako serializowane tablice PHP. Adres URL nie stoi tam nago, tylko z podaną z przodu długością w bajtach:
s:19:"https://example.com"
Jeśli zamienisz example.com przez UPDATE ... REPLACE() na nowa-domena.pl, ciąg zrobi się dłuższy, a liczba 19 zostanie na miejscu. PHP nie potrafi już potem rozpakować tablicy, unserialize() zwraca false, a dane ustawienie przepada. Typowe objawy: zniknięte widżety, zresetowane opcje motywu, puste pozycje menu, a w logach Notice: unserialize(): Error at offset.
WP-CLI rozwiązuje to poprawnie, bo rozpakowuje dane, zamienia je i zapisuje z poprawioną informacją o długości. Zawsze zaczynaj od --dry-run:
wp search-replace 'https://stara-domena.pl' 'https://nowa-domena.pl' --all-tables --precise --dry-run --report-changed-only
Co robią poszczególne przełączniki: --all-tables obejmuje także tabele, które nie trzymają się prefiksu WordPressa, na przykład od wtyczek sklepowych albo formularzowych. --precise wymusza przetwarzanie w PHP zamiast w SQL, jest wolniejsze, ale niezawodnie radzi sobie z danymi serializowanymi. --report-changed-only skraca wyjście do tabel z rzeczywistymi trafieniami.
Jeśli podgląd wygląda sensownie, uruchom to samo polecenie bez --dry-run. Potem przychodzi przebieg, który pomija prawie każdy poradnik: kreatory stron w rodzaju Elementora zapisują swoje treści w bazie jako JSON, a ukośniki są tam maskowane. Zamiana https://stara-domena.pl po prostu nie znajdzie https:\/\/stara-domena.pl. Dlatego drugi przebieg:
wp search-replace 'https:\/\/stara-domena.pl' 'https:\/\/nowa-domena.pl' --all-tables --precise --report-changed-only
Przy Elementorze do kompletu należy jeszcze regeneracja plików CSS w zapleczu, w sekcji Narzędzia, inaczej wygenerowane arkusze stylów dalej będą wskazywać na starą domenę.
Dwie rzeczy, do których search-replace nie sięga: stałe w pliku wp-config.php oraz multisite. Jeśli stoi tam define('WP_HOME', 'https://stara-domena.pl');, nadpisuje to każdą wartość z bazy, a ty szukasz godzinami w złym miejscu. Przy multisite potrzebujesz dodatkowo --network i musisz poprawić domeny w tabelach wp_blogs oraz wp_site.
I ostrzeżenie co do prefiksu tabel: nie zmieniaj go przy przeprowadzce. Prefiks siedzi także w nazwie opcji wp_user_roles oraz w polach meta użytkownika wp_capabilities i wp_user_level. Kto zmieni nazwy samych tabel, ten wprawdzie nadal się zaloguje, ale zaraz potem dostanie Sorry, you are not allowed to access this page. i zostanie bez administratora.
Wystawienie certyfikatu, zanim DNS zacznie wskazywać
Tkwi tu problem jajka i kury. Zwyczajna walidacja HTTP w Let's Encrypt wymaga, żeby nazwa domeny wskazywała już na walidowany serwer. A w tym momencie jeszcze tak nie jest, bo domena ma przecież serwować dalej starą stronę. Kto mimo to uruchomi certbot --nginx, dostanie:
Certbot failed to authenticate some domains (authenticator: nginx).
Invalid response from http://example.com/.well-known/acme-challenge/...: 404
Czystym wyjściem jest walidacja DNS. Sprawdza ona wpis TXT _acme-challenge.example.com i w ogóle nie interesuje jej, dokąd wskazuje rekord A:
certbot certonly --manual --preferred-challenges dns -d example.com -d www.example.com
Certbot pokaże wartość, którą wpisujesz jako rekord TXT w swojej strefie DNS. Przed potwierdzeniem koniecznie sprawdź sam, czy strefa już ten wpis serwuje, inaczej spalisz jedną nieudaną próbę:
dig +short TXT _acme-challenge.example.com
Przy domenach wildcard albo gdy chcesz to zautomatyzować, drogę przez API DNS opisuje wpis o certyfikacie wildcard. Wariant ręczny nie odnawia się sam, dlatego po przełączeniu DNS przestaw się na zwykłą walidację HTTP. Od tego momentu zadziała, bo domena wskazuje już na nowy serwer.
Test przez plik hosts, tak jak prawdziwy odwiedzający
Teraz przychodzi część, która decyduje o różnicy między „powinno działać” a „działa”. Kierujesz na nowy serwer wyłącznie własny komputer, podczas gdy reszta świata dalej widzi starą stronę.
Pod Linuksem i macOS edytujesz /etc/hosts jako root, pod Windowsem C:\Windows\System32\drivers\etc\hosts w edytorze uruchomionym jako administrator. Dopisz wiersz z adresem IP nowego serwera:
203.0.113.10 example.com www.example.com
Potem wyczyść cache DNS, inaczej zmiana nie zadziała od razu:
resolvectl flush-caches
Pod Windowsem ipconfig /flushdns, pod macOS sudo dscacheutil -flushcache, a po nim sudo killall -HUP mDNSResponder. Sprawdź, czy poskutkowało:
getent hosts example.com
Pułapka, która kosztuje wiele godzin: Firefox z włączonym DNS przez HTTPS ignoruje plik hosts. Mozilla oznaczyła to wprost jako „wontfix”. Widzisz wtedy dalej starą stronę i uznajesz test za nieudany, choć nowy serwer od dawna odpowiada poprawnie. Dlatego wyłącz w przeglądarce testowej szyfrowane rozwiązywanie nazw albo, jeszcze lepiej, sprawdzaj niezależnie od przeglądarki. curl potrafi nadpisać rozwiązywanie nazw dla pojedynczego wywołania, całkiem bez pliku hosts:
curl -sI --resolve example.com:443:203.0.113.10 https://example.com/
Co warto w tym stanie przejść: stronę główną, co najmniej dwie podstrony, wpis ze zdjęciami, logowanie pod /wp-login.php, zaplecze, formularz kontaktowy, a przy sklepie testowy produkt aż do kasy. Na obrazki zwróć szczególną uwagę, bo brakujących plików z katalogu uploads w zapleczu nie widać.
Czego test przez plik hosts wprost nie obejmuje: wszystkiego, co sięga do serwera z zewnątrz. Webhooki operatorów płatności, zewnętrzne cronjoby, roboty wyszukiwarek i twoja wysyłka poczty nadal trafiają w stary cel. To w porządku, musisz tylko o tym wiedzieć.
Moment przełączenia: synchronizacja różnicowa i DNS
Między pierwszym kopiowaniem a przestawieniem doszły komentarze, zamówienia albo wpisy. Kolejność czystego przełączenia:
- Na starym serwerze włącz tryb konserwacji albo przynajmniej na chwilę wstrzymaj zamówienia i komentarze.
- Zsynchronizuj pliki jeszcze raz, tym razem potrwa to sekundy.
- Wyeksportuj bazę danych na świeżo i zaimportuj ją na nowym serwerze.
- Wyczyść cache:
wp cache flushorazwp rewrite flush --hard. - Sprawdź jeszcze raz przez curl po nowym adresie IP.
- Przestaw rekordy A i AAAA.
Nie zapomnij o rekordzie AAAA. Jeśli zostanie na starym adresie IPv6, wszyscy odwiedzający po IPv6 dalej wylądują na starym serwerze, podczas gdy odwiedzający po IPv4 zobaczą nową stronę. Powstaje dokładnie ten rodzaj obrazu awarii, przy którym dwie osoby siedzą obok siebie i widzą różne witryny.
Stary serwer zostaw uruchomiony przez co najmniej tydzień, bez żadnych zmian. To twoja droga powrotna i twoje archiwum.
Po czym poznasz, że naprawdę się udało
Nie po tym, że „strona się ładuje”, tylko po mierzalnych dowodach:
dig +short A example.com
dig +short AAAA example.com
curl -sI https://example.com/ | head -n 12
W nagłówku nie może stać 301 na starą domenę ani na http://. Potem zapytaj bazę danych, jaki adres sam WordPress uważa za właściwy:
wp option get siteurl
wp option get home
Wiarygodnym testem na przeoczone pozostałości jest przebieg wyszukiwania na sucho. Jeśli zgłosi zero zamian, stary adres faktycznie nigdzie już nie figuruje:
wp search-replace 'stara-domena.pl' 'stara-domena.pl' --all-tables --dry-run --report-changed-only
Do tego skontroluj certyfikat, razem z okresem ważności i pokrytymi nazwami:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -subject
I na koniec log błędów serwera WWW, podczas gdy przez pięć minut klikasz po stronie. Jeśli nic nowego się tam nie pojawi, to koniec. Gdyby jednak coś się zacinało, najczęstszym punktem zaczepienia jest naprawa błędu 502 Bad Gateway, zwykle dlatego, że socket PHP-FPM w bloku nginx nadal nosi ścieżkę starej wersji PHP.
Gdy coś pójdzie nie tak: droga powrotna
Droga powrotna to jedna jedyna czynność i właśnie dlatego krok 1 był tak ważny: rekordy A i AAAA z powrotem na stary adres IP. Przy TTL 300 sekund większość odwiedzających w ciągu pięciu minut wraca na stary serwer, który przez cały czas pracował bez zmian.
Jest dokładnie jeden przypadek, w którym to nie jest czyste: gdy na nowym serwerze powstały już dane, których na starym nie ma. Nowe zamówienia, komentarze, rejestracje. Po wycofaniu zmiany te dane istnieją tylko w nowym systemie. Dlatego decyzję podejmuje się w ciągu pierwszych minut albo wcale. I dlatego tryb konserwacji podczas przełączania: utrzymuje on małe okno czasowe, w którym oba zbiory danych mogą się rozjechać.
Jeśli jednak musisz wrócić później, wyeksportuj z nowego serwera tylko dotknięte tabele i wgraj je na stary, zamiast przepychać z powrotem całą bazę danych. Czyste wycofanie ze starego pełnego backupu kasuje wszystko, co wydarzyło się w międzyczasie.
Komunikaty błędów w oryginalnym brzmieniu
Error establishing a database connection
Dane dostępowe albo DB_HOST w pliku wp-config.php nie pasują do nowego serwera. Często stoi tam jeszcze adres IP zewnętrznego serwera bazy danych zamiast localhost.
ERROR 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'
Zrzut z MySQL 8 jest importowany do MariaDB. Ustaw w zrzucie porównywanie na utf8mb4_unicode_ci przy pomocy sed.
Error: This does not seem to be a WordPress installation.
WP-CLI zostało uruchomione w złym katalogu albo pliki leżą nie tam, gdzie przypuszczasz. Pracuj z --path=/var/www/html.
ERR_TOO_MANY_REDIRECTS
Prawie zawsze sprzeczność między siteurl w bazie danych, stałą w pliku wp-config.php i przekierowaniem w serwerze WWW. Dodatkowo dotyczy to instalacji za proxy, które nie dowiadują się o statusie HTTPS i dlatego bez końca odsyłają z http na https i z powrotem.
Strona główna się ładuje, wszystkie podstrony zwracają 404
Klasyk przy przesiadce z Apache'a na nginx. Plik .htaccess z regułami bezpośrednich odnośników jest przez nginx ignorowany, tam potrzeba try_files $uri $uri/ /index.php?$args; w bloku location.
Przesłany plik przekracza dyrektywę upload_max_filesize w php.ini.
Konfiguracja PHP nie została przeniesiona razem z resztą. Ustaw upload_max_filesize, post_max_size i memory_limit na wartości ze starego serwera.
Biała strona, brak wpisu w logu
Najczęściej drop-in wskazujący donikąd: wp-content/object-cache.php albo advanced-cache.php. Zmień nazwę pliku i odśwież stronę.
Przeprowadzka poprowadzona w ten sposób nie ma okna bez działającej witryny. Jedynym odczuwalnym momentem jest zmiana DNS, a o jej długości decyduje TTL, który ustawiasz dwa dni wcześniej.
Najczęstsze pytania
Ile czasu mija, zanim zmiana DNS zadziała wszędzie?
Dlaczego nie mogę po prostu podmienić domeny poleceniem SQL w bazie danych?
Czy w ogóle muszę podmieniać domenę, jeśli zmienia się tylko serwer?
Jak zdobyć certyfikat Let's Encrypt, gdy domena wskazuje jeszcze stary serwer?
Plik hosts jest poprawiony, a mimo to dalej widać starą stronę. Z czego to wynika?
Jaka jest najszybsza droga powrotna, gdy po przestawieniu coś nie działa?
2026 KernelHost GmbH. Wszelkie prawa zastrzeżone. Ten poradnik jest chroniony prawem autorskim. Publikowanie go w innych serwisach, w całości, we fragmentach lub w zmienionej formie, wymaga naszej pisemnej zgody. Cytaty z podaniem źródła i z linkiem są jak najbardziej mile widziane.

