Как заставить или перенаправить на SSL в nginx?


222

У меня есть страница регистрации на поддомене, например: https://signup.example.com

Он должен быть доступен только через HTTPS, но я боюсь, что люди могут как-то наткнуться на него через HTTP и получить 404.

Мой блок html / server в nginx выглядит так:

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;
    }
  }
}

Что я могу добавить, чтобы люди, которые идут, были http://signup.example.comперенаправлены на https://signup.example.com? (К вашему сведению, есть плагины Rails, которые могут форсировать, SSLно надеялись избежать этого)


Ответы:


145

В соответствии с подводными камнями nginx , немного лучше пропустить ненужный захват, используя $request_uriвместо этого. В этом случае добавьте знак вопроса, чтобы nginx не удваивал аргументы запроса.

server {
    listen      80;
    server_name signup.mysite.com;
    rewrite     ^   https://$server_name$request_uri? permanent;
}

68
Или, согласно сайту, на который вы return 301 http://domain.com$request_uri;
ссылаетесь

13
один комментарий. $ server_name $ выбирает первую переменную server_name. Так что имейте это в виду, если в вашей конфигурации не имена FQN
engineerDave

2
@ nh2 Это еще один случай неправильной документации, поскольку использование return 301...приводит к ошибке «слишком много перенаправлений», в то время как метод перезаписи действительно работает.
Майк Бетани

1
Это теперь задокументировано как «также ПЛОХО». @MikeBethany return 301работает, если (я полагаю) вы не запускаете его и для правильных URL-адресов, прослушивая оба порта (пример конфигурации. Вызывая проблему: возьмите первый ответ serverfault.com/a/474345/29689 и пропустите if ).
Blaisorblade

1
Интересно, что изменилось за эти годы и лучше ли этот другой ответ: serverfault.com/a/337893/119666
Райан,

256

Лучший способ, как описано в официальном руководстве, - использовать returnдирективу:

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

5
кратчайший ответ и отлично работал в моем случае
mateusz.fiolka

1
Как правило, это рекомендуется, потому что он возвращает 301 Moved Permanently(ваши ссылки постоянно перемещены), а также переписывает
sgb

1
Это не работает, так как вызывает ошибку «слишком много перенаправлений», даже если вы установилиproxy_set_header X-Forwarded-Proto https;
Майк Бетани

1
@MikeBethany вы определяете listen 443;в одном блоке?
Джо Б.

2
Это должен быть принятый ответ.
Sjas

119

Это правильный и наиболее эффективный способ, если вы хотите хранить все это в одном блоке сервера:

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;
    }
}

Все остальное выше, с использованием «переписать» или «если ssl_protocol» и т. Д. Медленнее и хуже.

Здесь то же самое, но даже более эффективно, только выполняя перезапись по протоколу http, он избегает необходимости проверять переменную $ circuit при каждом запросе. А если серьезно, это настолько незначительная вещь, что вам не нужно их разделять.

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;
}

8
Отлично, какой-то трус отверг этот ответ, не сказав, почему, хотя этот ответ правильный. Может быть, еще один из тех "если это зло" культисты. Если вы потрудитесь прочитать документацию Nginx о If, вы будете знать, что IfIsNOTEvil, просто CERTAIN использует его в контексте location {}, чего мы здесь не делаем. Мой ответ - абсолютно правильный способ ведения дел!
DELETEDACC

2
Я не голосовал против этого, но хотел бы отметить, что в самых последних версиях default было изменено на default_server.
spuder

Первое решение не может быть наиболее эффективным, если второе еще более эффективно. И вы даже описали, почему вы не должны использовать if там: «он избегает необходимости проверять переменную $ circuit при каждом запросе». Смысл не использования ifs заключается не только в производительности, но и в декларативности, а не в необходимости.
pepkin88

+1 для if ($ схема = http)
Фернандо Кош

Здесь следует использовать $ host, как указано в других ответах.
Артем Руссаковский

56

Если вы используете новое определение двух серверов HTTP и HTTPS, вы можете использовать следующее:

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;
    }
}

Похоже, это работает для меня и не вызывает петли перенаправления.

Редактировать:

Заменены:

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

с линией переписывания Пратика.


2
@DavidPashley ваше решение сработало как очарование для меня. Спасибо
Джаеш Гопалан

1
If you are using the new dual HTTP and HTTPS server definitionтогда вы должны отделить это.
VBart

2
элегантно и отлично работает!
Jacktrade

2
Это было единственное решение, которое работало для меня с моей конфигурацией Laravel / Homestead Nginx.
Джаред Эйтньер

1
Также должна быть строка перезаписи, так return 301 https://$server_name$request_uri;как это предпочтительный метод.
Джаред Эйтньер,

27

Еще один вариант, который сохраняет заголовок запроса Host: и следует примеру «ХОРОШО» в ловушках 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;
}

Вот результаты. Обратите внимание, что использование $server_nameвместо $hostбудет всегда перенаправлять на 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

Note that using $server_name instead of $host would always redirect to https://site1разве не $request_uriдля этого?
Юрген Павел

2
$request_uriне содержит имя хоста или домена. Другими словами, он всегда начинается с символа «/».
Питер,

2
Лучший ответ на сегодняшний день.
Ашеш

3
Я не уверен, почему этот ответ так низко в голосовании. Это единственный, который стоит использовать.
zopieux

2
Не могу поверить, что так много людей используют $ server_name, это правильный способ сделать это
Грег Эннис

3

Убедитесь, что вы установили «безопасный» для любых файлов cookie, иначе они будут отправлены по HTTP-запросу и могут быть получены с помощью такого инструмента, как Firesheep.


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;
}

Это работает лучше, я думаю. хххх относится к IP вашего сервера. Если вы работаете с Plesk 12, вы можете сделать это, изменив файл «nginx.conf» в каталоге «/var/www/vhosts/system/domain.tld/conf» для любого домена, который вы хотите. Не забудьте перезапустить службу nginx после сохранения конфигурации.


rewrite ^ https://$host$request_uri? permanent; было бы лучшим решением, так как вы могли бы иметь несколько имен серверов на

0

Я думаю, что это самое простое решение. Форсирует как HTTPS, так и WWW трафик как без HTTPS, так и без WWW.

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;
    }
}

РЕДАКТИРОВАТЬ - Апр 2018: Решение без IF можно найти в моем сообщении здесь: https://stackoverflow.com/a/36777526/6076984


1
Разве в мире nginx условия не считаются злыми и неэффективными?
PKHunter

Да, в общем. Но для этой простой проверки я бы не догадался. У меня есть правильный файл конфигурации, который требует больше написания кода, но полностью избегает IF.
Stamster

Google рекомендует использовать 301 вместо 303. Источник: support.google.com/webmasters/answer/6073543?hl=ru
dylanh724

@DylanHunt - я оставил 303 только для тестирования, обратите внимание, что первый обработчик был установлен на 301, только второй я забыл изменить :) Кроме того, решение без IF: stackoverflow.com/a/36777526/6076984
stamster
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.