Вопрос или проблема
Я хочу переписать все http-запросы на моем веб-сервере на https-запросы, я начал с следующего:
server { listen 80; location / { rewrite ^(.*) https://mysite.com$1 permanent; } ...
Одна проблема заключается в том, что это удаляет всю информацию о поддомене (например, node1.mysite.com/folder), как можно переписать вышеуказанное, чтобы перенаправить все на https и сохранить поддомен?
Правильный способ в новых версиях nginx
Выяснилось, что мой первый ответ на этот вопрос был правильным в определенное время, но превратился в очередную ловушку – чтобы оставаться в курсе, пожалуйста, проверьте Taxing rewrite pitfalls
## Перенаправление всего HTTP-трафика на HTTPS
server {
listen [::]:80 default_server;
listen 80 default_server;
server_name _;
return 307 https://$host$request_uri;
}
server {
listen 443 ssl http2 quic;
server_name my.domain.com;
# добавьте Strict-Transport-Security для предотвращения атак типа "человек посередине"
add_header Strict-Transport-Security "max-age=31536000" always;
...
}
ПРИМЕЧАНИЕ: Лучший способ сделать это был предоставлен https://serverfault.com/a/401632/3641 – но здесь он повторяется:
server {
listen 80;
return 301 https://$host$request_uri;
}
В самом простом случае ваш хост будет привязан к тому сервису, на который вы хотите их отправить – это сделает 301 редирект в браузере, и URL браузера обновится соответственно.
Ниже приведен предыдущий ответ, который неэффективен из-за использования регулярных выражений, простой 301 отлично работает, как показано @kmindi
Я использую nginx 0.8.39 и выше, и использовал следующее:
server {
listen 80;
rewrite ^(.*) https://$host$1 permanent;
}
Отправляет постоянный редирект клиенту.
Я думаю, что лучший и единственный способ – использовать редирект HTTP 301 Moved Permanently следующим образом:
server {
listen [::]:80;
return 301 https://$host$request_uri;
}
Редирект HTTP 301 Moved Permanently также является самым эффективным, потому что нет необходимости оценивать регулярные выражения, согласно уже упомянутым ловушкам.
Новая HTTP 308 Moved Permanently сохраняет метод запроса и поддерживается основными браузерами. Например, использование 308
предотвращает изменение браузером метода запроса с POST
на GET
для запроса редиректа.
Если вы хотите сохранить имя хоста и поддомен, это способ.
Это все еще работает, если у вас нет DNS, так как я также использую его локально.
Я обращаюсь, например, с http://192.168.0.100/index.php
и получаю перенаправление точно на https://192.168.0.100/index.php
.
Я использую listen [::]:80
на моем хосте, потому что у меня bindv6only
установлен в false
, поэтому он также привязывается к ipv4 сокету. измените на listen 80
, если вы не хотите использовать IPv6 или хотите привязать к другому месту.
Решение от Saif Bechan использует server_name
, которое в моем случае является localhost, но это недоступно через сеть.
Решение от Michael Neale хорошее, но согласно ловушкам, есть лучшее решение с редиректом 301 😉
Внутри блока сервера вы можете также сделать следующее:
# Принудительное HTTPS-соединение. Это правило не зависит от домена
if ($scheme != "https") {
rewrite ^ https://$host$request_uri permanent;
}
Вышеупомянутое не работало у меня с новыми поддоменами, создаваемыми все время.
например, AAA.example.com BBB.example.com для около 30 поддоменов.
Наконец, я получил конфигурацию, работающую со следующим:
server {
listen 80;
server_name _;
rewrite ^ https://$host$request_uri? permanent;
}
server {
listen 443;
server_name example.com;
ssl on;
ssl_certificate /etc/ssl/certs/myssl.crt;
ssl_certificate_key /etc/ssl/private/myssl.key;
ssl_prefer_server_ciphers on;
# ...
# оставшаяся конфигурация здесь
# ...
}
Я давным-давно оставил комментарий к правильному ответу с очень важной поправкой, но я считаю необходимым выделить эту поправку в отдельном ответе. Никакие из предыдущих ответов нельзя безопасно использовать, если в какой-то момент у вас была незащищенная настройка HTTP и ожидались пользовательские данные, есть формы, хостится API или настроены любые веб-сайты, инструменты, приложения или утилиты для взаимодействия с вашим сайтом.
Проблема возникает, когда делается POST
запрос к вашему серверу. Если сервер отвечает простым 301
/302
редиректом содержимое POST будет потеряно. Происходит следующее: браузер/клиент обновит запрос до SSL, но понизит POST
до GET
запроса. Параметры POST
будут утеряны и будет осуществлен неверный запрос к вашему серверу.
Решение простое. Вы должны использовать HTTP 1.1 307
редирект. Это детализировано в RFC 7231 S6.4.7:
Примечание: Этот код состояния похож на 302 (Found), за исключением того,
что он не допускает изменения метода запроса с POST на GET.
Эта спецификация не определяет эквивалентного аналога для 301 (Moved
Permanently) ([RFC7238], однако, определяет код состояния 308
(Permanent Redirect) для этой цели).
Решение, адаптированное из принятого решения, заключается в использовании 307
в коде редиректа:
server {
listen 80;
server_name my.domain.com;
return 307 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name my.domain.com;
# добавьте Strict-Transport-Security для предотвращения атак типа "человек посередине"
add_header Strict-Transport-Security "max-age=31536000";
[....]
}
Я запускаю ngnix за AWS ELB. ELB общается с ngnix по http. Поскольку ELB не может отправить клиентам редиректы, я проверяю заголовок X-Forwarded-Proto и перенаправляю:
if ($http_x_forwarded_proto != 'https') {
return 301 "https://www.exampl.com";
}
Я смог это сделать так:
server {
listen 80;
listen 443 ssl;
server_name domain.tld www.domain.tld;
# глобальный обработчик HTTP
if ($scheme = http) {
return 301 https://www.domain.tld$request_uri;
}
# глобальный обработчик non-WWW HTTPS
if ($http_host = domain.tld){
return 303 https://www.domain.tld$request_uri;
}
}
Если вы return 301 https://$host$request_uri;
в качестве ответа по умолчанию на порту 80, то ваш сервер может рано или поздно попасть в список открытых прокси[1] и начнет использоваться для отправки трафика в другие места Интернета. Если ваши логи заполнятся сообщениями, подобными этому, значит, это случилось с вами:
42.232.104.114 - - [25/Mar/2018:04:50:49 +0000] "GET http://www.ioffer.com/i/new-fashion-fine-gold-bracelet-versaec-bracelet-641175733 HTTP/1.1" 301 185 "http://www.ioffer.com/" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Hotbar 4.1.8.0; RogueCleaner; Alexa Toolbar)"
Проблема в том, что $host
будет возвращать все, что браузер отправляет в заголовке Host
или даже имя хоста из начальной строки HTTP, такую как эта:
GET http://www.ioffer.com/i/new-fashion-fine-gold-bracelet-versaec-bracelet-641175733 HTTP/1.1
Из-за этой проблемы некоторые ответы здесь рекомендуют использовать $server_name
вместо $host
. $server_name
всегда оценивается в то, что вы указали в декларации server_name
. Но если у вас там несколько поддоменов или используется подстановочный знак, это не будет работать, потому что $server_name
использует только первую запись после декларации server_name
, и более важно, просто вернет подстановочный знак (не расширит его).
Так как же поддерживать несколько доменов, сохраняя безопасность? На моих собственных системах я решил эту дилемму, сначала указав блок default_server
, который не использует $host
, а затем указав блок с подстановочным знаком, который использует:
server {
listen 80 default_server;
server_name example.com;
return 301 https://example.com$request_uri;
}
server {
listen 80;
server_name *.example.com;
return 301 https://$host$request_uri;
}
(Вы также могли бы указать более одного домена во втором блоке.)
С этой комбинацией неподходящие домены будут перенаправлены на что-то жестко закодированное (всегда example.com
), а домены, которые соответствуют вашим собственным, перейдут в нужное место. Ваш сервер не будет полезен как открытый прокси, так что вы не будете привлекать неприятностей.
Если вы в настроении, я думаю, вы могли бы также сделать так, чтобы блок default_server
соответствовал никаким из ваших легитимных доменов и выдал что-то обидное. . . .
[1] Технически “прокси” – это неправильное слово, потому что ваш сервер не выходит и не выполняет запросы для клиентов, просто отправляет редирект, но я не уверен, какое слово было бы правильным. Я также не уверен, какова цель, но это заполняет ваши логи шумом и потребляет ваш процессор и пропускную способность, так что лучше с этим покончить.
Похоже, никто так и не сделал это на 100% правильно. Чтобы запросы на порт 80 поступали на их эквиваленты для 443 для всего веб-сервера, вам нужно использовать директиву listen, а не директиву server_name, чтобы указать имя по умолчанию. См. также https://nginx.org/en/docs/http/request_processing.html
server { listen 80 default; listen [::]:80 default; return 307 https://$host$request_uri; }
- $host захватывает имена поддоменов.
- 307 и 308 включают как запросы POST, так и GET.
- 307 временный, измените на постоянный 308 после тщательного тестирования:
И убедитесь, что вы проверили, что уже есть в /etc/nginx/conf.d/, потому что чаще всего у меня были проблемы, когда default.conf возвращал какой-то существующий vhost. Мой порядок работы с проблемами nginx всегда начинается с удаления файла по умолчанию, затем возвращения его назад комментированием строки за строкой, чтобы увидеть, где происходит сбой.
rewrite ^!https https://$host$request_uri permanent;
.
Ответ или решение
Чтобы перенаправить все HTTP-запросы на HTTPS на вашем веб-сервере Nginx и при этом сохранить информацию о поддоменах, вам нужно настроить конфигурацию Nginx соответствующим образом. Рассмотрим этот процесс детально.
Основная проблема и её решение
Основная задача заключается в переадресации всех запросов с порта 80 (HTTP) на порт 443 (HTTPS), удаляя при этом уязвимости, связанные с возможным изменением метода запроса, и обеспечивая сохранение поддоменов.
Конфигурация Nginx
Вот корректная конфигурация для перенаправления HTTP-запросов на HTTPS с минимальными накладными расходами и с сохранением поддоменов:
server {
listen 80 default_server;
listen [::]:80 default_server;
# Переадресация всех HTTP-запросов на HTTPS, используя поддомен и правый URI
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name ваш.домен.*; # Укажите ваш домен
# Настройки SSL
ssl_certificate /путь/к/cert.crt;
ssl_certificate_key /путь/к/privkey.key;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000" always;
# Остальная часть конфигурации...
}
Объяснение настройки
-
Слушатель для HTTP: Первый
server
блок прослушивает 80 порт и перенаправляет все запросы на соответствующий HTTPS URL-адрес, сохраняя поддомен и путь URI. Перенаправление с кодом 301 — это постоянное перенаправление, что является SEO-дружественным решением, и браузер обновляет адрес в адресной строке, запоминая новое местоположение. -
Использование
$host
и$request_uri
: Эти переменные обеспечивают сохранность поддоменов и исходного URI в процессе перенаправления. -
Настройки SSL: Второй
server
блок обслуживает HTTPS-запросы. Тут включены SSL-сертификаты и директриваStrict-Transport-Security
для защиты пользователей от атак «человек посередине». -
Оптимизация производительности и безопасности: Использование прямого перенаправления
return
вместоrewrite
снижает нагрузку на сервер, так как не применяется дополнительная обработка выражений.
Заключение
Рассмотренный выше метод является хорошей практикой для обеспечения безопасного доступа к веб-сайтам через HTTPS, что особенно важно в текущих условиях кибербезопасности. Правильная конфигурация вашего веб-сервера Nginx способствует поддержанию как безопасности, так и оптимальной производительности ресурсов.
Следуя изложенным рекомендациям, вы сможете обеспечить ваше сетевое приложение целостным решением для перенаправления трафика, которое будет поддерживать поддомены, улучшать SEO-оптимизацию и защищать данные пользователей.