Удалите "www" и перенаправьте на "https" с помощью nginx
Я хочу создать правило в nginx, которое делает две вещи:
- Удаляет "www." из запроса URI
- Перенаправляет на "https", если URI запроса - "http"
Существует множество примеров того, как выполнять каждую из этих вещей в отдельности, но я не могу найти решение, которое выполняет обе эти функции правильно (то есть не создает цикл перенаправления и правильно обрабатывает все случаи).
Он должен обрабатывать все эти случаи:
1. http://www.example.com/path
2. https://www.example.com/path
3. http://example.com/path
4. https://example.com/path
Все они должны заканчиваться на https://example.com/path (# 4) без зацикливания. Есть идеи?
11 ответов
Лучший способ сделать это - использовать три серверных блока: один для перенаправления http на https, один для перенаправления http-имени https на no-www, а второй для фактической обработки запросов. Причина использования дополнительных серверных блоков вместо ifs заключается в том, что выбор сервера выполняется с использованием хеш-таблицы и выполняется очень быстро. Использование уровня сервера if означает, что if запускается для каждого запроса, что бесполезно. Кроме того, захват запрошенного URI в перезаписи является расточительным, так как nginx уже имеет эту информацию в переменных $uri и $request_uri (без и со строкой запроса, соответственно).
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>
}
Это работает для меня:
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
}
Имейте в виду, что оба yourdomain.com
а также www.yourdomain.com
должен быть в вашем сертификате SSL. Это возможно с подстановочным сертификатом или с альтернативным именем сервера, как описано здесь. Проверьте https://www.startssl.com/ для хороших и бесплатных сертификатов, которые делают это. (Edith: начиная с Chrome версии 56, сертификаты StartSL больше не будут доверять. Попробуйте вместо этого https://letsencrypt.org/.)
Проведя так много времени с сотнями подобных случаев, я пришел к следующему фрагменту. Он короткий и легко подстраивается под что угодно.
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 / {
# ...
}
Ох но
if
это зло!
Да, это может быть. Но оно существует по определенной причине и не должно причинять вреда тем, кто знает, как правильно его использовать.;)
Я предпочитаю вернуться с кодом ответа, чтобы браузер знал, что вы перенаправляете его на другой URL.
server {
listen 80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
затем другой блок настроек сервера для https
server {
listen 443 ssl;
server_name example.com;
...
}
Если у вас много доменов и вы ищете более общий подход без потери производительности и без постоянного перечисления всех доменов, проверьте это.
Как это работает?
- прослушайте 80 и перенаправьте все http на https - включая
http://www.
который пойдет вhttps://www.
- слушать 433, но только по именам серверов и перенаправлять на неиспользующее регулярное выражение
- слушайте 433 для каждого вашего несерверного имени — именно здесь будет находиться весь трафик
Это быстро?
Да! Несмотря на то, что мы используем RegExp, он есть только вwww
-versions, который возвращает 301. Таким образом, весь обычный трафик будет обрабатываться без каких-либо дополнительных затрат на обработку.
# Redirect everything to HTTPS (including "www")
server {
listen 80 default_server;
listen [::]:80 default_server;
return 301 https://$host$request_uri;
}
# Redirect away from "www" versions:
server {
listen [::]:443 ssl http2;
listen 443 ssl http2;
server_name www.example-1.com
www.example-2.com
www.example-3.com;
# using generic "www" removal // https://stackoverflow.com/questions/11323735/nginx-remove-www-and-respond-to-both/45676731#45676731
if ( $host ~ ^www\.(.+)$ ) {
set $without_www $1;
rewrite ^ $scheme://$without_www$uri permanent;
}
# SSL settings:
ssl_certificate /etc/letsencrypt/live/example/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example/privkey.pem; # managed by Certbot
}
# And finally one "server" block for each of your domains
server {
listen [::]:443 ssl http2;
listen 443 ssl http2;
server_name example-1.com;
# SSL settings, etc...
}
server {
server_name example-2.com;
# ... listen, ssl, etc...
}
server {
server_name example-3.com;
# ... etc
}
PS: если вам нужна помощь с настройкой SSL, воспользуйтесь генератором конфигурации SSL Mozilla:
https://ssl-config.mozilla.org/
Я думаю, что это должно работать.
В вашем простом определении HTTP-сервера предлагается что-то вроде anthonysomerset, а именно:
rewrite ^(.*) https://example.net$1 permanent;
Затем на вашем определении сервера SSL:
if ($host ~ /^www\./) {
rewrite ^(.*) https://example.net$1 permanent;
}
Таким образом, перенаправление должно происходить только один раз для каждого запроса, независимо от того, на какой URL-адрес изначально переходит пользователь
Вот полный пример, который помог мне. Проблема была в том, что у меня не было деталей ssl (ssl_certificate
и т. д.) в блоке перенаправления www. Не забудьте проверить свои логи (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;
}
}
Как насчет создания блока сервера для этой цели:
server{
listen 80;
server_name www.example.net example.net;
rewrite ^(.*) https://example.net$1 permanent;
}
затем перезапустить nginx
server {
listen 80;
listen 443 ssl;
server_name devly.co www.devly.co;
ssl on;
ssl_certificate /var/www/devly.co/cert/ssl-bundle.crt;
ssl_certificate_key /var/www/devly.co/cert/devly_co.key;
access_log /var/log/nginx/devly.co.access.log rt_cache;
error_log /var/log/nginx/devly.co.error.log;
root /var/www/devly.co/htdocs;
index index.php index.htm index.html;
# force https-redirects
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
}
Это работает для меня
server {
listen 80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 80;
listen 443 ssl;
server_name example.com;
#location
}
PS с другими решениями, которые я получил: ERR_TOO_MANY_REDIRECTS
Во-первых, навсегда перенаправьте все http [80] запросы на https [301] из первого блока сервера. Настройте SSL во втором блоке серверов с тем же доменом и поддоменом.
server {
listen 80;
listen [::]:80;
server_name domain.com www.domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
root /var/www/html/app;
server_name domain.com www.domain.com;
ssl_certificate /etc/ssl/chain.crt;
ssl_certificate_key /etc/ssl/private.key;
}