Capire il modulo Rewrite di Apache una volta per tutte
da Massimo Della Rovere · pubblicato il 2 Settembre, 2014 · modificato il 10 Luglio, 2016

Il modulo di Rewrite presente in Apache è sempre stato un’elemento di discussione che riguarda gli sviluppatori web, questo è dovuto al fatto che da una parte risolve diversi problemi legati al reindirizzamento e alla gestione degli URL, ma dall’altra il formato delle regole non è facile e può portare facilmente a delle incomprensioni. Infatti gli utilizzi sono diversi, le forme di inserimento hanno diverse varianti e le regole vengono indicate con delle espressioni regolari, il tutto genera un mix pericoloso e complesso.

L’unica maniera per riuscire a prendere confidenza con questo strumento è quello di studiare con pazienza le sue caratteristiche principali. Quindi vedere a cosa serve e che compiti specifici può risolvere, come devono essere inserite le regole, capire la loro sequenza e la sintassi utilizzata, studiare le espressioni regolari più importanti. Forse non riusciremo a chiarire proprio tutto in questo articolo, ma cercheremo di fare quel passo in avanti che ci permetterà di non avere più paura di questo strumento :)

Introduzione

Il Rewrite è un modulo nativo del famoso webserver Apache, normalmente lo si trova sempre installato, specialmente nei servizi di hosting. Se invece utilizzate un server dedicato o un’ambiente virtuale in cui avete installato da soli il software non è detto che l’opzione sia attiva di default, quindi è sempre meglio controllare. In Apache 2.x per attivare il modulo basta eseguire i seguenti comandi da un terminale:

sudo a2enmod rewrite          // Attivazione del modulo
sudo service apache2 restart  // Riavvio di Apache

Una volta che avete eseguito i comandi, per utilizzare il modulo bisogna specificare il seguente codice, che può essere inserito nel file .htaccess presente nella root del nostro sito web o nel file di configurazione generale che riguarda il virtualhost.

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
</IfModule>

Le istruzioni che sto presentando in questo articolo sono state testate su Ubuntu Server 14.04 LTS e Apache 2.4, se avete un’ambiente diverso e qualche comando non dovesse funzionare, accertatevi se esistono delle varianti rispetto alla vostra versione, una buona risorsa di partenza è proprio la documentazione ufficiale di apache.

Concetti generali

La funzione di Rewrite sostanzialmente serve a riscrivere le URL di una richiesta dopo che sono stati accertati alcuni controlli. Ad esempio possiamo convertire degli URL più familiari e meglio graditi dai motori di ricerca in richieste interne con l’elenco dei parametri secondo lo standard classico HTTP. Vi riporto qui di seguito un esempio:

// Se viene richiesto un URL http://mydomain.com/prodotto/1/
// eseguo una riscrittura http://mydomain.com/prodotto.php?codice=1

RewriteRule ^prodotto/([0-9]+)/$ /prodotti.php?codice=$1

Adesso non vi preoccupate delle espressioni regolari che sono state usate, le vedremo meglio nei paragrafi successivi, quindi concentratevi sulla logica della riscrittura. In questo esempio la riscrittura è interna e l’utente non si accorge di quello che avviene, infatti la stringa URL per l’utente finale rimane sempre la stessa.

Al contrario esistono delle rewrite esterne che modificano la richiesta URL originale in una nuova stringa URL che forzerà il browser dell’utente ad eseguire una nuova richiesta HTTP, questo tipo di rewrite è ovviamente percepita dall’utente, che si vede cambiare la stringa URL usata nella richiesta originale. Ad esempio, possiamo usare questa tecnica per forzare una sotto cartella che non usiamo più ad una nuova locazione.

// Se viene richiesto un URL http://mydomain.com/oldfolder/listino/
// eseguo una riscrittura http://mydomain.com/newfolder/listino/

RewriteRule ^/oldfolder/(.*) /newfolder/$1 [R=301,L]

Nel file .htaccess possiamo inserire diversi RewriteRule, i quali saranno eseguiti nella sequenza naturale, quindi dobbiamo sempre tenere conto di dove inseriamo le regole e i flag che utilizzeremo a fine regola tra le parentesi quadre. Vi riporto un esempio sempre per capire il concetto, non vi preoccupate adesso della sintassi che non capite.

