Reescrita de URLs com mod_rewrite: conceitos, regras e depuração

Capítulo 12

Tempo estimado de leitura: 8 minutos

+ Exercício

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/302 e o navegador muda a URL (isso também pode ser feito com mod_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 On

Sem isso, as regras não são avaliadas.

RewriteRule

Define uma regra de transformação. Estrutura geral:

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

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). Use R=301 quando 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 .htaccess em 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 .htaccess estiver no DocumentRoot).

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 /contato e verifique que o conteúdo é entregue.
  • Acesse um caminho inexistente (ex.: /nao-existe) e confirme que não vira /nao-existe.php se 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/123 e confirme no script que $_GET['id'] é 123.
  • Acesse /produto/123?ref=ads e confirme que id=123 e ref=ads chegam juntos (com QSA).

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]
  • NC torna 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:trace3

Para 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:trace3 no 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/302 em 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 .htaccess a 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.

Agora responda o exercício sobre o conteúdo:

Qual é a diferença prática entre reescrita interna e redirecionamento externo ao usar mod_rewrite?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Reescrita interna altera apenas o mapeamento dentro do servidor, mantendo a URL visível. Redirecionamento externo envia um status 301/302 e o navegador passa a exibir a nova URL.

Próximo capitúlo

Controle de acesso e restrição de diretórios no Apache: Require, Directory e proteção de áreas

Arrow Right Icon
Capa do Ebook gratuito Apache para Iniciantes: Configuração Essencial, Virtual Hosts e Segurança Básica
75%

Apache para Iniciantes: Configuração Essencial, Virtual Hosts e Segurança Básica

Novo curso

16 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.