O que é reescrita de URL (rewrite) e quando usar
A reescrita de URLs com mod_rewrite permite transformar uma URL “bonita” (ex.: /produto/123) em uma requisição interna para um arquivo ou script real (ex.: /produto.php?id=123), sem que o usuário veja essa mudança. Também pode ser usada para aplicar regras de segurança simples (bloquear padrões), normalizar URLs e criar redirecionamentos condicionais.
É importante distinguir dois comportamentos:
- Reescrita interna: o navegador continua vendo a URL original; o Apache apenas mapeia internamente para outro caminho/arquivo.
- Redirecionamento externo: o Apache responde com
301/302e o navegador muda a URL (isso também pode ser feito commod_rewrite, mas para redirecionamentos simples geralmente é melhor usar as diretivas de redirecionamento mais diretas).
As três peças principais: RewriteEngine, RewriteRule e RewriteCond
RewriteEngine
Ativa ou desativa o mecanismo de reescrita no contexto atual:
RewriteEngine OnSem isso, as regras não são avaliadas.
RewriteRule
Define uma regra de transformação. Estrutura geral:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
RewriteRule PADRAO SUBSTITUICAO [FLAGS]- PADRAO: expressão regular (regex) aplicada ao caminho da URL.
- SUBSTITUICAO: para onde reescrever (pode incluir grupos capturados como
$1,$2). - FLAGS: modificadores entre colchetes (ex.:
[L,R=301,QSA]).
RewriteCond
Adiciona condições para que uma RewriteRule seja aplicada. Uma ou mais condições podem preceder uma regra; todas precisam ser verdadeiras (por padrão) para a regra disparar.
RewriteCond TESTE CONDICAO [FLAGS]Exemplos de TESTE comuns:
%{REQUEST_URI}: caminho solicitado (ex.:/produto/123).%{QUERY_STRING}: query string (ex.:id=123).%{REQUEST_FILENAME}: caminho no filesystem correspondente ao request (útil para checar se arquivo/diretório existe).
Flags mais usadas (L, R, QSA) e o que realmente fazem
- L (Last): para o processamento de regras no conjunto atual quando esta regra casa. Ajuda a evitar múltiplas reescritas inesperadas.
- R ou R=301/302: faz redirecionamento externo. Sem código, o padrão costuma ser
302(temporário). UseR=301quando tiver certeza de que é permanente. - QSA (Query String Append): preserva e adiciona a query string original à nova. Sem
QSA, a query string pode ser substituída pela definida na regra.
Exemplo rápido do efeito do QSA:
# URL: /busca/tenis?ordem=preco => /busca.php?q=tenis&ordem=preco (com QSA) /busca.php?q=tenis (sem QSA)Regras no VirtualHost vs regras em .htaccess (diferenças práticas)
Onde colocar
- No VirtualHost (ou em arquivos incluídos por ele): preferível quando você tem acesso à configuração do servidor. É mais performático e previsível.
- No .htaccess: útil quando você não pode editar o vhost (ex.: hospedagem compartilhada). O Apache precisa verificar o
.htaccessem cada requisição, o que tem custo.
Diferença crítica: o “caminho” que a regex enxerga
- Em VirtualHost (contexto de servidor): a regra normalmente casa com o caminho começando por
/(ex.:^/produto/([0-9]+)$). - Em .htaccess (contexto por diretório): o prefixo do diretório é removido e o caminho geralmente vem sem a barra inicial (ex.:
^produto/([0-9]+)$se o.htaccessestiver noDocumentRoot).
Isso explica por que uma regra “igual” pode funcionar no vhost e falhar no .htaccess. Ao migrar regras entre contextos, ajuste o padrão.
Pré-requisito para .htaccess funcionar
Para o Apache ler regras de rewrite no .htaccess, o diretório precisa permitir overrides. Em geral, é necessário que o bloco de diretório permita AllowOverride FileInfo (ou AllowOverride All). Prefira liberar apenas o necessário.
Exemplo 1 (progressivo): remover .php da URL
Objetivo: permitir que /contato sirva o conteúdo de /contato.php (reescrita interna). Também é comum redirecionar /contato.php para /contato para padronizar (redirecionamento externo).
1A) Reescrita interna: /contato -> /contato.php
No VirtualHost (ajuste caminhos conforme seu site):
RewriteEngine On
# Se o arquivo/diretório existir, não reescreva
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Se não existir, tente adicionar .php
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^/(.+)$ /$1.php [L]No .htaccess no DocumentRoot (note a ausência de “/” no padrão):
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^(.+)$ $1.php [L]Passo a passo para testar:
- Crie/garanta que existe
contato.php. - Acesse
/contatoe verifique que o conteúdo é entregue. - Acesse um caminho inexistente (ex.:
/nao-existe) e confirme que não vira/nao-existe.phpse o arquivo não existir.
1B) Canonicalização: redirecionar /contato.php -> /contato
Agora vamos padronizar a URL visível. Isso é um redirecionamento externo (o navegador muda a URL).
No VirtualHost:
RewriteEngine On
# Redireciona qualquer /algo.php para /algo (mantendo query string)
RewriteRule ^/(.+)\.php$ /$1 [R=301,L]No .htaccess:
RewriteEngine On
RewriteRule ^(.+)\.php$ $1 [R=301,L]Cuidado com loop: se você tiver também a regra que reescreve /contato para /contato.php, tudo bem, porque uma é redirecionamento (cliente) e a outra é reescrita interna (servidor). O loop acontece se você redirecionar em ambos os sentidos ou se a regra de redirecionamento também casar com a URL “limpa”.
Exemplo 2 (progressivo): mapear /produto/123 para um script
Objetivo: transformar /produto/123 em /produto.php?id=123. Aqui a reescrita interna é ideal.
2A) Regra básica
No VirtualHost:
RewriteEngine On
RewriteRule ^/produto/([0-9]+)$ /produto.php?id=$1 [L]No .htaccess no DocumentRoot:
RewriteEngine On
RewriteRule ^produto/([0-9]+)$ produto.php?id=$1 [L]2B) Preservando query string com QSA
Se você quiser permitir parâmetros adicionais (ex.: /produto/123?ref=ads), use QSA:
RewriteRule ^produto/([0-9]+)$ produto.php?id=$1 [L,QSA]Passo a passo para validar:
- Acesse
/produto/123e confirme no script que$_GET['id']é123. - Acesse
/produto/123?ref=adse confirme queid=123eref=adschegam juntos (comQSA).
2C) Evitando reescrever arquivos reais
Se existir um diretório real /produto/ com arquivos estáticos, você pode querer evitar que a regra interfira quando o arquivo existe:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^produto/([0-9]+)$ produto.php?id=$1 [L,QSA]Exemplo 3 (progressivo): bloquear padrões simples
mod_rewrite pode ajudar a bloquear padrões óbvios de abuso (sem substituir um WAF). A ideia é negar cedo requisições com padrões suspeitos.
3A) Bloquear acesso a arquivos sensíveis por extensão
Ex.: bloquear tentativa de acessar .env, .ini, .log via URL.
No VirtualHost:
RewriteEngine On
RewriteRule ^/(.+)\.(env|ini|log)$ - [F,L]No .htaccess:
RewriteEngine On
RewriteRule ^(.+)\.(env|ini|log)$ - [F,L]A flag F retorna 403 Forbidden.
3B) Bloquear padrões na query string
Ex.: bloquear tentativas simples com palavras-chave comuns de SQL injection na query string. Isso pode gerar falsos positivos; use com cuidado.
RewriteEngine On
RewriteCond %{QUERY_STRING} (union|select|concat|sleep) [NC]
RewriteRule ^ - [F,L]NCtorna a condição case-insensitive.- A regra
^ -significa “não reescreva o caminho”, apenas aplique a ação (aqui,F).
Depuração: como enxergar o que o mod_rewrite está fazendo
1) Comece com testes controlados
- Teste uma regra por vez.
- Evite aplicar regras globais sem condições (principalmente em sites com muitas rotas).
- Use URLs simples e anote o comportamento esperado (reescrita interna vs redirecionamento).
2) Use LogLevel para rastrear reescritas
Você pode aumentar o nível de log para o módulo de rewrite e acompanhar no log de erro. Em Apache 2.4, é comum usar:
LogLevel warn rewrite:trace3Para depuração mais detalhada, aumente gradualmente (trace4, trace5). Evite deixar níveis muito altos em produção por muito tempo, pois pode gerar muitos logs.
Passo a passo sugerido:
- Ative temporariamente
rewrite:trace3no vhost do site que você está depurando. - Recarregue o Apache.
- Faça uma requisição que deveria casar com a regra.
- Verifique as entradas geradas e confirme: qual regra casou, quais grupos (
$1, etc.) foram capturados e qual substituição foi aplicada.
3) Verifique cuidadosamente loops de redirecionamento
Loops são comuns quando uma regra de redirecionamento (com R) também casa com a URL já redirecionada, ou quando duas regras se “desfazem” mutuamente.
Sinais típicos:
- Navegador mostra “too many redirects”.
- Você vê várias respostas
301/302em sequência para a mesma navegação.
Checklist para evitar loops:
- Ao usar
R, garanta que a regra não casa com o destino final (use padrões mais específicos ou condições). - Use
[L]para interromper o processamento quando apropriado. - Se estiver canonicalizando (ex.: remover
.php), faça o redirecionamento apenas quando a URL realmente terminar com.php. - Quando combinar “URL limpa -> arquivo .php” (interna) com “.php -> URL limpa” (externa), garanta que a regra externa não seja acionada por reescritas internas (normalmente não é, mas regras mal posicionadas/condicionadas podem confundir).
4) Atenção ao contexto: regra no vhost não é igual à do .htaccess
Se uma regra funciona no .htaccess e falha no vhost (ou vice-versa), revise:
- Se o padrão precisa de
^/(vhost) ou não (htaccess). - Se o caminho de substituição precisa ser absoluto (
/script.php) ou relativo (script.php) conforme o contexto. - Se há condições baseadas em
%{REQUEST_FILENAME}que mudam conforme o diretório onde a regra está.
Orientação prática: prefira o VirtualHost quando possível
- Performance: regras no vhost evitam a busca e leitura de
.htaccessa cada requisição. - Controle: regras ficam centralizadas e mais fáceis de auditar.
- Menos surpresas: o contexto do vhost é mais previsível para URLs completas e para organização de regras por site.
Uma abordagem comum é: desenvolver e estabilizar as regras no vhost (ou em um arquivo incluído por ele) e usar .htaccess apenas quando for realmente necessário por restrição de acesso à configuração do servidor.