// In questo esempio http://mydomain.com/abcdef/TEST/
// sarà riscritto in http://mydomain.com/ghilmn/TEST/
// in quanto le rewrite saranno eseguite una dopo l'altra

RewriteRule /abcdef/(.*) /123456/$1
RewriteRule /123456/(.*) /ghilmn/$1

// In questo esempio http://mydomain.com/abcdef/TEST/
// sarà riscritto in http://mydomain.com/123456/TEST/
// in quanto la prima rewrite ha il flag L=Last e si ferma

RewriteRule /abcdef/(.*) /123456/$1 [L]
RewriteRule /123456/(.*) /ghilmn/$1

Il modulo di Rewrite per Apache è cosi potente che non solo è in grado di controllare la struttura di una stringa URL ma può eseguire queste riscritture solo se si verificano dei determinati controlli sulle variabili d’ambiente. Ad esempio possiamo eseguire le regole di rewrite solo se si sta utilizzando un determinato browser, se viene specificato un nome host particolare o se il client appartiene ad un determinato range IP.

Per utilizzare questa tecnica dobbiamo utilizzare uno o più comandi RewriteCond prima del comando RewriteRule che abbiamo visto negli esempi precedenti, usando questa struttura il comando RewriteRule sarà preso in considerazione solo se saranno vere le condizioni indicate nei comandi di RewriteCond, vediamo un’esempio per capire:

// Eseguo un rewrite se sto utilizzando IE6-8
// la variabile da controllare è HTTP_USER_AGENT

RewriteCond %{HTTP_USER_AGENT} "MSIE [6-8]" [NC]
RewriteRule ^/$ /sei-proprio-antico.html [L]

// Come ho detto precedentemente possiamo anche utilizzare
// diverse condizioni prima del comando RewriteRule

RewriteCond %{REMOTE_ADDR} !^192\.168\..* [OR]
RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1 [OR]
RewriteCond %{REMOTE_HOST} !^www.dom.com$ [OR]
RewriteRule ^/$ /guardati-questo.html [L]

Arrivati a questo punto, anche se sono sicuro che ci sono dei dubbi su alcune sintassi e su alcuni caratteri utilizzati, possiamo dire che abbiamo capito una cosa ben precisa e che possiamo riassumere come segue: Per inserire le regole di rewrite dobbiamo stare attenti alla sequenza e possiamo utilizzare due forme di controllo come segue:

// Attivazione delle funzioni di rewrite se 
// il modulo risulta caricato in Apache

<IfModule mod_rewrite.c>

    RewriteEngine On

    // (1) inserisco prima i RewriteCond e dopo il RewriteRule che sarà
    // preso in considerazione solo se sono rispettate le condizioni

    RewriteCond VARIABILE CONTROLLO [FLAGS]
    RewriteRule CONTROLLO REWRITE   [FLAGS]

    // (2) inserisco le RewriteRule che verranno elaborate nella
    // sequenza naturale di inserimento ad eccezione di alcuni FLAG

    RewriteRule CONTROLLO REWRITE   [FLAGS]
    RewriteRule CONTROLLO REWRITE   [FLAGS]
    RewriteRule CONTROLLO REWRITE   [FLAGS]

</IfModule>

VARIABILE: specificare nel formato %{NOME} la variabile d’ambiente che dobbiamo controllare come condizione. Le variabili a disposizione sono tantissime, ad esempio abbiamo REMOTE_ADDR, REMOTE_HOST, REMOTE_PORT, REMOTE_USER, etc.

CONTROLLO: sarà indicata una stringa che verrà analizzata come condizione della regola, questa stringa può contenere delle espressioni regolari, in quanto sarà possibile eseguire controlli complessi come la stringa inizia con, contiene questo, finisce, etc.

FLAGS: sono dei codici prestabiliti che vanno indicati tra parantesi quadre e sono divisi tra di loro da una virgola, come ad esempio [R=301,QSA,L], in questo articolo è possibile vederne solo alcuni, ma li trovate tutti spiegati nella documentazione ufficiale.

Espressioni regolari

Se avete avuto il coraggio di seguirmi fino a qui significa che avete la voglia di imparare, quindi passiamo a vedere alcune regole delle espressioni regolari, in maniera tale che le regole che abbiamo visto fino adesso vi diventino ancora più chiare :)

Le espressioni regolari contengono dei caratteri speciali o delle sintassi che identificano un tipo di controllo da eseguire, come ad esempio inizia con, contiene questo, etc. Per il momento iniziamo ad elencare alcuni operatori e poi faremo qualche considerazione.

