Pular para o conteúdo
Servidor e .htaccess

Regras de redirecionamento no .htaccess do WordPress: 301 bem feitos

Mova um URL sem perder posições: qual diretiva de redirecionamento usar no .htaccess do WordPress, onde as regras devem ficar e como testar sem se trancar do lado de fora.

Publicado

Vais mover um URL e queres que o antigo mantenha as posições? Precisas de um 301 no .htaccess, colocado onde o WordPress não o apague, escrito com a diretiva certa e apontado ao URL exato que o WordPress serve de facto. Falha em qualquer um desses três pontos e ficas com uma cadeia de redirecionamentos, um ciclo, ou um 500 que te tranca fora do wp-admin.

A decisão, a regra de colocação e o teste, por esta ordem.

Que diretiva: Redirect, RedirectMatch ou RewriteRule

Há dois módulos do Apache em jogo e não são intermutáveis.

Redirect (mod_alias) — um caminho conhecido para um destino. A coisa mais simples que funciona:

Redirect 301 /old-page/ https://example.com/new-page/

Dois comportamentos a conhecer. A correspondência é por prefixo de caminho, não por cadeia exata, por isso /old-page/ também apanha /old-page/anything/ e acrescenta /anything/ ao destino. E leva a cadeia de consulta consigo automaticamente.

RedirectMatch (mod_alias) — uma expressão regular a cobrir muitos URL. É assim que se move uma secção inteira:

RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1

O grupo de captura (.*) passa a $1 no destino, por isso /blog/hello-world/ aterra em /articles/hello-world/. Repara na barra inicial: os padrões do mod_alias correspondem ao caminho completo do URL.

RewriteRule (mod_rewrite) — necessária quando o redirecionamento depende de uma condição: o nome do anfitrião, o protocolo, uma cadeia de consulta, um agente de utilizador. Nada no mod_alias consegue avaliar isso.

RewriteEngine On
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]

Num .htaccess por pasta, o padrão fica sem a barra inicial — é por isso que aqui é ^(.*)$ e no exemplo do mod_alias é ^/blog/. Confundir isto é, de longe, a razão mais comum para uma regra copiada não fazer silenciosamente nada.

Usa mod_alias por omissão. Recorre a RewriteRule só quando precisares de uma condição. Menos expressões regulares, menos formas de errar.

Onde as regras têm de ficar

Os redirecionamentos personalizados ficam acima da linha # BEGIN WordPress. Não lá dentro.

# --- custom redirects ---
Redirect 301 /old-page/ https://example.com/new-page/
RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1

# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

Duas razões independentes:

  1. O WordPress reescreve o seu próprio bloco. Guardar Definições → Ligações permanentes chama flush_rewrite_rules(), que descarta tudo o que está entre os marcadores e volta a gerá-lo. Uma regra que ponhas lá dentro desaparece na próxima vez que alguém mexer nesse ecrã — possivelmente meses depois, sem ninguém ligar os dois acontecimentos.
  2. A ordem decide quem ganha. O bloco do WordPress termina com uma regra genérica que envia todos os pedidos que não sejam ficheiro nem pasta para index.php. Uma RewriteRule colocada depois nunca chega a correr.

Uma complicação que vale a pena admitir: o mod_alias e o mod_rewrite não correm pela ordem do ficheiro. O Apache processa o mod_alias durante a tradução do URL e o mod_rewrite por pasta mais tarde, na fase de fixup — por isso uma linha Redirect pode ganhar a uma RewriteRule que aparece acima dela no ficheiro. Se te vires a precisar dos dois em caminhos que se sobrepõem, escolhe um módulo para esse caminho e fica dentro dele. Depurar uma disputa entre mod_alias e mod_rewrite não compensa a hora que custa.

Barras finais

O próprio redirecionamento canónico do WordPress acrescenta uma barra final à maioria das ligações permanentes. Por isso isto:

Redirect 301 /old-page/ https://example.com/new-page

produz dois saltos — o teu 301 para o URL sem barra e depois o 301 do próprio WordPress a acrescentá-la. As cadeias continuam a transmitir sinais de posicionamento, mas desperdiçam orçamento de rastreio e acrescentam uma ida e volta a cada visitante.

Carrega primeiro o destino num navegador, copia o URL exatamente como fica na barra de endereço e usa esse. Se a tua estrutura de ligações permanentes acaba em .html ou não tem barra final, faz corresponder isso. Aqui não há resposta universalmente correta, apenas «faz corresponder o que o WordPress serve».

Testar sem te trancares fora

O .htaccess é lido em todos os pedidos. Um erro de sintaxe devolve um 500 no site inteiro, wp-admin incluído, por isso o caminho de recuperação tem de existir antes de precisares dele.

Faz primeiro uma cópia do ficheiro. Por SSH:

cp /path/to/wordpress/.htaccess /path/to/wordpress/.htaccess.bak

Se o site der 500, volta a colocar a cópia por cima do ficheiro estragado e o site regressa de imediato. Mantém uma sessão SFTP já aberta noutra janela — entrar de raiz com o site em baixo é onde começa o pânico. Repara que o apachectl configtest não analisa o .htaccess, por isso vai reportar uma configuração saudável enquanto o teu site está morto.

