Pular para o conteúdo
Configuração

Substituir a URL no banco de dados do WordPress sem quebrar os dados serializados

Troque a URL de um site WordPress no banco sem zerar widgets, menus ou configurações do tema. Use o comando WP-CLI que mantém os dados serializados intactos.

Publicado

Não rode um UPDATE ... SET coluna = REPLACE(coluna, 'urlantiga', 'urlnova') puro no banco de dados do WordPress. Ele vai trocar o texto direitinho e destruir as suas configurações ao mesmo tempo, porque o WordPress guarda arrays e objetos como strings serializadas do PHP, e esse formato registra o tamanho em bytes de cada string que carrega. Uma substituição SQL crua reescreve o texto mas nunca toca no tamanho registrado, e o valor deixa de ser legível.

Use uma ferramenta que entenda serialização. Na linha de comando, isso é o wp search-replace. Faça backup do banco antes, rode como simulação e só depois rode de verdade.

O que quebra de fato

Uma string serializada do PHP tem esta cara:

s:18:"http://example.com"

O 18 é o tamanho em bytes do texto que vem depois. Conte — http://example.com tem exatamente 18 caracteres.

Agora imagine uma configuração de tema guardada assim:

a:1:{s:4:"logo";s:18:"http://example.com";}

Rode uma substituição SQL simples de http:// para https:// e você teria:

a:1:{s:4:"logo";s:19:"https://example.com";}

Só que não. Você recebe isto:

a:1:{s:4:"logo";s:18:"https://example.com";}

A string agora tem 19 bytes, mas continua afirmando ter 18. O PHP lê o tamanho declarado, anda 18 bytes para frente, não encontra a aspa de fechamento e o ponto e vírgula onde esperava, e desiste. O array inteiro falha na desserialização.

Por que a falha é silenciosa

É essa parte que torna o problema tão caro. O WordPress não lança um erro e para. Quando lê uma opção, ele passa o valor pelo próprio auxiliar de desserialização, o PHP devolve false para o dado malformado, e o WordPress entrega ao seu código um valor que se comporta como se a opção nunca tivesse sido definida. O plugin ou tema então volta ao padrão dele.

Ou seja, o sintoma não é uma página de erro. O sintoma é:

  • Widgets somem das barras laterais
  • Configurações do personalizador voltam ao padrão do tema
  • Os dados de layout de um construtor de página voltam vazios e a página renderiza em branco
  • Telas de configuração de plugins parecem recém-instaladas
  • Menus perdem as posições atribuídas

Nada no painel avisa que algo está errado. A linha continua no banco, ainda cheia do que parece ser o texto certo. Só o prefixo de tamanho está errado por um ou dois bytes. O PHP até emite um aviso quando a desserialização falha, mas num site de produção com a exibição de erros desligada ninguém vê. Ligar o WP_DEBUG com o WP_DEBUG_LOG ativado e o WP_DEBUG_DISPLAY como false coloca esses avisos num arquivo de log onde dá para ler.

O estrago também não é distribuído por igual. Valores escalares simples — siteurl, home, conteúdo puro de post, uma URL sozinha na própria coluna — sobrevivem perfeitamente a uma substituição ingênua. Só os valores serializados quebram. É por isso que uma substituição malfeita costuma parecer bem-sucedida à primeira vista.

Confira o wp-config.php antes de tocar em qualquer coisa

Se o wp-config.php define WP_HOME ou WP_SITEURL, essas constantes vencem o que estiver no banco. Alterar o wp_options vai parecer não surtir efeito nenhum. Procure por linhas assim antes de concluir que o problema está no banco:

define( 'WP_HOME', 'http://example.com' );
define( 'WP_SITEURL', 'http://example.com' );

Ou atualize as constantes para a URL nova, ou remova as duas e deixe o banco mandar.

Faça backup antes, e leve isso a sério

Uma quebra de serialização nem sempre é fácil de reverter. Uma vez que o prefixo de tamanho e o conteúdo passam a discordar, o valor original não é recuperável a partir do valor quebrado — não dá para saber qual deveria ser o tamanho correto sem conhecer a string original. O backup é o único desfazer de verdade.

wp db export backup-before-replace.sql

Ou com mysqldump, se o WP-CLI não estiver disponível:

mysqldump -u USER -p DBNAME > backup-before-replace.sql

Guarde fora do servidor, se possível. Um backup parado na raiz do site é ao mesmo tempo um ponto de restauração e um problema de segurança.

O comando seguro

O search-replace do WP-CLI desserializa cada valor, percorre a estrutura decodificada, substitui lá dentro e serializa de novo com os tamanhos corretos. Simulação primeiro:

wp search-replace 'http://example.com' 'https://example.com' --dry-run --all-tables --skip-columns=guid

Leia o relatório. Ele mostra quantas substituições vão acontecer por tabela e por coluna. Se a lista de tabelas parecer estranha, ou se uma tabela que você não reconhece tiver milhares de ocorrências, pare e investigue antes de rodar de verdade.

Depois, rode:

wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid --report-changed-only

Observações sinceras sobre as flags:

  • --all-tables cobre tabelas que o próprio WordPress não registra, que é justamente onde muito dado de plugin mora. Sem essa flag, algumas tabelas de plugins são puladas por completo.
  • --skip-columns=guid importa. O guid é um identificador permanente para leitores de feed, não uma URL viva. Reescrevê-lo pode fazer os assinantes do feed verem todo o seu arquivo como posts novos.
  • Busque sem a barra final e sem as variações de protocolo que você não quer mexer. Substituir example.com sem mais nada também vai pegar endereços de e-mail e qualquer menção dentro do texto dos posts.

