Як змусити або перенаправити SSL в nginx?


222

У мене на субдомені є сторінка реєстрації на зразок: https://signup.example.com

Він повинен бути доступний лише через HTTPS, але я переживаю, що люди можуть якось натрапити на нього через HTTP та отримати 404.

Мій html / серверний блок в 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? (FYI Я знаю, що є плагіни 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;
nh2

13
один коментар. $ server_name $ підбирає першу змінну імені_сервера. Тож пам’ятайте про це, якщо у вашій конфігурації не є імена FQN
engineerDave

2
@ nh2 Це ще один випадок помилки документації, оскільки використання return 301...викликає помилку "занадто багато переадресацій", тоді як метод переписання насправді працює.
Майк Бетхані

1
Це зараз задокументовано як "також BAD". @MikeBethany return 301працює, якщо (я думаю) ви не викликаєте це також для правильних URL-адрес, прослуховуючи обидва порти (приклад конфігурації. Викликає проблему: візьміть першу відповідь serverfault.com/a/474345/29689 і не опустіть ).
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, уникнути необхідності перевірки змінної схеми $ у кожному запиті. Але якщо серйозно, то це така незначна річ, що не потрібно їх розділяти.

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 використовує її в локальному {} контексті, жодного з яких ми тут не робимо. Моя відповідь - абсолютно правильний спосіб робити речі!
DELETEDACC

2
Я не відмовився від цього, але хочу зазначити, що в останніх версіях за замовчуванням було змінено значення "default_server".
спудер

Перше рішення не може бути найефективнішим, якщо друге - ще ефективнішим. І ви навіть описали, чому ви не повинні використовувати знак "if if there": "це дозволяє уникнути необхідності перевірки змінної схеми $ при кожному запиті". Сенс не використовувати 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 ваше рішення спрацювало як шарм для мене. Спасибі
Jayesh Gopalan

1
If you are using the new dual HTTP and HTTPS server definitionтоді слід розділити його.
VBart

2
елегантний і працює ідеально!
джек-трейд

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

Це працює краще, я думаю. xxxx посилається на IP вашого сервера. Якщо ви працюєте з Plesk 12, ви можете зробити це, змінивши файл "nginx.conf" у каталозі "/var/www/vhosts/system/domain.tld/conf" для того, який домен ви хочете. Не забудьте перезапустити службу nginx після збереження конфігурації.


rewrite ^ https://$host$request_uri? permanent; було б кращим рішенням, оскільки у вас може бути кілька імен серверів на vhost

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

EDIT - квітень 2018: Рішення без ІФ можна знайти у моєму дописі тут: https://stackoverflow.com/a/36777526/6076984


1
Чи не вважаються умови ІФ злими та неефективними у світі nginx?
PKHunter

Так, вони взагалі. Але для цієї простої перевірки я б не здогадався. У мене є належний файл конфігурації, який передбачає більше написання коду, але уникає IF повністю.
stamster

Google радить використовувати 301 замість 303. Джерело: support.google.com/webmasters/answer/6073543?hl=uk
dylanh724

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