Supprimer «www» et rediriger vers «https» avec nginx

57

Je veux créer une règle dans nginx qui fait deux choses:

  1. Supprime le "www." à partir de l'URI de la demande
  2. Redirige vers "https" si l'URI de la requête est "http"

Il existe de nombreux exemples sur la manière de faire chacune de ces choses individuellement, mais je ne peux pas trouver de solution qui fasse les deux correctement (c’est-à-dire qu’il ne crée pas de boucle de redirection et gère correctement tous les cas).

Il doit gérer tous ces cas:

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

Celles-ci devraient toutes se terminer sur https://example.com/path (# 4) sans faire de boucle. Des idées?

Devin
la source
Je viens de rediriger www.mondomaine.com vers mydomain.com au niveau DNS et d’ajouter 301 pour non-https à https dans nginx. On dirait que ça devrait aller. ¯ \ _ (ツ) _ / ¯
jonathanbell

Réponses:

94

Pour ce faire, la meilleure solution consiste à utiliser trois blocs de serveur: un pour rediriger http vers https, un pour rediriger le https www-name vers no-www et un pour gérer les demandes. La raison d'utiliser des blocs de serveur supplémentaires au lieu de ifs est que la sélection du serveur est effectuée à l'aide d'une table de hachage et est très rapide. Utiliser un niveau de serveur if signifie que if est exécuté pour chaque demande, ce qui est une perte de temps. Aussi, capturer l'URI demandé dans la réécriture est une perte de temps, car nginx a déjà cette information dans les variables $ uri et $ request_uri (avec et avec la chaîne de requête, respectivement).

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
la source
2
Le bloc central est-il nécessaire? Le premier bloc ne réécrit-il pas déjà de www en non-www?
Pbreitenbach
3
Le premier bloc ne gère que http. Le bloc central est nécessaire pour rediriger les demandes https de https: // www.exemple.com/ vers https: // exemple.com/. (Désolé pour les espaces supplémentaires, je ne peux pas afficher le https autrement)
kolbyjack
1
note mineure de mise en forme - si vous souhaitez éviter de créer un lien, vous pouvez insérer un commentaire dans des guillemets `, placés sous tilde. Il se présenterait comme:https://example.com/
Cyclope
9
le second bloc a aussi besoin de cert info.
Ricka
3
En essayant cette réponse, j'ai rencontré un autre problème. Je pensais que je pouvais rediriger de www.sub.example.comvers sub.example.comet puis obtenir seulement un certificat SSL pour sub.example.comMaintenant, je sais que la vérification SSL cert se produit avant la redirection 301, donc cela ne peut pas fonctionner. Plus d'explications ici: serverfault.com/a/358625/144811
Gruzzles
11

Cela fonctionne pour moi:

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
}

Gardez à l'esprit que les deux yourdomain.com et www.yourdomain.com doivent figurer dans votre certificat SSL. Cela est possible avec un certificat générique ou avec un autre nom de serveur, comme expliqué ici . Consultez https://www.startssl.com pour des certificats gratuits et agréables qui le font. ( Edith : à partir de la version 56 de Chrome, les certificats startssl ne seront plus fiables. Essayez plutôt https://letsencrypt.org/ .)

e18r
la source
Celui-ci fonctionne réellement, mais je pensais que cela pourrait être fait de manière plus claire sans beaucoup de lignes de configuration en double.
Zloynemec
@zloynemec Vous pouvez placer le contenu SSL dans un fichier .conf séparé et utiliser la includerègle pour l'ajouter aux deux blocs de serveur SSL.
Igettäjä
De plus, si vous utilisez cloudflare, vous devez payer le certificat de 10 $ / mois pour pouvoir rediriger et proxy les 2 sous-domaines (www + quelque chose). Faites-moi savoir s'il existe une solution de contournement.
Freedo
7

Après avoir passé tant de temps avec des centaines de cas similaires, je viens avec l'extrait suivant. C'est court et peut être facilement modifié pour s'adapter à n'importe quoi.

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 / {
    # ...
}

Oh mais ifc'est diabolique !

Oui ça peut être. Mais il existe pour une raison et ne devrait pas nuire à ceux qui savent l’utiliser correctement. ;)

Emyller
la source
J'aime ça, mais avez-vous des données sur la performance? Je vous remercie!
Freedo
1
Honnêtement, je n'ai jamais comparé cela, mais je pense qu'il n'y aurait guère d'impact par rapport à des règles distinctes, car l'effet est pratiquement identique.
Emyller
point de repère sur la redirection? ce n'est pas vraiment pertinent non? (vraie question, pas un troll ^^)
Matrix
3

Je préfère renvoyer un code de réponse pour que le navigateur sache que vous le redirigez vers une autre URL.

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

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

puis un autre bloc de configurations de serveur pour la https

server {
        listen   443 ssl;
        server_name  example.com;
        ...
    }
montss
la source
0

pourquoi ne pas créer un bloc serveur à cette fin:

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

puis redémarrer nginx

anthonysomerset
la source
J'obtiens une erreur "Nom du serveur en conflit" lors du redémarrage. En outre, cette commande n'écoute pas le port 443 pour SSL et je dois également m'inquiéter de la redirection https://www.example.comvers https://example.com.
Devin
0

Je pense que cela devrait fonctionner.

Dans votre définition de serveur HTTP standard, quelque chose comme anthonysomerset est suggéré, à savoir:

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

Puis sur votre définition de serveur SSL:

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

De cette manière, la redirection ne devrait avoir lieu qu'une fois par requête, quelle que soit l'URL d'origine de l'utilisateur.

Eduardo Ivanec
la source
Cela a fonctionné, merci. J'ai dû changer votre condition en if ($host = 'www.example.com') {puisque votre regex ne fonctionnait pas pour moi, cependant. Aucune idée pourquoi, car cela semble correct.
Devin
Notez que si est mauvais et qu'il est généralement préférable d'utiliser une méthode déclarative.
Blaise
0

Voici l'exemple complet qui a fini par travailler pour moi. Le problème était que je n'avais pas les détails ssl ( ssl_certificate, etc.) dans le bloc de redirection www. N'oubliez pas de vérifier vos journaux ( 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;
    }
}

la source