Comment forcer ou rediriger vers SSL dans nginx?

222

J'ai une page d'inscription sur un sous-domaine tel que: https://signup.example.com

Il ne devrait être accessible que via HTTPS, mais je crains que les gens ne tombent dessus via HTTP et obtiennent un 404.

Mon bloc html / server dans nginx ressemble à ceci:

html {
  server {
    listen 443;
    server_name signup.example.com;

    ssl                        on;
    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    ssl_session_timeout 30m;

    location / {
      root /path/to/my/rails/app/public;
      index index.html;
        passenger_enabled on;
    }
  }
}

Que puis-je ajouter pour que les personnes qui vont se http://signup.example.comfaire rediriger https://signup.example.com? (Pour info je sais qu'il y a des plugins Rails qui peuvent forcer SSLmais espéraient l'éviter)

Callmeed
la source

Réponses:

145

Selon les pièges de nginx , il est légèrement préférable d’omettre la capture inutile, de l’utiliser $request_uriplutôt. Dans ce cas, ajoutez un point d'interrogation pour empêcher nginx de doubler les arguments de la requête.

server {
    listen      80;
    server_name signup.mysite.com;
    rewrite     ^   https://$server_name$request_uri? permanent;
}
Pratik Amin
la source
68
Ou, selon le site que vous avez lié, "MEILLEUR" :return 301 http://domain.com$request_uri;
nh2
13
un commentaire. $ nom_serveur $ récupère la première variable nom_serveur. Soyez donc conscient de cela si vous avez des noms non FQN dans votre configuration
engineerDave
2
@ nh2 C'est un autre cas où la documentation est fausse car utiliser return 301...provoque l'erreur "Trop de redirections" alors que la méthode de réécriture fonctionne réellement.
Mike Bethany
1
C'est maintenant documenté comme "aussi MAUVAIS". @MikeBethany return 301fonctionne, à moins que (je suppose) que vous le déclenchiez également pour les URL correctes, en écoutant sur les deux ports (exemple de configuration déclenchant le problème: prenez la première réponse de serverfault.com/a/474345/29689 et omettez le if. ).
Blaisorblade
1
Je me demande ce qui a changé au fil des ans et si cette autre réponse est meilleure: serverfault.com/a/337893/119666
Ryan
256

Le meilleur moyen décrit dans le guide officiel consiste à utiliser la returndirective:

server {
    listen      80;
    server_name signup.mysite.com;
    return 301 https://$server_name$request_uri;
}
VBart
la source
5
réponse la plus courte et a parfaitement fonctionné dans mon cas
mateusz.fiolka
1
Ceci est généralement recommandé car il retourne un 301 Moved Permanently(vos liens ont été définitivement déplacés) ainsi qu'une ré-écriture
sgb
1
Cela ne fonctionne pas car cela provoque une erreur "trop ​​de redirections" même si vous avez définiproxy_set_header X-Forwarded-Proto https;
Mike Bethany
1
@ MikeBethany définissez-vous listen 443;dans le même bloc?
Joe B
2
Cela devrait être la réponse acceptée.
Sjas
119

C'est le moyen le plus efficace et le plus efficace si vous souhaitez tout conserver dans un bloc de serveur:

server {
    listen   80;
    listen   [::]:80;
    listen   443 default_server ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    if ($scheme = http) {
        return 301 https://$server_name$request_uri;
    }
}

Tout le reste ci-dessus, en utilisant "rewrite" ou "if ssl_protocol" etc. est plus lent et pire.

Voici la même chose, mais encore plus efficace, en ne lançant que la réécriture sur le protocole http, cela évite de vérifier la variable $ scheme à chaque requête. Mais sérieusement, c'est une chose tellement mineure qu'il n'est pas nécessaire de les séparer.

server {
    listen   80;
    listen   [::]:80;

    server_name www.example.com;

    return 301 https://$server_name$request_uri;
}
server {
    listen   443 default_server ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;
}
DELETEDACC
la source
8
Génial, un lâche a rejeté cette réponse sans dire pourquoi, même si cette réponse est correcte. Peut-être un autre de ces cultistes "si est mauvais". Si vous prenez la peine de lire la documentation de Nginx sur If, vous saurez que IfIsNOTEvil, CERTAINES utilisations l’utilise dans un contexte location {}, ce que nous ne faisons pas ici. Ma réponse est absolument la bonne façon de faire les choses!
DELETEDACC
2
Je n'ai pas voté contre cela, mais je tiens à souligner que le paramètre par défaut a été remplacé par 'default_server' dans les versions les plus récentes.
spuder
La première solution ne peut pas être la plus efficace si la seconde est encore plus efficace. Et vous avez même expliqué pourquoi vous ne devriez pas utiliser un si: "cela évite de devoir vérifier la variable $ scheme à chaque requête". Le fait de ne pas utiliser les ifs ne concerne pas seulement les performances, mais aussi le fait d'être déclaratif et non impératif.
pepkin88
+1 pour si ($ scheme = http)
Fernando Kosh
Devrait utiliser $ host ici, comme mentionné dans les autres réponses.
Artem Russakovskii
56

