Usuń „www” i przekieruj do „https” za pomocą nginx

57

Chcę utworzyć regułę w Nginx, która robi dwie rzeczy:

  1. Usuwa „www”. z URI żądania
  2. Przekierowuje do „https”, jeśli identyfikator URI żądania to „http”

Istnieje wiele przykładów, jak wykonać każdą z tych czynności osobno, ale nie mogę znaleźć rozwiązania, które działa poprawnie (tzn. Nie tworzy pętli przekierowania i poprawnie obsługuje wszystkie przypadki).

Musi obsłużyć wszystkie te przypadki:

1. http://www.example.com/path
2. https://www.example.com/path
3. http://example.com/path
4. https://example.com/path

Wszystko to powinno zakończyć się na https://example.com/path (# 4) bez zapętlania. Jakieś pomysły?

Devin
źródło
Właśnie przekierowałem www.mydomain.com na mydomain.com na poziomie DNS i dodałem 301 dla non-https do https w nginx. Wygląda na to, że powinno być dobrze ¯ \ _ (ツ) _ / ¯
jonathanbell

Odpowiedzi:

94

Najlepszym sposobem na osiągnięcie tego jest użycie trzech bloków serwera: jeden do przekierowania http na https, jeden do przekierowania nazwy www https na no-www i jeden do obsługi żądań. Powodem użycia dodatkowych bloków serwera zamiast ifs jest to, że wybór serwera jest dokonywany przy użyciu tabeli skrótów i jest bardzo szybki. Korzystanie z poziomu serwera if oznacza, że ​​if jest uruchamiane dla każdego żądania, co jest marnotrawstwem. Również przechwytywanie żądanego identyfikatora URI w przepisywaniu jest marnotrawstwem, ponieważ nginx ma już tę informację w zmiennych $ uri i $ request_uri (odpowiednio bez i z ciągiem zapytania).

