Nginx – przydatne rewrite’y i różne sztuczki

Polubiłem Nginx’a i wykorzystuję go na coraz więcej sposobów. Kilka rzeczy udało mi się całkiem fajnie w nim skonfigurować i postanowiłem zebrać te przykłady by następnym razem gdy postanowię do nich sięgnąć nie musieć wertować konfigów po serwerach 🙂

Słowo wstępu

Niektóre rewrite’y kończą się znakiem ? – czemu?
Otóż Nginx próbuje automatycznie dodawać parametry na końcu przepisanego adresu. Jeśli jednak wykorzystamy zmienną $request_uri to ona sama w sobie zawiera już parametry zapytania (czyli to co w URI znajduje się po znaku ?) i właśnie dodanie pytajnika tuż za tą zmienną powoduje że argumenty nie są dublowane.
Ma to też zastosowanie gdy chcemy by rewrite kierował np. na główną stronę beż żadnych dodatkowych argumentów (zostaną one obcięte).
Więcej na ten temat można znaleźć w dokumentacji Nginx.

Inna warta wspomnienia uwaga dotyczy drobnej optymalizacji, o której warto pamiętać na etapie tworzenia rewrite’ów (można znaleźć masę kiepskich przykładów w sieci): na początku najlepiej jest stworzyć coś co działa (i przy małym ruchu może to być wystarczające) a później optymalizować – moje przykłady starałem się zoptymalizować według zalecanych praktyk.
Dlatego zamiast pisać:

rewrite ^(.*)$ $scheme://www.domain.com$1 permanent;

lepiej napisać:

rewrite ^ $scheme://www.domain.com$request_uri? permanent;

(nie wykorzystujemy przechowywania wartości dopasowania – mniejsze zużycie pamięci i lżejsza interpretacja REGEXP’a).
A jeszcze lepiej napisać:

return 301 $scheme://www.domain.com$request_uri;

(w ogóle nie wykorzystujemy REGEXP’ów praktycznie zerowy narzut na przetwarzanie) – dzięki za uwagę: lukasamd.

Przekierowanie starej domeny na nową

server {
    listen 80;
    server_name old-domain.com www.old-domain.com;
    return 301 $scheme://www.new-domain.com$request_uri;
    # rewrite ^ $scheme://www.new-domain.com$request_uri? permanent;
    # or
    # rewrite ^ $scheme://www.new-domain.com? permanent;
}

Wykorzystanie return w tej sytuacji jest nieco bardziej optymalne gdyż nie angażuje w ogóle silnika REGEXP a w tej sytuacji jest wystarczające.
Pierwsza linia z rewrite i $request_uri spowoduje przepisywanie też parametrów wywołań do nowej lokalizacji co jest jak najbardziej sensowne gdy pomimo domeny nie zmieniła się zbytnio struktura strony.
Jeśli strona jednak się zmieniła to możemy zdecydować o przekierowaniu bez parametrów – po prostu na główną stronę – i to robi druga linia.
W obu przypadkach parametr permanent nakazuje użycie kodu przekierowania HTTP 301 (Moved Permanently), co ułatwi zorientowanie się crawlerom że ta zmiana jest już na stałe.

Dodanie WWW na początku domeny

server {
    listen 80;
    server_name domaim.com;
    return 301 $scheme://www.domain.com$request_uri;
    #rewrite ^ $scheme://www.domain.com$request_uri? permanent;
    # or
    #rewrite ^(.*)$ $scheme://www.domain.com$1 permanent;
}

Przykład zakomentowany jest według dokumentacji mniej optymalny ale również zadziała. Reszta jest prosta i samoopisująca się 🙂
A to jeszcze bardziej ogólna wersja dla wielu domen:

server {
    listen 195.117.254.80:80;
    server_name domain.pl domain.eu domain.com;

    return 301 $scheme://www.$http_host$request_uri;
    #rewrite ^ $scheme://www.$http_host$request_uri? permanent;
}

Ta wersja wykorzystuje zmienną $http_host do przekierowania na domenę z zapytania (zmienna ta zawiera też numer portu jeśli jest niestandardowy np. 8080, w przeciwieństwie do zmiennej $host, która zawiera tylko domenę).

Usunięcie WWW z początku domeny

server {
    listen 80;
    server_name www.domain.com;
    return 301 $scheme://domain.com$request_uri;
    #rewrite ^ $scheme://domain.com$request_uri? permanent;
}

Czasami może się przydać jeszcze inny kawałek, gdy strona działa na wielu domenach i chcemy przekierować wszystkie:

server {
    server_name www.domain.com _ ;
    # server_name www.domain1.com www.domain2.com www.domain3.eu www.domain.etc.com;

    if ($host ~* www\.(.*)) {
        set $pure_host $1;
        return 301 $scheme://$pure_host$request_uri;
        #rewrite ^ $scheme://$pure_host$request_uri? permanent;
        #rewrite ^(.*)$ $scheme://$pure_host$1 permanent;
    }
}

Choć to podejście nie jest zalecane (pomimo zwięzłości). Lepiej zdefiniować dwa bloki server z domenami www.* i domenami bez www na początku. Ale z drugiej strony to cholernie wygodne… 😉

Przekierowanie “pozostałych” zapytań na domyślną domenę

server {
    listen 80 default;
    server_name _;
    rewrite ^ $scheme://www.domena.com;
    #rewrite ^ $scheme://www.domena.com/search/$host;
}

To bardzo przydatny przykład – czyli domyślny vhost, który “przyjmie” wszystkie zapytania do domen nie zdefiniowanych w konfiguracji i przekieruje na naszą “główną stronę”.
Zakomentowany przykład jest nieco bardziej przekombinowany bo próbuje wykorzystać wyszukiwarkę na naszej stronie do wyszukania “czegoś” pomocnego – z tym przykładem należy uważać bo jeśli do serwera trafi dużo błędnych zapytań to może zostać przeciążony “bzdetnymi” wyszukiwaniami.