PRG Gruppo Pattern Spiegazione
001 Identificatore di testo . qualsiasi carattere presente
002 Identificatore di testo [abc] a, b oppure c
003 Identificatore di testo [a-z] tutti i valori compresi tra a e z
004 Identificatore di testo [^abc] né a, né b, né c
005 Identificatore di testo abc|def abc oppure def
006 Quantificatore ? 0 o 1 occorrenze del testo precedente
007 Quantificatore * 0 o N occorrenze del testo precedente
008 Quantificatore + 1 o N occorrenze del testo precedente
009 Ancora ^ inizio stringa
010 Ancora $ fine della stringa
011 Escape \ usare come escape dei caratteri comando
012 Negazione ! negazione, usare insieme ai precedenti
013 Raggruppamento ( ) raggruppare come singola unità atomica

Adesso con un po’ di pazienza andiamo a vedere qualche esempio in cui possiamo vedere questa tabella in azione. Iniziamo dagli identificatori di testo.

// PRG=001 le parentesi raggruppano una singola unità che
// sta ad indicare "." = qualsiasi carattere "*" = 0 o N occorrenze
 
RewriteRule /abcdef/(.*) /123456/$1

http://mydomain.com/abcdef/        SI perché permesse 0 occorrenze
http://mydomain.com/abcdef/qwerty/ SI perché permesse N occorrenze

Passiamo al secondo identificatore dove è possibile indicare i singoli caratteri che devono essere presenti nella parte di stringa indicata dalla sintassi [abc].

// PRG=002 le parentesi raggruppano una singola unità che
// sta ad indicare "[abc]" = a,b o c "?" = 0 o 1 occorrenza 

RewriteRule /abcdef/([abc]?) /123456/$1

http://mydomain.com/abcdef/a  = SI
http://mydomain.com/abcdef/b  = SI
http://mydomain.com/abcdef/ab = NO perché sono due occorrenze

Il terzo identificatore di testo indica che la porzione di stringa indicata deve contenere dei caratteri compresi tra un range specificato nelle seguente forma [a-z].

// PRG=003 le parentesi raggruppano una singola unità che
// sta ad indicare "[a-z]" compreso tra a/z "*" = 0 o N occorrenze 

RewriteRule /abcdef/([a-z]*) /123456/$1

http://mydomain.com/abcdef/abc  = SI
http://mydomain.com/abcdef/abce = SI
http://mydomain.com/abcdef/abcA = NO la maiuscola non è nel range

Dato che l’identificatore 4 è uguale al 2 in forma negativa, passiamo al numero 5 dove bisogna controllare una stringa o un’altra presente nella porzione indicata.

// PRG=005 le parentesi raggruppano una singola unità che
// sta ad indicare "[ab|cd]" ab o cd "*" = 0 o N occorrenze 

RewriteRule /abcdef/([con|per|insieme]*) /123456/$1

http://mydomain.com/abcdef/con  = SI
http://mydomain.com/abcdef/per  = SI
http://mydomain.com/abcdef/solo = NO non è presente nei valori

Dopo l’analisi di questi esempi avrete sicuramente capito anche le occorrenze, quindi passiamo ad un esempio generale, preso da un codice inserito precedentemente per vedere gli operatori che ci sono rimasti da analizzare, che sono anche i più semplici.

// Nelle condizioni successive troviamo diversi comandi
// ad esempio !=Negazione ^=Inizia $=Fine \=Escape

// Significato riga (1) = se non inizia con 192.168.(qualsiasi)
// Significato riga (2) = se non inizia con 127.0.0.1
// Significato riga (3) = se non inizia e finisce con www.dom.com

RewriteCond %{REMOTE_ADDR} !^192\.168\..*   [OR]
RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1   [OR]
RewriteCond %{REMOTE_HOST} !^www\.dom\.com$ [OR]

// Perchè usiamo il comando \=Escape ? Per il semplice fatto
// che il punto sarebbe interpretato come "." = qualsiasi carattere
// invece noi vogliamo che sia letto proprio come semplice punto

RewriteRule ^/$ /guardati-questo.html [L]