Si vous utilisez la nouvelle définition de serveur double HTTP et HTTPS, vous pouvez utiliser les éléments suivants:

server {
    listen   80;
    listen   [::]:80;
    listen   443 default ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    if ($ssl_protocol = "") {
       rewrite ^   https://$server_name$request_uri? permanent;
    }
}

Cela semble fonctionner pour moi et ne provoque pas de boucles de redirection.

Modifier:

Remplacé:

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

avec la ligne de réécriture de Pratik.

David Pashley
la source
2
@ DavidPashley votre solution a fonctionné comme un charme pour moi. Merci
Jayesh Gopalan
1
If you are using the new dual HTTP and HTTPS server definitionalors vous devriez le séparer.
VBart
2
élégant et fonctionne parfaitement!
jacktrade
2
C'était la seule solution qui fonctionnait pour moi avec ma configuration Laravel / Homestead Nginx.
Jared Eitnier
1
La ligne de réécriture doit également être return 301 https://$server_name$request_uri;la méthode recommandée.
Jared Eitnier
27

Encore une autre variante, qui préserve l’en-tête Host: request et suit l’exemple "GOOD" sur les pièges nginx :

server {
    listen   10.0.0.134:80 default_server;

    server_name  site1;
    server_name  site2;
    server_name  10.0.0.134;

    return 301 https://$host$request_uri;
}

Voici les résultats. Notez que l'utilisation $server_nameau lieu de $hostserait toujours rediriger vers https://site1.

# curl -Is http://site1/ | grep Location
Location: https://site1/

# curl -Is http://site2/ | grep Location
Location: https://site2/


# curl -Is http://site1/foo/bar | grep Location
Location: https://site1/foo/bar

# curl -Is http://site1/foo/bar?baz=qux | grep Location
Location: https://site1/foo/bar?baz=qux
Peter
la source
Note that using $server_name instead of $host would always redirect to https://site1c'est pas pour ça $request_uri?
Jürgen Paul
2
$request_urine contient pas d'hôte ou de nom de domaine. En d'autres termes, il commence toujours par un caractère "/".
Peter
2
Meilleure réponse de loin.
Ashesh
3
Je ne suis pas sûr de savoir pourquoi cette réponse est si faible dans les votes. C'est le seul qui vaille la peine d'être utilisé.
zopieux
2
Je ne peux pas croire que tant de gens utilisent $ nom_serveur c'est la bonne façon de le faire
Greg Ennis
3

Assurez-vous que les cookies sont sécurisés, sinon ils seront envoyés sur la requête HTTP et pourraient être saisis par un outil comme Firesheep.

W. Andrew Loe III
la source
1
server {
    listen x.x.x.x:80;

    server_name domain.tld;
    server_name www.domian.tld;
    server_name ipv4.domain.tld;

    rewrite     ^   https://$server_name$request_uri? permanent;
}

Cela fonctionne mieux je pense. xxxx fait référence à l'IP de votre serveur. Si vous travaillez avec Plesk 12, vous pouvez le faire en modifiant le fichier "nginx.conf" dans le répertoire "/var/www/vhosts/system/domain.tld/conf" pour le domaine de votre choix. N'oubliez pas de redémarrer le service nginx après avoir enregistré la configuration.

Caner SAYGIN
la source
rewrite ^ https://$host$request_uri? permanent; serait une meilleure solution car vous pourriez avoir plusieurs noms de serveur sur un vhost
0

Je pense que c'est la solution la plus simple. Force le trafic non HTTPS et non WWW vers HTTPS et www uniquement.

server {
    listen 80;
    listen 443 ssl;

    server_name domain.tld www.domain.tld;

    # global HTTP handler
    if ($scheme = http) {
        return 301 https://www.domain.tld$request_uri;
    }

    # global non-WWW HTTPS handler
    if ($http_host = domain.tld) {
        return 303 https://www.domain.tld$request_uri;
    }
}

EDIT - avril 2018: La solution sans IF est disponible dans mon message ici: https://stackoverflow.com/a/36777526/6076984

stamster
la source
1
Les conditions de la FI ne sont-elles pas considérées comme mauvaises et inefficaces dans le monde nginx?
PKHunter
Oui ils le sont, en général. Mais pour cette simple vérification, je suppose que non. J'ai un bon fichier de configuration qui implique plus d'écriture de code, mais évite complètement les IF.
Stamster
Google conseille d'utiliser 301 au lieu de 303. Source: support.google.com/webmasters/answer/6073543?hl=fr
dylanh724 le
@DylanHunt - Je laisse 303 uniquement pour les tests, prendre note que le 1er gestionnaire a été fixé à 301, seulement 2 j'ai oublié de changer :) En outre, la solution w / o de IF: stackoverflow.com/a/36777526/6076984
stamster