Przekierowanie pewnych podstron po zmianie struktury strony

server {
    listen 80;
    server_name www.domain.com;

    location / {
        try_files $uri $uri/ @rewrites;
    }

    location @rewrites {
        rewrite /tag/something  $scheme://new.domain.com permanent;
        rewrite /category/hobby /category/painting permanent;
        # etc ...

        rewrite ^ /index.php last;
    }
}

Im starsza strona tym więcej zbiera się linków, których po prostu nie można usunąć, a które z racji wprowadzonych zmian nie mają prawa bytu w nowym układzie. Warto je przekierować w nowe miejsca, lub najbardziej odpowiadające/bliskie tym starym. Problemem może się wkrótce stać duża lista przekierowań, która zaciemni konfigurację.
Powyższy sposób w dość optymalny sposób porządkuje takie przekierowania – najpierw sprawdza czy przypadkiem nie próbujemy pobrać istniejących plików, jeśli nie to wrzuca nas nas na listę przekierowań, a jeśli i tu nic nie znajdzie to zapytanie przekazywane jest do głównego skryptu strony.

Przekierowanie w zależności od wartości parametru w URI

if ($args ~ producent=toyota){
    rewrite ^ $scheme://toyota.domena.com$request_uri? permanent;
}

To rzadko stosowane przekierowanie a w dodatku mało czytelnie i ponoć mało wydajne… Ale potrafi być bardzo przydatne gdy chcemy przepisać adres w zależności od wartości parametru np. gdy pewna podstrona doczeka się rozbudowy w zupełnie nowym serwisie lub gdy chcemy ładnie przekierować adresy ze starej strony na nową.

Blokowanie dostępu do ukrytych plików

location ~ /\. { access_log off; log_not_found off; deny all; }

Przyznaję – to nie rewrite… Ale ta linijka jest równie przydatna – pozwala zablokować możliwość pobierania ukrytych plików (np. .htaccess’ów po konfiguracji z Apachego).

Wyłączenie logowania dla robots.txt i favicon.ico

location = /favicon.ico { try_files /favicon.ico =204; access_log off; log_not_found off; }
location = /robots.txt  { try_files /robots.txt =204; access_log off; log_not_found off; }

To też nie rewrite – ale bardzo fajnie obsługuje sytuację gdy mamy i gdy nie mamy powyższych dwóch pliczków. Po pierwsze wyłącza logowanie i serwuje je gdy są dostępne. Gdy nie istnieją to serwuje puste pliki (kod 204) dzięki czemu nie przeszkadzają nam 404-ki 🙂

Blokowanie dostępu do obrazków dla nieznanych referererów

location ~* ^.+\.(?:jpg|png|css|gif|jpeg|js|swf)$ {
    # definiujemy poprawnych refererow
    valid_referers none blocked *.domain.com domain.com;
    if ($invalid_referer)  {
        return 444;
    }
    expires max;
    break;
}

Zabezpieczenie warte tyle co nic bo banalne do ominięcia – ale jeśli zdarzy się że ktoś postanowi wykorzystać grafikę z naszej stronki np. w aukcji na allegro czy własnym sklepie to tą prostą sztuczką możemy go przyciąć i przeważnie jest to wystarczające.
Muszę też zaznaczyć szczególne znaczenie wartości kodu błędu 444 w Nginx’ie – powoduje on zerwanie połączenia bez wysyłania jakiejkolwiek odpowiedzi. Jeśli nie chcemy być tak okrutni to możemy użyć innego kodu, np.: 403 albo 402 🙂

Przekierowanie ciekawskich w “ciemną dupę”

location ~* ^/(wp-)?admin(istrator)?/?  {
    rewrite ^ http://whatismyipaddress.com/ip/$remote_addr redirect;
}

Ten prosty redirect odwodzi wielu amatorów zbyt głębokiego penetrowania naszej strony… A pozostałych na pewno rozbawi 🙂

Inne przykłady konfiguracji na mojej stronie:
Nginx – hide server version and name in Server header and error pages
Nginx – kompresowanie plików dla gzip_static
Nginx – konfiguracja pod WordPress’a
Nginx – ustawienie domyślnego vhosta
Nginx – mój domyślny config

Źródła:
http://www.engineyard.com/blog/2011/useful-rewrites-for-nginx/
http://wiki.nginx.org/HttpRewriteModule

5 thoughts on “Nginx – przydatne rewrite’y i różne sztuczki”

    1. Jak najbardziej zgadza się – ale gdy zaczynałem z Nginx’em to rewrite’y wydawały mi się bardziej naturalne/zrozumiałe i w wielu miejscach powieliłem tą konfigurację. Może już czas zmienić przyzwyczajenia…

      Właśnie po to prowadzę bloga, by ktoś upomniał mnie gdy umie coś zrobić lepiej – dziękuję za zwrócenie uwagi na to polecenie.

    2. Przyglądnąłem się jeszcze raz tym przykładom i jest tam dużo więcej miejsc na wykorzystanie return’a – muszę koniecznie przetestować czy będzie się to zachowywać równie dobrze co rewrite’y.

    1. P.S. Bo był zrobiony pod stary szablon i jeszcze nie miałem czasu poprawić. Ale w trosce o “ukochane bot’y” na pewno znajdę czas 😉
      P.S.1. Był ładniejszy – owszem. Ale nie lepszy bo nie był responsywny – stronka źle wyglądała na tabletach i komórkach. Choć oczywiście to wszystko jest subiektywne 😉

Leave a Reply

Your email address will not be published. Required fields are marked *