Назад | Перейти на главную страницу

Удалите www и перенаправьте на https с помощью nginx

Я хочу создать правило в nginx, которое выполняет две функции:

  1. Удаляет www. из URI запроса
  2. Перенаправляет на 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) без петли. Любые идеи?

Лучший способ добиться этого - использовать три серверных блока: один для перенаправления http на https, один для перенаправления www-name 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 за красивые и бесплатные сертификаты, которые это делают. (Эдит: начиная с версии Chrome 56, сертификаты startssl больше не будут доверять. Пытаться 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;
        ...
    }

как насчет создания для этого серверного блока:

server{
    listen 80;
    server_name www.example.net example.net;
    rewrite ^(.*) https://example.net$1 permanent;
}

затем перезапустите nginx

Я думаю, это должно сработать.

В вашем простом определении 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;
    }
}