Staging WordPressu je úplná kopie vašeho webu na samostatné subdoméně, například staging.example.com, s vlastní databází, kde aktualizujete pluginy a šablony bez rizika pro ostrý web. Vytvoříte ho v pěti krocích: zkopírujete soubory, zkopírujete databázi, nahradíte adresy URL příkazem wp search-replace (ne sedem), kopii zamknete heslem a noindex a vypnete v ní e-maily a cron. Změny se na ostrý web vracejí jako soubory, ne jako databáze: na našem testovacím stroji přepsání ostré databáze databází ze stagingu smazalo 7 nových objednávek a 5 komentářů a ostrý web přepnulo na noindex.
Jak jsme testovali
10. listopadu 2026 na testovacím stroji: kontejner Ubuntu 24.04, nginx 1.24 před Apache 2.4, PHP-FPM 8.3, MariaDB 10.11, WordPress 7.1.4, WooCommerce 11.2.2 a WP-CLI 2.12. „Ostrý“ web měl 25 produktů z ukázkových dat WooCommerce, 300 objednávek, 41 komentářů a tři fotky po 5 MB. Kopii pro staging jsme vytvořili na subdoméně stejného serveru a lokální lapač pošty zachytával každou zprávu, místo aby ji odeslal. Časy a velikosti jsme měřili na lokálním disku testovacího stroje; nejde o hodnoty serverů RichHost. Dokumentaci WP-CLI a Google jsme ověřili týž den.
Staging v šesti krocích: příkaz a kontrola
Příkazy wp spouštějte přes SSH ve složce webu. Cesty platí pro typické uspořádání, kde web leží v ~/web/domain/public_html.
| Krok | Příkaz | Jak ověřit |
|---|---|---|
| 1. Subdoména a prázdná databáze | Panel: přidejte subdoménu jako webovou doménu, pak vytvořte databázi | Subdoména se otevře, nová databáze nemá žádné tabulky |
| 2. Kopie souborů | rsync -a ze složky ostrého webu do složky subdomény |
find . -type f | wc -l vrátí v obou složkách stejné číslo |
| 3. Kopie databáze s novými adresami | wp search-replace s --export, údaje nové databáze do wp-config.php kopie, wp db import |
wp option get siteurl na kopii ukáže subdoménu |
| 4. Skrýt před vyhledávači a cizími lidmi | wp option update blog_public 0, heslo a X-Robots-Tag v .htaccess |
curl -I bez hesla vrátí 401 a X-Robots-Tag |
| 5. Vypnout cron a e-maily | DISABLE_WP_CRON, WP_ENVIRONMENT_TYPE, mu-plugin blokující poštu |
V logu není POST /wp-cron.php, testovací objednávka nepošle e-mail |
| 6. Přenést změny zpět | rsync --dry-run --itemize-changes, jen šablony a pluginy |
Počet objednávek a komentářů na ostrém webu se nezmění |
Subdoména a kopie souborů
V ovládacím panelu se subdoména obvykle přidává jako každá jiná webová doména, s názvem staging.example.com. Panel vytvoří samostatnou složku s public_html, a pokud je na serveru i zóna DNS domény, obvykle přidá záznam pro subdoménu. SSL zapnete v nastavení domény stejně jako u hlavního webu. Databázi pro kopii vytvořte v sekci databází; mnoho panelů před názvy databáze a uživatele přidává název účtu, proto si poznamenejte celé názvy.
Soubory můžete zkopírovat ve správci souborů panelu, ale u tisíců malých souborů je jednodušší se připojit přes SSH a použít rsync. Než začnete, udělejte čerstvou zálohu ostrého webu: staging zálohu nenahrazuje.
rsync -a ~/web/example.com/public_html/ ~/web/staging.example.com/public_html/ find ~/web/example.com/public_html -type f | wc -l find ~/web/staging.example.com/public_html -type f | wc -l
Kolik to zabere v praxi. Testovací obchod měl 224 MB a 10 547 souborů: jádro WordPressu se všemi přibalenými šablonami 136 MB, pluginy 55 MB (skoro 5500 souborů, hlavně WooCommerce), média 33 MB. Databáze měla v MariaDB 5,1 MB, 48 tabulek.
| Operace na testovacím stroji | Čas | Výsledek |
|---|---|---|
| rsync souborů na stejném disku | 1,1 s | 224 MB, 10 547 souborů |
| Archiv tar.gz všech souborů | 15,3 s | 104 MB |
| Dump databáze (wp db export) | 0,33 s | 1,2 MB SQL, 146 KB po gzip |
| wp search-replace s exportem do souboru | 1,1 s | 6 náhrad |
| Import databáze do kopie | 1,9 s | 48 tabulek |
Na jednom serveru trvá kopie malého obchodu sekundy; nejpomalejší je archivace. Pokud se web stěhuje na jiný server, postup je stejný jako při přesunu WordPressu na jiný hosting.
Náhrada adres v databázi: proč sed rozbije nastavení
WordPress ukládá některá nastavení (widgety, volby šablony a pluginů) jako serializovaná pole PHP, kde před každým řetězcem stojí jeho délka, například s:152:"...". Když adresu v dumpu nahradíte obyčejným sedem, text se prodlouží, ale číslo zůstane stejné. Vyzkoušeli jsme to na textovém widgetu se dvěma odkazy na ostrý web:
$ sed 's#http://live.cw14.test:14101#http://staging.live.cw14.test:14101#g' live.sql > staging_sed.sql
$ wp db import staging_sed.sql
...s:4:"text";s:152:"<a href="http://staging.live.cw14.test:14101/shop/">Go to the shop</a> | <img src=...
PHP Warning: unserialize(): Error at offset 235 of 272 bytes
bool(false)
# after one page view:
a:1:{s:12:"_multiwidget";i:1;}
Řetězec měl teď 168 znaků (o 8 víc na každou adresu) a PHP ho nedokázalo přečíst. Web dál vracel 200 bez jakékoli chyby; widget prostě zmizel. Ještě horší je, že hned při prvním zobrazení stránky WordPress přepsal poškozenou hodnotu prázdnou, takže obsah widgetu byl z databáze stagingu nadobro pryč. Proto si takového poškození často nikdo nevšimne: nastavení se potichu vrátí na výchozí hodnoty. Past je zrádná i tím, že sed funguje, když má nová adresa stejnou délku, a zvyk zůstane.
WP-CLI serializovaná data rozbalí, změní text a zabalí je zpět se správnou délkou. Nejdřív ho spusťte s --dry-run: ukáže, co a kde se změní, aniž by cokoli zapsal.
$ wp search-replace 'http://live.cw14.test:14101' 'http://staging.live.cw14.test:14101' --skip-columns=guid --dry-run --report-changed-only Table Column Replacements Type wp_options option_value 3 PHP wp_posts post_content 2 SQL wp_users user_url 1 SQL Success: 6 replacements to be made.
Tři ze šesti náhrad byly v serializovaných volbách (typ PHP), takže sed by rozbil polovinu změn. Sloupec guid jsme vynechali záměrně: je to identifikátor příspěvku, ne odkaz, a 337 řádků v něm si ponechalo starou adresu. Pro staging je nejpohodlnější spustit náhradu na ostrém webu s --export. Ostrá databáze zůstane nedotčená a výsledek půjde rovnou do souboru, který naimportujete do kopie.
cd ~/web/example.com/public_html wp search-replace 'https://example.com' 'https://staging.example.com' --skip-columns=guid --export=$HOME/staging.sql cd ~/web/staging.example.com/public_html wp config set DB_NAME user_staging wp config set DB_USER user_staging wp config set DB_PASSWORD 'new-db-password' wp db import $HOME/staging.sql wp option get siteurl rm $HOME/staging.sql
Pokud WP-CLI nemáte, použijte migrační plugin, který výslovně podporuje serializovaná data. Najít a nahradit v textovém editoru má stejnou vadu jako sed.
Jak udržet staging mimo Google
Každý nejdřív zaškrtne v nastavení čtení políčko, které žádá vyhledávače, aby web neindexovaly (volba blog_public nastavená na 0). Ověřili jsme, co přesně ve WordPressu 7.1.4 mění:
- stránky HTML dostanou
<meta name='robots' content='noindex, nofollow' />; - z robots.txt zmizí řádek Sitemap a
wp-sitemap.xmlvrací 404; - v robots.txt není pravidlo
Disallow: /ani hlavička X-Robots-Tag; - obrázky z uploads se servírují s kódem 200 a bez jakéhokoli označení: meta značka v HTML se na ně nevztahuje.
Políčko tedy jen žádá, aby se stránky neindexovaly, zatímco kopie se všemi daty je otevřená každému, kdo zná adresu. Zablokovat všechno v robots.txt také není dobrý nápad: Google uvádí, že u adresy blokované v robots.txt robot pravidlo noindex nikdy neuvidí a adresa se přesto může objevit ve výsledcích díky odkazům (dokumentace Google, ověřeno 10.11.2026). Bezpečnější je dát subdoménu za heslo a přidat hlavičku X-Robots-Tag, která pokryje i obrázky a PDF.
Pokud web běží na Apache (jako na našem testovacím stroji s nginx před Apache), přidejte pravidla na začátek souboru .htaccess kopie. Soubor s hesly mějte mimo public_html. Na serveru jen s nginx se totéž udělá direktivami auth_basic v konfiguraci webu.
htpasswd -cB ~/web/staging.example.com/.htpasswd tester # top of ~/web/staging.example.com/public_html/.htaccess Header always set X-Robots-Tag "noindex, nofollow" AuthType Basic AuthName "Staging" AuthUserFile /home/user/web/staging.example.com/.htpasswd Require valid-user
Ověřte výsledek tak, jak web vidí robot vyhledávače, tedy bez hesla a s jeho User-Agentem:
$ curl -I -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" http://staging.live.cw14.test:14101/ HTTP/1.1 401 Unauthorized X-Robots-Tag: noindex, nofollow WWW-Authenticate: Basic realm="Staging"
Bez hesla vrátila hlavní stránka i obrázek 401, špatné heslo také 401 (log Apache: AH01617: user tester: authentication failure). Se správným heslem vracely stránky i obrázky 200 s hlavičkou X-Robots-Tag. Ostrého webu se to nedotklo: běžná meta značka robots a funkční sitemapa.
E-maily a cron: kopie nesmí žít vlastním životem
Kopie databáze obsahuje adresu každého zákazníka, takže staging jim může posílat e-maily. Testovací objednávku #35 jsme na kopii označili jako dokončenou, jak by to udělal každý, kdo testuje obchod, a lapač okamžitě přijal Your CW14 Shop order is now complete adresovaný zákazníkovi. Na skutečném hostingu by ten e-mail dorazil skutečnému člověku.
Nejjednodušší ochranou je označit kopii jako stagingové prostředí a zablokovat její poštu filtrem pre_wp_mail v mu-pluginu. Na ostrém webu s typem prostředí production nic neblokuje.
cd ~/web/staging.example.com/public_html
wp config set WP_ENVIRONMENT_TYPE staging
wp config set DISABLE_WP_CRON true --raw
mkdir -p wp-content/mu-plugins
# wp-content/mu-plugins/staging-no-mail.php
<?php
/* Plugin Name: Staging: no outgoing mail */
add_filter('pre_wp_mail', function ($return, $atts) {
if (wp_get_environment_type() === 'production') {
return $return;
}
$to = is_array($atts['to']) ? implode(',', $atts['to']) : $atts['to'];
error_log('STAGING mail blocked: ' . $to . ' | ' . $atts['subject']);
return false;
}, 10, 2);
Potom další testovací objednávka ani formulář pro obnovu hesla neposlaly jediný e-mail a v logu PHP se objevily řádky typu STAGING mail blocked: customer2.seed@example.com | Your CW14 Shop order is now complete. Pluginy, které posílají poštu přes vlastní API a obcházejí wp_mail, tento filtr nezastaví: na kopii je vypněte nebo je přepněte na testovací klíče.
S cronem je to podobné. WP-Cron spouštějí návštěvy a s databází se do kopie přeneslo 12 naplánovaných událostí, včetně fronty úloh WooCommerce na pozadí. Před nastavením hesla každé zobrazení stránky na stagingu vyvolalo POST /wp-cron.php s odpovědí 200. Po přidání hesla jsme si všimli vedlejšího efektu: interní požadavek WordPressu sám na sebe dostal 401 a událost naplánovaná na okamžité spuštění zůstala ležet ve frontě. Cron na kopii se potichu zastavil, takže každý test naplánovaných úloh by dával falešné výsledky. Konstanta DISABLE_WP_CRON to dělá jednoznačným: po jejím nastavení v logu nebyly žádné požadavky na wp-cron.php a úlohu můžete spustit ručně příkazem wp cron event run --due-now. Pokud na ostrém webu volá WP-Cron cron úloha na serveru, pro subdoménu ji nekopírujte.
Jak přenést změny zpět a nepřijít o objednávky
Staging je snímek z okamžiku kopírování, zatímco ostrý web běží dál. Nasimulovali jsme typickou situaci. Na kopii jsme nainstalovali malý plugin, změnili jeden řádek CSS v šabloně, vytvořili stránku a během testování označili dvě objednávky jako dokončené. Mezitím na ostrý web přišlo 7 nových objednávek a 5 komentářů. Pak jsme vyzkoušeli dva způsoby přenosu zpět.
| Ukazatel | Ostrý web před přenosem | Staging | Ostrý web po přepsání databáze | Ostrý web po přenosu souborů |
|---|---|---|---|---|
| Objednávky | 307 | 300 | 300 | 307 |
| Komentáře | 46 | 41 | 41 | 46 |
| Testovací stavy „dokončeno“ | 0 | 2 | 2 | 0 |
| blog_public | 1 | 0 | 0 (noindex na ostrém webu) | 1 |
| Nový plugin | není | aktivní | v databázi, soubory chybí, WordPress ho deaktivoval | aktivní |
Přepsání databáze napáchalo tři škody najednou: smazalo vše, co na ostrý web přibylo po vytvoření kopie, přeneslo změny testovacích objednávek a přepnulo ostrý web na noindex, protože blog_public je uložené v databázi. Ostrý web jsme obnovili z dumpu pořízeného před přenosem a operaci zopakovali správně, jen se soubory:
$ cat push-exclude.txt wp-config.php .htaccess wp-content/uploads/ wp-content/cache/ wp-content/mu-plugins/staging-no-mail.php wp-content/upgrade/ $ rsync -a --delete --dry-run --itemize-changes --exclude-from=push-exclude.txt staging/ live/ cd+++++++++ wp-content/plugins/cw14-delivery-note/ >f+++++++++ wp-content/plugins/cw14-delivery-note/cw14-delivery-note.php >f.st...... wp-content/themes/twentytwentyone/style.css
Zkušební běh vypsal přesně ty tři změny, které jsme udělali, a nic jiného. Po ostrém běhu zbývalo jen aktivovat plugin na ostrém webu a znovu tam vytvořit stránku. Objednávky, komentáře i viditelnost ve vyhledávání zůstaly nedotčené. Přepínač --delete maže na ostrém webu soubory, které v kopii chybí, proto ho nikdy nepoužívejte bez zkušebního běhu.
Změny v databázi (stránky, nastavení pluginů, menu) se přenášejí ručně: při testování si zapisujte, co měníte, a zopakujte to na ostrém webu. Trvá to déle než jeden import, ale nic se neztratí.
Co zvolit: když X, udělejte Y
| Když | Udělejte |
|---|---|
| Aktualizujete pluginy, šablonu nebo verzi PHP | Otestujte na stagingu, pak stejnou aktualizaci zopakujte na ostrém webu, nebo soubory přeneste přes rsync po zkušebním běhu |
| Změnili jste nastavení, stránky nebo menu | Zopakujte změny ručně na ostrém webu podle poznámek |
| Obchod, fórum, web s registrací uživatelů | Nikdy nepřepisujte ostrou databázi databází ze stagingu |
| Prezentační web bez formulářů a komentářů, hodně změn | Výměna databáze je možná: záloha, režim údržby, wp search-replace zpět a blog_public = 1 |
| Bez SSH nebo WP-CLI | Migrační plugin s podporou serializovaných dat; sed a textové editory nestačí |
| Testování je hotové | Smažte subdoménu i databázi kopie: zapomenutá kopie se zákaznickými daty je riziko |
Staging nepotřebuje samostatný server: stačí subdoména a druhá databáze na stejném účtu, pokud váš tarif povoluje více webů. V hostingových tarifech RichHost je počet webů neomezený, s vlastním ovládacím panelem s asistentem AI a bezplatným SSL Let’s Encrypt.
Časté dotazy
Stačí ve WordPressu políčko „Požádat vyhledávače, aby neindexovaly tento web“?
Ne. Na našem testovacím stroji jen přidalo meta značku noindex ke stránkám HTML. Obrázky zůstaly neoznačené a samotná kopie byla otevřená komukoli. Heslo na subdoméně a k tomu hlavička X-Robots-Tag pokryjí všechno.
Jak často obnovovat staging?
Před každým větším testem si udělejte čerstvou kopii ostrého webu. Stará kopie stagingu zaostává ve verzích pluginů i v datech, takže test na ní může projít a na ostrém webu selhat.
Proč web po přenosu zmizel z Google?
Nejčastější příčinou je, že se blog_public = 0 dostalo na ostrý web spolu s databází. Ověřte to příkazem wp option get blog_public (hodnota má být 1) a ve zdrojovém kódu hlavní stránky hledejte noindex.
Můžu na stagingu testovat platby?
Jen v testovacím režimu platební brány s testovacími klíči. Ostré klíče v kopii databáze vyměňte hned po vytvoření stagingu, spolu s klíči služeb pro rozesílání e-mailů.