Testa com curl, não com um navegador. Os navegadores guardam as respostas 301 em cache com força e mostram-te alegremente o resultado de ontem:

curl -sI https://example.com/old-page/ | head -n 5

Lê duas coisas: a linha de estado deve dizer 301 e o cabeçalho Location: deve ser o URL final exato. Para veres a cadeia toda, segue-a:

curl -sIL https://example.com/old-page/ | grep -E '^(HTTP|Location)'

Mais do que uma linha HTTP/1.1 301 significa que construíste uma cadeia. Mais de cinco, aproximadamente, significa que construíste um ciclo, e o curl vai parar e dizer-to.

Usa um 302 enquanto ainda tiveres dúvidas. Um 302 não é guardado em cache da mesma forma, por isso um erro é reversível em segundos. Passa a 301 quando o curl mostrar o destino que pretendias. Isto não custa nada e já salvou mais sites do que qualquer outro hábito neste artigo.

Se uma regra parecer não fazer absolutamente nada, confirma que o Apache está sequer a ler o ficheiro. AllowOverride None na pasta faz o Apache ignorar o .htaccess por completo, e o nginx ignora-o totalmente — verifica o cabeçalho Server: com curl -I antes de te pores a depurar expressões regulares. Para montar o bloco com a sintaxe certa para a tua versão do Apache e o teu caminho de instalação, usa o gerador de .htaccess para WordPress.

Quando não usar de todo o .htaccess

Para um punhado de redirecionamentos que os editores tenham de gerir sozinhos, um plugin de redirecionamentos que guarde as regras na base de dados é a melhor ferramenta — sobrevive a mudanças de alojamento e não exige SSH. O compromisso é real: o PHP tem de arrancar para servir o redirecionamento, o que é mais lento do que o Apache a responder diretamente.

Usa o .htaccess para mudanças estruturais e permanentes — uma mudança de domínio, o renomear de uma secção, forçar https ou www. Usa um plugin para redirecionamentos editoriais pontuais. Fazer as duas coisas não tem problema desde que saibas que camada é dona de que URL, porque um redirecionamento definido em dois sítios é um erro à espera de uma tarde má.

FAQ

Perguntas

Onde ficam os redirecionamentos personalizados no ficheiro .htaccess do WordPress?

Acima da linha # BEGIN WordPress, nunca entre os marcadores. O WordPress regenera tudo o que está dentro desses marcadores sempre que alguém guarda o ecrã das ligações permanentes, por isso uma regra colocada lá dentro é apagada sem aviso. A colocação também importa pela ordem: o bloco do WordPress termina numa regra genérica que envia os pedidos não correspondidos para index.php.

Devo usar Redirect, RedirectMatch ou RewriteRule para um 301?

Usa Redirect para um único caminho conhecido, RedirectMatch quando um padrão cobre muitos URL e RewriteRule quando o redirecionamento depende de uma condição como o nome do anfitrião, o protocolo ou a cadeia de consulta. Redirect e RedirectMatch vêm do mod_alias e são mais simples. RewriteRule vem do mod_rewrite e é a única capaz de avaliar condições.

Porque é que o meu redirecionamento no .htaccess provoca um ciclo de redirecionamento?

Normalmente o destino continua a corresponder à regra que lá enviou o visitante, por isso a regra dispara indefinidamente. A outra causa comum é forçar https por trás de um balanceador de carga ou de uma CDN, onde o servidor vê http simples em todos os pedidos mesmo com o visitante já em https. Avalia antes o cabeçalho X-Forwarded-Proto.

A barra final faz diferença num redirecionamento do WordPress?

Faz. O redirecionamento canónico do WordPress acrescenta uma barra final à maioria das ligações permanentes, por isso apontar um 301 para um URL sem barra produz dois saltos em vez de um. Os redirecionamentos encadeados continuam a transmitir sinais de posicionamento, mas desperdiçam orçamento de rastreio e atrasam o visitante. Faz corresponder o URL final exato que o WordPress serve, barra incluída.

Como testo um redirecionamento no .htaccess sem partir o site?

Corre o curl com a opção de apenas cabeçalhos contra o URL antigo e lê o código de estado e o cabeçalho Location antes de confiar num navegador. Os navegadores guardam as respostas 301 em cache de forma agressiva e vão mostrar-te um resultado desatualizado. Guarda primeiro uma cópia do ficheiro que funciona, porque um erro de sintaxe no .htaccess devolve um 500 em todas as páginas, incluindo o wp-admin.

Porque é que o site inteiro devolveu um erro 500 depois de eu adicionar um redirecionamento?

Uma única diretiva mal formada no .htaccess derruba todos os URL abaixo dessa pasta, administração incluída. As causas comuns são um bloco IfModule por fechar, uma linha RewriteEngine On em falta, ou a sintaxe de acesso do Apache 2.2 e 2.4 misturada no mesmo ficheiro. Remove o último bloco que adicionaste e recarrega para confirmar.