Si sono accesse le lampadine ? adesso ci dovremmo essere, non vi voglio più vedere in community a fare sempre le stesse domande sulle regole di rewrite per .htaccess :) A parte gli scherzi se avete ancora dei dubbi scrivete pure nei commenti che cercherò di rispondervi il prima possibile. Però prima mi promettete di leggere tutto :)

Alcuni aspetti avanzati

Negli esempi precedenti abbiamo visto che con RewriteCond si esegue un controllo sulle variabili d’ambiente che vengono confrontate con un’espressione regolare, in realtà è possibile indicare anche delle condizioni alternative, vi riporto un’esempio:

// Se il file specificato in URL non esiste "!-f" o non
// rappresenta una directory "!-d" verrà richiamato index.php

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

Adesso possiamo leggere: se le due condizioni risultano vere, quindi la stringa URL non rappresenta un file o una directory esistente, viene eseguito il comando RewriteRule che per “.” qualsiasi carattere e quindi per qualsiasi URL viene richiamato index.php, e dato che c’è la “/” iniziale verrà preso dalla directory root, oltre a questo possiamo osservare che viene indicato il flag [L], il quale indica che questo è l’ultimo controllo.

Per fare un’esempio pratico, se la richiesta si riferisce ad un file CSS che esiste verrà richiamato quest’ultimo, stessa cosa per un URL di un’immagine, ma se specifichiamo un URL che non corrisponde a nessuna risorsa allora verrà elaborato il file index.php della directory principale. Questo è il comportamento di molti CMS come WordPress.

Vi riporto qui di seguito alcune condizioni che potete utilizzare con RewriteCond per controllare diversi aspetti che riguardano i controlli senza espressioni regolari:

Condizione Descrizione generale
-f verifica l’esistenza di un file sul file system
-d verifica l’esistenza di una directory sul file system
-s verifica che la dimensione del file non sia nulla (vuoto)
-l verifica che il file sia un link simbolico
< “testo” verifica che la stringa di testo è minore di “testo”
> “testo” verifica che la stringa di testo è maggiore di “testo”
= “testo” verifica che la stringa di testo è uguale a “testo”

Esistono altre tipologie di condizioni e diverse maniere di attuazione, però il compito di questo articolo è quello di mettervi sulla giusta strada per poi andare ad approfondire nella documentazione ufficiale in cui trovate tutti i particolari che vi possono servire.

