Mover um site WordPress para um novo domínio parece simples no papel: copiar os ficheiros, importar o dump SQL para a nova base de dados e alterar os URLs do site e da página inicial. Na prática, um localizar e substituir simples em SQL corrompe a base de dados. O WordPress guarda opções, definições de widgets e dados de construtores de páginas como strings PHP serializadas, e essas strings levam os seus próprios marcadores de comprimento. Este guia explica porque é que isso estraga as substituições ingénuas, o que faz cada flag do WP-CLI segundo a referência oficial do comando, como se comportam o WP_HOME e o WP_SITEURL, e o que a Google documenta sobre redirecionamentos e a ferramenta de alteração de endereço.
Se a mudança for também uma reconstrução do site, veja os nossos serviços de migração de sites para Astro e Next.js.
1. Porque é que o REPLACE em SQL estraga os dados serializados do WordPress
O WordPress guarda muitas opções de configuração, layouts de widgets e propriedades do tema na tabela wp_options (e também em wp_postmeta, wp_usermeta e wp_termmeta) como arrays ou objetos serializados. Uma string PHP serializada declara o tipo de dados e o comprimento em bytes de cada valor de texto.
Como uma string PHP serializada guarda o comprimento
Veja esta string serializada que guarda uma definição de domínio: s:22:"https://old-domain.com";
Nesta estrutura:
sdeclara o tipo como string.22indica que o valor tem exatamente 22 bytes."https://old-domain.com"é o próprio valor.
Um array de opções do tema com o URL do logótipo fica assim depois de serializado: a:2:{s:11:"theme_color";s:4:"blue";s:8:"logo_url";s:28:"http://dev.site.com/logo.png";}
Cada chave e cada valor leva o seu comprimento: theme_color tem 11 bytes, logo_url tem 8 e o URL tem 28.
Porque é que um comprimento errado faz o WordPress ignorar a opção
Se executar um REPLACE normal em SQL para mudar o domínio, por exemplo de old-domain.com para my-new-company-domain.com, a query reescreve o valor mas deixa o marcador de comprimento intacto: s:22:"https://my-new-company-domain.com";
O novo valor tem 33 bytes, mas o marcador continua a dizer 22. Quando o WordPress lê a linha, passa-a por maybe_unserialize(). O PHP lê 22 bytes (https://my-new-company), espera as aspas de fecho, encontra um hífen e o unserialize() devolve false. A opção que o tema ou o plugin esperava chega como false, por isso o tema volta às predefinições, os widgets desaparecem e alguns plugins voltam a pedir a chave de licença.
O mesmo acontece no sentido inverso. Substituir dev.site.com por site.com no exemplo do logótipo produz um URL de 24 bytes sob um marcador que continua a dizer 28, e o array inteiro fica ilegível, não só o logótipo.
Uma substituição só sobrevive a um REPLACE simples em SQL quando a string antiga e a nova têm exatamente o mesmo comprimento, e isso é uma coincidência, não um método. É por isso que a base de dados WordPress não pode ser tratada como uma folha de cálculo onde basta localizar e substituir.
Objetos serializados e __PHP_Incomplete_Class
Se uma string serializada representar um objeto: O:31:"WPPoland\Database\CustomObject":1:{...}
O PHP associa o nome de classe guardado a uma classe carregada em tempo de execução. Se a classe não estiver carregada quando o valor é desserializado (por exemplo porque o plugin que a define está inativo, ou porque a migração mudou o namespace), o PHP cria em vez dela um marcador __PHP_Incomplete_Class. Os métodos desse objeto não podem ser chamados, e o código que espera o objeto real falha. As ferramentas que respeitam a serialização desserializam e voltam a serializar estes valores; não renomeiam classes.
2. Como mudar o domínio WordPress com o WP-CLI
O WP-CLI inclui o comando search-replace. A referência oficial diz que ele “trata de forma inteligente os dados serializados em PHP e não altera os valores das chaves primárias” (referência de comandos do WP-CLI, tradução própria). É o método padrão para atualizar URLs na base de dados WordPress.
Passo 1: exportar um backup da base de dados
Antes de mexer em qualquer linha, exporte um backup SQL completo. Confirme também que a versão do PHP no novo servidor corresponde à do servidor antigo, para não somar dois problemas na mesma migração.
# Exportar a base de dados para um ficheiro SQL
wp db export backup-pre-domain-migration.sqlPasso 2: executar o search-replace em modo dry run
Corra o comando primeiro com --dry-run. Segundo a referência, esta opção “executa toda a operação de pesquisa e substituição e mostra o relatório, mas não grava as alterações na base de dados” (tradução própria).
# Dry run: mostra o que mudaria, não grava nada
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --all-tables-with-prefix --skip-columns=guid --dry-runO relatório é uma tabela com as colunas Table, Column, Replacements e Type, uma linha por cada coluna com correspondências, seguida de uma linha como Success: 245 replacements to be made. (os números dependem inteiramente do seu site). A coluna Type mostra SQL ou PHP: o motor que tratou essa coluna.
O que faz realmente o —precise
A referência descreve o --precise assim: “força o uso de PHP (em vez de SQL) em todas as colunas” (tradução própria). Por omissão, o comando “usa queries SQL rápidas, mas passa automaticamente para PHP nas colunas que contêm dados serializados” (tradução própria). Por isso não precisa do --precise para proteger valores serializados; o WP-CLI já os deteta. Use o --precise quando quiser processamento em PHP em todo o lado, por exemplo quando não confia na deteção numa coluna com conteúdo invulgar, e aceite que a execução demora mais.
Que tabelas: por omissão, —all-tables e —all-tables-with-prefix
Por omissão, o comando usa apenas as tabelas registadas no objeto $wpdb, ou seja, as tabelas do núcleo e as tabelas de plugins registadas aí. Duas flags alargam o âmbito:
| Flag | Âmbito segundo a referência do WP-CLI |
|---|---|
| nenhuma | Tabelas registadas em $wpdb |
--all-tables-with-prefix | Qualquer tabela com o prefixo de tabelas, mesmo que não esteja registada em $wpdb |
--all-tables | TODAS as tabelas da base de dados, independentemente do prefixo |
Muitos plugins criam tabelas próprias sem as registar em $wpdb, por isso uma execução por omissão pode falhar URLs guardados nelas. O --all-tables-with-prefix é a forma mais segura de as incluir. Use o --all-tables só quando a base de dados pertence a este único site: se duas instalações WordPress partilharem uma base de dados com prefixos diferentes, o --all-tables reescreve também os URLs do outro site.
Porque deve ignorar a coluna guid
A documentação do WordPress sobre a mudança de site é inequívoca quanto à coluna guid em wp_posts: “Nunca, mesmo nunca, altere o conteúdo da coluna GUID, em circunstância nenhuma” (Moving WordPress, developer.wordpress.org, tradução própria). A mesma página explica que os leitores de feeds usam o GUID para saber se já mostraram um item, por isso “alterar o GUID fará com que muitos leitores de feeds voltem de repente a mostrar o seu conteúdo” (tradução própria). A referência do WP-CLI usa exatamente este caso como exemplo para ignorar uma coluna: --skip-columns=guid.
Passo 3: executar a substituição
Se o relatório do dry run estiver correto, corra o mesmo comando sem --dry-run. O --skip-columns aceita também nomes qualificados com a tabela (table.column), e o --skip-tables exclui tabelas inteiras, o que ajuda em bases de dados grandes com tabelas de registos:
# Executar, manter os GUID, incluir tabelas de plugins não registadas com o prefixo do site
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --all-tables-with-prefix --skip-columns=guidPara gerar um ficheiro SQL migrado sem tocar na base de dados ativa, use o --export, que escreve “os dados transformados num ficheiro SQL em vez de gravar as substituições na base de dados” (tradução própria):
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --skip-columns=guid --export=migrated.sqlPasso 4: corrigir conteúdo misto depois de passar para HTTPS
Se o site também passar de HTTP para HTTPS, faça uma segunda passagem para as referências inseguras escritas diretamente no conteúdo:
# Substituir ligações HTTP antigas por ligações HTTPS no novo domínio
wp search-replace 'http://old-domain.com' 'https://new-domain.com' --all-tables-with-prefix --skip-columns=guidPasso 5: limpar as ligações permanentes e a cache de objetos
Depois de uma execução bem-sucedida, o próprio WP-CLI mostra Please remember to flush your persistent object cache with `wp cache flush` quando há uma cache de objetos ativa. Faça-o, regenere as regras de reescrita e confirme os URLs guardados:
wp rewrite flush
wp cache flush
wp option get home
wp option get siteurlSem acesso à linha de comandos, as regras de reescrita também se regeneram no painel: abra Definições > Ligações permanentes e clique em Guardar alterações. Limpe ainda a cache de página do plugin ou do alojamento, e o Redis se o usar como cache de objetos.
Mudança de domínio em WordPress multisite
Em multisite, acrescente --network para percorrer “todas as tabelas registadas em $wpdb numa instalação multisite” (tradução própria). O guia Moving WordPress acrescenta verificações manuais que o comando não substitui: “reveja manualmente as tabelas wp_site e wp_blogs em qualquer caso” (tradução própria), e reveja cada tabela de opções por site (wp_2_options, wp_3_options e assim por diante) quanto aos valores home, siteurl e fileupload_url. A coluna domain dessas tabelas de rede guarda apenas o nome do anfitrião, por isso uma pesquisa por https://old-domain.com não a encontra. Atualize também as constantes de rede que o ecrã Configuração da rede lhe pediu para acrescentar ao wp-config.php. As instruções do próprio guia para mudar o URL do site “aplicam-se apenas a instalações individuais do WordPress” (tradução própria).
3. Definir WP_HOME e WP_SITEURL no wp-config.php
Se a base de dados ainda tiver o domínio antigo mas precisar de carregar o site no novo, pode sobrepor os dois valores no wp-config.php. A documentação do wp-config.php diz que o WP_SITEURL “sobrepõe o valor de siteurl na tabela wp_options” (tradução própria) e que o WP_HOME sobrepõe o valor de home, “mas não o altera na base de dados” (tradução própria). O valor home é o endereço que os visitantes escrevem; o siteurl é o endereço onde estão os ficheiros do núcleo. Ambos devem incluir https:// e não ter barra no fim.
Overrides temporários de URL no wp-config.php em staging
O override muda o encaminhamento e os URLs dos recursos, não as linhas guardadas. As ligações dentro do conteúdo dos artigos, dos metadados e das definições dos plugins continuam a apontar para o domínio antigo até correr um search-replace. A documentação avisa também que “o URL volta ao valor antigo da base de dados se esta linha for alguma vez removida do wp-config” (tradução própria). Trate as constantes como uma forma de entrar no painel e corra a substituição completa antes do lançamento:
// Sobrepor os URLs guardados enquanto a base de dados ainda tem o domínio antigo
define( 'WP_HOME', 'https://new-domain.com' );
define( 'WP_SITEURL', 'https://new-domain.com' );Enquanto as constantes estiverem definidas, os dois campos de URL em Definições > Geral não podem ser editados, porque os valores estão fixos no código.
Definir o URL do site a partir do nome do anfitrião atual
A documentação mostra uma forma dinâmica baseada em $_SERVER['HTTP_HOST'], útil quando a mesma base de código corre em ambientes local, de staging e de produção:
define( 'WP_HOME', 'https://' . $_SERVER['HTTP_HOST'] );
define( 'WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST'] );Vem com um aviso: o HTTP_HOST “é criado dinamicamente pelo PHP com base no valor do cabeçalho HTTP Host do pedido, o que pode permitir vulnerabilidades de inclusão de ficheiros” (tradução própria). Quando o Apache corre com UseCanonicalName On, o SERVER_NAME vem da configuração do servidor, e a documentação considera-o a escolha mais segura nesse caso. Num servidor de produção público, prefira um valor fixo ou uma lista de anfitriões permitidos.
Outras formas de mudar o URL do site: wp option update, RELOCATE e functions.php
O guia Moving WordPress enumera várias formas de mudar os dois valores guardados:
- WP-CLI:
wp option update home 'https://example.com'ewp option update siteurl 'https://example.com'. Isto grava os valores emwp_optionse não muda mais nada. - functions.php: acrescente temporariamente
update_option( 'siteurl', ... )eupdate_option( 'home', ... )ao tema ativo, carregue o site algumas vezes e depois remova o código. O guia diz claramente: “Não deixe este código no ficheiro functions.php” (tradução própria). - RELOCATE: acrescente
define( 'RELOCATE', true );aowp-config.php, inicie sessão emwp-login.phpno novo endereço, verifique os dois campos em Definições > Geral e remova a linha. O guia considera inseguro deixá-la ficar e nota que só atualiza o URL do site, por isso as outras referências continuam a precisar de um search-replace. - Edição direta na base de dados: depois de um backup, edite as linhas
siteurlehomeda tabela de opções no phpMyAdmin (o prefixo das tabelas pode ser diferente dewp_).
Nenhum destes métodos toca nos URLs dentro do conteúdo, e é por isso que o search-replace da secção 2 continua a ser o passo principal.
4. Search-replace sem SSH: Better Search Replace e Search Replace DB
O guia de migração do WordPress indica duas alternativas ao WP-CLI para alojamentos sem acesso à linha de comandos.
Plugin Better Search Replace
Se ainda conseguir entrar no painel, o plugin Better Search Replace inclui nas suas funcionalidades o “suporte para serialização em todas as tabelas” (tradução própria) e acrescenta a sua página ao menu Ferramentas:
- Instale e ative o plugin, depois abra Ferramentas > Better Search Replace.
- Escreva o URL antigo no campo de pesquisa e o URL novo no campo de substituição.
- Selecione as tabelas a processar.
- Mantenha a opção de dry run ativa na primeira passagem (é a predefinição desde a versão 1.0.1) e veja quantos campos seriam alterados.
- Desative o dry run e execute de novo.
Corre dentro de pedidos PHP, por isso em tabelas muito grandes pode bater no limite de tempo de execução do alojamento, e não serve de nada quando o painel já redireciona para o domínio antigo.
Script Search Replace DB da interconnect/it
Quando nem o WP-CLI nem o painel estão disponíveis, o guia aponta para o script da interconnect/it “para alterar com segurança todas as ocorrências do domínio ou caminho antigo para o novo” (tradução própria), com o aviso de “usar esta opção apenas se estiver à vontade com a administração de bases de dados” (tradução própria). Envie-o para uma pasta com nome aleatório, corra o dry run, depois a substituição, e apague a pasta logo a seguir: quem a encontrar pode ler e reescrever a base de dados.
5. Script PHP de search-replace para dados serializados
Se precisar da substituição dentro do seu próprio código PHP (por exemplo num pipeline de deploy sem WP-CLI), esta classe percorre recursivamente arrays e objetos, substitui as strings e volta a serializar, para que os marcadores de comprimento sejam recalculados pelo próprio PHP.
Crie-a como scripts/class-serialization-replace.php:
<?php
declare(strict_types=1);
namespace WPPoland\Database\Migrator;
class SerializationAwareReplacer {
private string $search;
private string $replace;
public function __construct( string $search, string $replace ) {
$this->search = $search;
$this->replace = $replace;
}
/**
* Ponto de entrada para pesquisar e substituir strings nos dados de forma recursiva.
*/
public function process( $data ) {
if ( is_string( $data ) ) {
// Verificar se a string está serializada
if ( $this->is_serialized_string( $data ) ) {
$unserialized = unserialize( $data );
if ( false !== $unserialized ) {
return serialize( $this->process( $unserialized ) );
}
}
return str_replace( $this->search, $this->replace, $data );
}
if ( is_array( $data ) ) {
$cleaned = [];
foreach ( $data as $key => $value ) {
$cleaned[ $key ] = $this->process( $value );
}
return $cleaned;
}
if ( is_object( $data ) ) {
$cleaned = clone $data;
foreach ( get_object_vars( $data ) as $key => $value ) {
$cleaned->$key = $this->process( $value );
}
return $cleaned;
}
return $data;
}
/**
* Verifica se uma string está serializada.
*/
private function is_serialized_string( string $string ): bool {
$string = trim( $string );
if ( 'N;' === $string ) {
return true;
}
if ( strlen( $string ) < 4 ) {
return false;
}
if ( ':' !== $string[1] ) {
return false;
}
$last_char = substr( $string, -1 );
if ( ';' !== $last_char && '}' !== $last_char ) {
return false;
}
$token = $string[0];
switch ( $token ) {
case 's':
if ( '"' !== substr( $string, -2, 1 ) ) {
return false;
}
return true;
case 'a':
case 'O':
return (bool) preg_match( "/^{$token}:[0-9]+:/s", $string );
case 'b':
case 'i':
case 'd':
return (bool) preg_match( "/^{$token}:[0-9.E-]+;\$/", $string );
}
return false;
}
}A função get_object_vars() devolve as propriedades acessíveis a partir do âmbito que a chama, aqui apenas as públicas, por isso as propriedades privadas e protegidas dos objetos passam sem alteração. Dentro do WordPress, as funções is_serialized() e maybe_unserialize() do núcleo fazem a deteção por si; o WP-CLI continua a ser a opção testada.
6. Redirecionar o domínio antigo com regras 301
A documentação da Google sobre mudanças de site com alteração de URLs pede redirecionamentos permanentes do lado do servidor, “como 301 e 308” (tradução própria), de cada URL antigo para o seu equivalente novo. A documentação sobre redirecionamentos explica que, para estes códigos permanentes, “o pipeline de indexação usa o redirecionamento como sinal de que o destino deve ser canónico” (tradução própria); os códigos temporários (302, 303, 307) não enviam esse sinal.
Redirecionamento 301 no Nginx que mantém o URI do pedido
Mantenha o caminho e a query string com $request_uri. O guia de mudança de site da Google desaconselha enviar muitos URLs antigos para uma única página sem relação, como a nova página inicial; a regra abaixo produz um mapeamento página a página:
server {
listen 80;
listen 443 ssl;
server_name old-domain.com www.old-domain.com;
# ssl_certificate e ssl_certificate_key do domínio antigo vão aqui
# Redirecionamento permanente que mantém o caminho e a query string
return 301 https://new-domain.com$request_uri;
}O domínio antigo continua a precisar de um certificado válido: um pedido HTTPS para ele falha no handshake TLS antes de o Nginx conseguir enviar o redirecionamento.
Redirecionamento 301 no .htaccess do Apache para um novo domínio
Acrescente estas regras no topo do ficheiro .htaccess do domínio antigo:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(?:www\.)?old-domain\.com$ [NC]
RewriteRule ^(.*)$ https://new-domain.com/$1 [R=301,L]
</IfModule>A documentação do mod_rewrite do Apache diz que “por omissão, a query string passa sem alterações” (tradução própria), por isso o ?page=2 sobrevive ao redirecionamento.
Redirecionamento no edge com um Cloudflare Worker
Se o domínio antigo passar pelo proxy da Cloudflare, um Worker pode responder com o redirecionamento sem chegar à origem. O código segue a sintaxe de módulos do exemplo de redirecionamento da própria Cloudflare:
export default {
async fetch(request) {
const base = "https://new-domain.com";
const { pathname, search } = new URL(request.url);
return Response.redirect(`${base}${pathname}${search}`, 301);
},
};7. Lista de verificação SEO da migração e configuração da Google Search Console
Como usar a ferramenta de alteração de endereço da Search Console
A página de ajuda da ferramenta de alteração de endereço da Google define estas condições:
- Propriedade: “Tem de ser proprietário da propriedade antiga e da nova na Search Console” (tradução própria).
- Só ao nível do domínio: a ferramenta “só pode ser usada em propriedades ao nível do domínio” e “não move nenhum subdomínio abaixo do domínio indicado (incluindo www)” (tradução própria). Envie pedidos separados para as variantes com e sem www que estejam em uso ou tenham sido usadas no passado.
- Primeiro o redirecionamento: configure um redirecionamento 301 da página inicial antiga para a nova e das restantes páginas. A ferramenta verifica que é proprietário dos dois sites e que algumas páginas devolvem redirecionamentos 301.
- Execução: abra a propriedade antiga, vá a Definições, escolha Alteração de endereço e selecione a nova propriedade.
- Sitemaps: envie o novo sitemap XML. O guia de mudança de site da Google diz que os avisos sobre URLs redirecionados no sitemap antigo são esperados enquanto os dois estiverem enviados.
A ferramenta não é necessária numa passagem de HTTP para HTTPS, numa troca entre www e sem www no mesmo domínio, nem em mudanças de caminho dentro de um domínio.
O que mais atualizar no novo domínio
O guia de mudança de site enumera as alterações no próprio site que acompanham os redirecionamentos:
- Atualize as ligações internas para apontarem diretamente para os novos URLs, sem passar por um redirecionamento.
- Dê a cada novo URL uma tag canonical que aponte para si próprio.
- Se usar hreflang, “não se esqueça de atualizar as anotações para usarem os novos URLs” (tradução própria).
- Confirme que o
robots.txtdo novo domínio não mantém umDisallow: /herdado do staging.
8. Quanto tempo manter os redirecionamentos depois de mudar o domínio WordPress
A Google dá dois valores. A página de ajuda da alteração de endereço diz que o encaminhamento de sinais continua “durante 180 dias depois de iniciar a migração na Search Console” (tradução própria) e pede que mantenha os redirecionamentos pelo menos 180 dias, “mais tempo se ainda vir tráfego da Pesquisa Google para eles” (tradução própria). O guia de mudança de site vai mais longe: manter os redirecionamentos o máximo de tempo possível, em regra pelo menos um ano. Renovar o registo do domínio antigo faz parte disso, porque os redirecionamentos deixam de funcionar no dia em que o domínio expira.
Quanto ao calendário, o mesmo guia diz que um site de dimensão média “pode demorar algumas semanas ou mais” (tradução própria) até a Google mostrar gradualmente os novos URLs, e que sites maiores demoram mais. Oscilações de posições nesse período são esperadas, por isso avalie a mudança pelos dados da Search Console ao longo de semanas, não de dias.
9. Porque é que o WordPress continua a redirecionar para o domínio antigo depois da migração
Se o site continuar a enviá-lo para o endereço antigo depois da substituição, verifique estes pontos por esta ordem:
- wp-config.php: uma constante
WP_HOMEouWP_SITEURLesquecida com o valor antigo sobrepõe-se à base de dados. - wp_options: execute
wp option get homeewp option get siteurl, ou leia as duas linhas no phpMyAdmin. - .htaccess e configuração do servidor: alguns plugins de segurança e de redirecionamentos escrevem lá regras, e pode ainda existir um bloco
serverantigo. - Caches: a cache de página, a cache de objetos (
wp cache flush), uma CDN e a própria cache do navegador de um 301 anterior podem servir o destino antigo.
10. Lista de verificação da mudança de domínio WordPress
Use esta lista nas suas migrações de domínio WordPress:
- Faça backup da base de dados e de todo o sistema de ficheiros, incluindo
wp-content/uploads. - Configure o novo alojamento e instale certificados SSL para o novo domínio, e mantenha um certificado válido no antigo.
- Importe a base de dados e atualize as credenciais no
wp-config.php. - Corra o
wp search-replacecom--dry-rune--skip-columns=guide reveja o relatório. - Execute a substituição para as variantes HTTPS e HTTP.
- Remova as linhas temporárias
WP_HOME,WP_SITEURLouRELOCATEque já não são necessárias. - Limpe as regras de reescrita e as caches, depois verifique
homeesiteurl. - Configure redirecionamentos 301 página a página no domínio antigo.
- Atualize as tags canonical, as anotações hreflang e as ligações internas.
- Submeta a alteração de endereço na Search Console e o novo sitemap.
- Mantenha os redirecionamentos e o registo do domínio antigo durante pelo menos um ano.
Precisa de ajuda para migrar a base de dados WordPress ou configurar regras de redirecionamento no servidor? No desenvolvimento WordPress faço eu próprio a auditoria da base de dados, a migração e a configuração da Search Console. Escreva-me para falarmos sobre o seu projeto.