Se você não puder usar a linha de comando, plugins de migração que entendem serialização fazem o mesmo trabalho de decodificação pela tela do painel. O comportamento que você procura é descrito explicitamente como tratamento de dados serializados; se a ferramenta não diz isso, presuma que ela não faz.

Quais tabelas e colunas entram na jogada

TabelaColunaRisco de serialização
wp_optionsoption_valueAlto — widgets, ajustes de tema, transients, a maioria das configurações de plugins
wp_postmetameta_valueAlto — layouts de construtores de página, arrays de campos personalizados
wp_usermetameta_valueAlto — capacidades, preferências de tela e do painel
wp_termmetameta_valueMédio — depende dos plugins
wp_postspost_content, guidBaixo para o conteúdo, mas atributos de shortcode e marcação de blocos carregam URLs
wp_commentscomment_content, comment_author_urlBaixo
wp_linkslink_url, link_imageBaixo, e quase sempre sem uso

Em multisite, some wp_blogs, wp_site, wp_sitemeta e as tabelas por site, wp_2_options, wp_3_options e assim por diante. Trocar URL em multisite tem regras extras além de uma busca e substituição — trate como um trabalho à parte.

Os casos que o search-replace não resolve por completo

Sendo honesto sobre os limites:

JSON guardado dentro do banco. Alguns construtores de página guardam os dados de layout em JSON com as barras escapadas, então a sua URL aparece como https:\/\/example.com e não como https://example.com. Uma busca pela forma normal passa batido. Talvez você precise de uma segunda passada na string escapada. Teste numa cópia.

Valores codificados em Base64. Se um plugin codifica as configurações antes de salvar, nenhuma substituição baseada em texto enxerga a URL. Essas configurações normalmente precisam ser digitadas de novo na tela do próprio plugin.

URLs fixas dentro de arquivos. Qualquer coisa escrita no functions.php, num template de tema filho ou num caminho de asset fixo não está no banco e não vai ser tocada. Faça um grep separado no diretório do tema.

Saída em cache e gerada. Cache de objetos, cache de páginas e cópias em CDN continuam entregando as URLs antigas mesmo depois de uma substituição correta. Limpe tudo e confira de novo antes de concluir que a substituição falhou.

Se você já rodou a consulta errada

Restaure o backup. Essa é a resposta, e é melhor dizer isso na lata do que oferecer um procedimento de reparo que quase nunca funciona.

Se não existe backup, o caminho de recuperação é estreito: os valores que nunca foram serializados estão bem, e os serializados quebrados precisam ser reconstruídos na mão ou zerados salvando de novo a tela de configuração correspondente. Salvar de novo uma área de widgets, reatribuir uma posição de menu ou redigitar as configurações de um plugin grava um valor novo, corretamente serializado, por cima do quebrado. Chato, mas funciona, e é melhor do que deixar linhas corrompidas no lugar para confundir a próxima pessoa que olhar o site.

Para gerar as instruções corretas antes de rodar qualquer coisa, monte tudo com a ferramenta de busca e substituição SQL do WordPress — ela mostra quais colunas dá para atacar com SQL puro e quais exigem tratamento consciente da serialização, para você enxergar essa divisão antes de se comprometer com um comando.

FAQ

Perguntas

Por que um REPLACE de SQL puro quebra um site WordPress?

Porque o WordPress guarda arrays e objetos como texto serializado do PHP, e esse formato registra o tamanho em bytes de cada string que ele contém. Um REPLACE cru muda o texto mas deixa o número antigo de tamanho no lugar. O PHP então se recusa a desserializar o valor, o WordPress cai no padrão e as configurações aparecem em branco em vez de erradas.

Qual a forma mais segura de trocar a URL de um site WordPress no banco?

Faça um backup completo do banco e rode o comando WP-CLI wp search-replace com uma simulação antes. O WP-CLI desserializa cada valor, substitui dentro da estrutura decodificada e serializa de novo com os tamanhos corrigidos. Plugins de migração que entendem serialização fazem o mesmo trabalho pela tela do painel quando não há acesso à linha de comando.

Quais tabelas guardam a URL antiga?

Principalmente wp_options, wp_posts, wp_postmeta, wp_usermeta, wp_termmeta e wp_comments. O perigo da serialização mora em wp_options e em qualquer coluna meta_value, já que widgets, ajustes de tema, transients e dados de construtores de página ficam guardados como arrays. Em multisite, wp_blogs, wp_site e wp_sitemeta também carregam o domínio.

Mexer no banco resolve a URL se o wp-config.php define ela?

Não. Se o wp-config.php define WP_HOME ou WP_SITEURL, essas constantes vencem o que estiver em siteurl e home dentro de wp_options. Editar o banco não muda nada visível até as constantes serem atualizadas ou removidas. Confira o wp-config.php primeiro, porque essa única linha explica um monte de migrações que deram errado.

Devo substituir também a coluna guid?

Não. O guid é um identificador permanente para leitores de feed, não uma URL funcional, e reescrevê-lo pode fazer os assinantes verem todos os posts antigos como novos. Use skip-columns com guid ao rodar o WP-CLI. A única exceção comum é um site que nunca teve feeds públicos nem assinantes.

Como sei se um valor quebrou depois de uma substituição malfeita?

Valores serializados quebrados parecem normais no banco, mas voltam vazios no WordPress. Widgets somem, ajustes do personalizador do tema se perdem e opções de plugins voltam ao padrão sem nenhuma mensagem de erro. Ligar o WP_DEBUG e o WP_DEBUG_LOG mostra o aviso de desserialização. Comparar o tamanho declarado da string com a contagem real de bytes confirma na hora.