11 Commenti

  1. Ciao Massimo, letto tutto, ma faccio fatica ad applicare una regola, che forse per voi è semplice Un sito (technosmedica.it) è stato diviso su due domini. Devo reindirizzare cose del tipo: poliambulatorio-specialistico-aosta.php?id=1016&l=it su un nuovo dominio: https://www.premiummedica.it/poliambulatorio-specialistico-aosta.php?id=1016&l=it siccome i parametri sono fondamentali per recuperare il giusto contenuto, e non voglio perdere seo, devo fare il giusto redirect 301 per le 20/25 pagine che adesso sono presenti solo su premiummedica.it A volte c'è solo l'id della pagina, altre volte anche la lingua Come dovrei fare? grazie mille

  2. Ciao Massimo, ho una domanda da farvi. Ho un ecommerce realizzato in prestashop con un ottima indicizzazione nella cartella root del mio server e un altro ecommerce realizzato da zero con wordpress in una sotto cartella della root chiamata "new". Il punto è che vorrei eliminare il vecchio sito fatto con prestashop ed utilizzare il nuovo fatto con wordpress ma allo stesso modo non vorrei perdere l'indicizzazione acquisita con il vecchio sito. Come posso risolvere? E' sufficiente che inserisco nel file .htaccess di prestashop la seguente regola : RedirectMatch 301 /new/(.*) http://www.sitoweb.com/$1 ?Posso cancellare il contenuto della root dopo ? Grazie

  3. Buongiorno Massimo, Un sincero e sentito grazie per la tua indicazione che mi ha permesso di risolvere il problema. Ti auguro tutto il bene del mondo.

  4. Buonasera Massimo, mi sono imbattuto in questo tuo post che ho trovato interessantissimo. Ti spiego il mio problema. Ho un sito sviluppato con wordpress dove, per diversi motivi, sono stato costretto a cambiare diversi server, affidandomi anche ad un amico per alcune cose in cui non ho competenza. L'amico mi ha cambiato la configurazione dei permalink, ed ora mi trovo un mare di errori 404 perchè il redirect funziona si, con i link del sito, ma avendo io tante pagine con link interni che rimandano ad altre pagine del sito, il link interni non funzionano. In pratica prima il permalink precedente prevedeva la data e gli errori 404 sono tutti del tipo: https://miosito/data/ titolo se da un link con errore 404 tolgo la data, il post diventa visibile, perciò diventa: https:/miosito/titolo Mi sembra di capire che facendo rewriterule su .htacess sarebbe possibile far intendere, a livello server, di fare un redirect per tutti i post che hanno nel link la data. Non so se sono riuscito a spiegarmi bene, però spero di si. Puoi, per favore, indottrinarmi sull'argomento? Ti sarei veramente grato.

  5. Ciao Gino, sicuramente puoi risolvere questo problema con una regola di redirect da inserire sul tuo file .htaccess. Penso che se cerchi su google tipo (wordpress redirect without date permalink) trovi diversi articoli che ti possono aiutare. Ti allego un link qui di seguito e ti consiglio di leggere l'ultimo esempio riportato:

    https://perishablepress.com/redirect-wordpress-date-archives-htaccess/

  6. Ciao Massimo, ho letto tutto fino in fondo e adesso mi fuma il cervello :D. Posso chiedere un aiutino? Io avrei bisogno di reindirizzare vecchi link sparsi per il web relativi al mio vecchio sito di cui ho mantenuto il dominio.it, trasferito presso lo stesso hosting dell'attuale .net su cui ho costruito il nuovo sito. Ho fatto il redirect nell'.htaccess del vecchio ed è ok, i vecchi link mi rimandano al nuovo sito, ma tutti con 404. Ora dovrei specificare il 301 nell'.htaccess del nuovo sito, e sarebbe bello non doverlo fare uno ad uno (sono cira 200 link).

    L'.htaccess sotto la root public html contiene questo:

    RewriteEngine On 
    RewriteBase / 
    RewriteRule ^index.php$ - [L] 
    
    # add a trailing slash to /wp-admin 
    RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L] 
    
    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d 
    RewriteRule ^ - [L] 
    
    RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L] 
    RewriteRule ^([_0-9a-zA-Z-]+/)?(.*.php)$ $2 [L] 
    RewriteRule . index.php [L]

    Ho provato ad aggiungere la seguente stringa, ma non funziona:

    RedirectMatch 301 /1/(.*) http://claudiagalli.net//$1

    Tutti i vecchi link iniziano con /1/, sia gli articoli che le pagine, quindi l'ho considerata una directory (non so se ho fatto bene...). Vorrei comunque che approdassero alla home del nuovo sito. Che stringa devo inserire?

    Ti ringrazio tanto per il prezioso supporto.

  7. Ciao Claudia, le espressioni regolari sono un argomento sempre difficile, non tanto per la teoria ma per il fatto che le usiamo poco e quando servono dobbiamo ricordarci di tutto quello studiato in passato :D

    Il tuo .htaccess sembra che sia una configurazione multisite, io aggiungerei dopo la directiva RewriteBase la seguente stringa, (da provare in quanto scrivere le espressioni a memoria non è il massimo).

    RewriteBase /
    RewriteRule ^1/(.*)$ /$1 [R=301,NC,L]

    Il simbolo ^ significa inizia con e lo slash iniziale non deve essere calcolato, esegue un redirect sullo stesso dominio quindi non è necessario indicarlo di nuovo. Ci posso essere soluzioni diverse prova a vedere su questa pagina che ti indico:

    Redirect subfolders: https://goo.gl/oxEXaK

  8. Ciao Massimo, grazie per questo articolo che mi ha veramente aiutato molto. Dove posso trovare l'elenco completo delle variabili che hai indicato nell'articolo come ad esempio REMOTE_ADDR, REMOTE_HOST, REMOTE_PORT, REMOTE_USER etc.

  9. Ciao Italo, sono dei valori chiamati Server Variable e le trovi nella documentazione ufficiale di apache.

    https://httpd.apache.org/docs/trunk/expr.html#vars
    https://httpd.apache.org/docs/trunk/mod/mod_rewrite.html

  10. Questo è un articolo da condividere 100 volte. Scritto in modo chiaro e impossibile da non capire. Grazie, condivido subito sul mio profilo facebook.

  11. Grazie Massimo, finalmente alcune cose mi sono più chiare adesso. Penso che questa sia la parte più difficile di una configurazione apache.

condividi