server {
    server_name www.example.com example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    ssl_certificate /path/to/server.cert;
    ssl_certificate_key /path/to/server.key;
    server_name www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    ssl_certificate /path/to/server.cert;
    ssl_certificate_key /path/to/server.key;
    server_name example.com;

    <locations for processing requests>
}
kolbyjack
źródło
2
Czy konieczny jest środkowy blok? Czy pierwszy blok nie jest już przepisywany z www na inne niż www?
pbreitenbach
3
Pierwszy blok obsługuje tylko http. Środkowy blok jest niezbędny do przekierowania żądań https z https: // www.example.com/ na https: // example.com/. (Przepraszam za dodatkowe spacje, nie mogę zmusić go do pokazania https inaczej)
kolbyjack
1
tylko drobna uwaga dotycząca formatowania - jeśli chcesz uniknąć tworzenia linku, możesz umieścić tekst komentarza w cudzysłowach `, pod tyldą. Pojawiłby się następująco:https://example.com/
Cyclops
9
drugi blok wymaga również informacji o certyfikacie.
ricka
3
Próbując znaleźć odpowiedź, napotkałem inny problem. Myślałem, że mogę przekierować 301 z www.sub.example.comdo, sub.example.coma następnie uzyskać tylko certyfikat SSL. sub.example.comTeraz wiem, że sprawdzanie certyfikatu ssl odbywa się przed przekierowaniem 301, więc nie może działać. Więcej wyjaśnień tutaj: serverfault.com/a/358625/144811
Gruzzles
11

To działa dla mnie:

server {
    listen              80;
    server_name         www.yourdomain.com yourdomain.com;
    return              301 https://yourdomain.com$request_uri;
}

server {
    listen              443 ssl;
    server_name         www.yourdomain.com;
    ssl_certificate     /path/to/certificate.crt;
    ssl_certificate_key /path/to/private/key.pem;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    return              301 https://yourdomain.com$request_uri;
}

server {
    listen              443 ssl;
    server_name         yourdomain.com;
    ssl_certificate     /path/to/certificate.crt;
    ssl_certificate_key /path/to/private/key.pem;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;

    # do the proper handling of the request
}

Pamiętaj, że jedno yourdomain.com i drugie www.yourdomain.com musi znajdować się w certyfikacie SSL. Jest to możliwe w przypadku certyfikatu wieloznacznego lub alternatywnej nazwy serwera, jak wyjaśniono tutaj . Sprawdź https://www.startssl.com, aby znaleźć ładne i bezpłatne certyfikaty, które to robią. ( Edith : począwszy od wersji Chrome 56, certyfikaty Startsl nie będą już zaufane. Zamiast tego spróbuj https://letsencrypt.org/ ).

e18r
źródło
Ten faktycznie działa, ale myślałem, że można to zrobić w bardziej przejrzysty sposób bez wielu zduplikowanych linii konfiguracji.
zloynemec
@zloynemec Możesz umieścić pliki SSL w osobnym pliku .conf i użyć includereguły, aby dodać je do obu bloków serwera SSL.
Igettäjä,
Ponadto, jeśli korzystasz z cloudflare, musisz zapłacić 10 USD za miesiąc, aby móc przekierować i proxy 2 subdomen (www + coś). Daj mi znać, jeśli istnieje obejście tego problemu.
Freedo
7

Po spędzeniu tak dużo czasu z setkami podobnych przypadków wymyśliłem następujący fragment kodu. Jest krótki i można go łatwo dostosować, aby pasował na wszystko.

server {
    listen 80;
    listen 443 ssl;
    server_name example.com www.example.com;
    ssl_certificate /path/to/my/certs/example.com/fullchain.pem;
    ssl_certificate_key /path/to/my/certs/example.com/privkey.pem;

    # Redirect to the correct place, if needed
    set $https_redirect 0;
    if ($server_port = 80) { set $https_redirect 1; }
    if ($host ~ '^www\.') { set $https_redirect 1; }
    if ($https_redirect = 1) {
        return 301 https://example.com$request_uri;
    }

    location / {
    # ...
}

Och, ale ifjest zły !

Tak, może być. Ale istnieje z jakiegoś powodu i nie powinien wyrządzać szkody tym, którzy wiedzą, jak go właściwie używać. ;)

emyller
źródło
Podoba mi się to, ale czy masz jakieś dane na temat wydajności? Dziękuję Ci!
Freedo
1
Szczerze mówiąc, nigdy nie analizowałem tego, ale uważam, że nie będzie to miało większego wpływu w porównaniu do oddzielnych zasad, ponieważ efekt jest prawie taki sam.
emyller
test na przekierowanie? to naprawdę nie ma znaczenia nie? (prawdziwe pytanie, nie troll ^^)
Matrix
3

Wolę powrócić z kodem odpowiedzi, aby przeglądarka wiedziała, że ​​przekierowujesz go na inny adres URL.

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

    return 301 https://example.com$request_uri;
}

następnie inny blok konfiguracji serwera dla https

server {
        listen   443 ssl;
        server_name  example.com;
        ...
    }
montss
źródło
0

co powiesz na utworzenie bloku serwera w tym celu:

server{
    listen 80;
    server_name www.example.net example.net;
    rewrite ^(.*) https://example.net$1 permanent;
}

następnie ponownie uruchamiam nginx

anthonysomerset
źródło
Podczas ponownego uruchamiania pojawia się błąd „nazwa serwera powodującego konflikt”. Ponadto to polecenie nie będzie nasłuchiwało na porcie 443 dla protokołu SSL i muszę się również martwić przekierowaniem https://www.example.comdo https://example.com.
Devin
0

Myślę, że to powinno zadziałać.

W zwykłej definicji serwera HTTP sugerowano coś takiego jak anthonysomerset, to znaczy:

rewrite ^(.*) https://example.net$1 permanent;

Następnie w definicji serwera SSL:

if ($host ~ /^www\./) {
  rewrite ^(.*) https://example.net$1 permanent;
}

W ten sposób przekierowanie powinno nastąpić tylko raz na żądanie, bez względu na adres URL użytkownika, do którego pierwotnie się udaje.

Eduardo Ivanec
źródło
To działało, dzięki. Musiałem jednak zmienić twoje warunki warunkowe, if ($host = 'www.example.com') {ponieważ regex nie działał dla mnie. Nie mam pojęcia dlaczego, ponieważ wygląda poprawnie.
Devin
Zauważ, że jeśli jest złe i ogólnie lepiej jest użyć deklaratywnego sposobu.
Blaise,
0

Oto pełny przykład, który skończył się dla mnie. Problem polegał na tym, że nie miałem szczegółów ssl ( ssl_certificateitp.) W bloku przekierowań www. Pamiętaj, aby sprawdzić swoje dzienniki ( sudo tail -f /var/log/nginx/error.log)!

# HTTP — redirect all traffic to HTTPS
server {
    listen 80;
    listen [::]:80 default_server ipv6only=on;
    return 301 https://$host$request_uri;
}

# HTTPS — redirects www to non-www
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.example.com;

    # Use the Let's Encrypt certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Include the SSL configuration from cipherli.st
    include snippets/ssl-params.conf;
    return 301 https://example.com$request_uri;
}

# HTTPS — proxy all requests to the app (port 3001)
server {
    # Enable HTTP/2
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com sub.example.com;

    # Use the Let's Encrypt certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Include the SSL configuration from cipherli.st
    include snippets/ssl-params.conf;

    # For LetsEncrypt:
    location ~ /.well-known {
        root /var/www/html;
        allow all;
    }

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://localhost:3001;
        proxy_ssl_session_reuse off;
        proxy_set_header Host $http_host;
        proxy_cache_bypass $http_upgrade;
        proxy_redirect off;
    }
}

źródło