Перенаправление с www на основной домен в Nginx

Пошаговая настройка постоянного перенаправления с www-поддомена на основной домен в Nginx с сохранением URI и параметров запроса.

Один сайт может быть доступен сразу по двум адресам:

https://example.com
https://www.example.com

Чтобы пользователи и поисковые системы всегда использовали один основной адрес, настраивают постоянное перенаправление с дополнительного имени на canonical domain.

В этой инструкции рассматривается только перенаправление:

www.example.com → example.com

с сохранением пути и параметров запроса.

Проверено на: Ubuntu Server 24.04 LTS
Уровень сложности: начальный
Время выполнения: около 10–15 минут
Требуемый доступ: пользователь с правами sudo

Что будет настроено

После выполнения инструкции:

  • будет выбран основной домен;
  • будет проверена DNS-запись www;
  • будет проверен TLS-сертификат;
  • будет создан отдельный redirect server block;
  • путь и query string будут сохраняться;
  • будет проверен код 301;
  • будут разобраны циклические перенаправления;
  • будет показан безопасный откат.

Исходная схема

Основной адрес:

https://example.com

Дополнительный адрес:

https://www.example.com

Ожидаемое поведение:

https://www.example.com/catalog/item?id=10

перенаправляется на:

https://example.com/catalog/item?id=10

Почему нужен один основной домен

Единый canonical domain помогает:

  • исключить дублирование адресов;
  • упростить cookies и сессии;
  • избежать разных абсолютных ссылок;
  • упростить аналитику;
  • централизовать HTTPS;
  • сделать поведение сайта предсказуемым.

Выбор основного имени

До настройки определите основной адрес.

В этой статье используется:

example.com

Вариант с основным www.example.com также возможен, но направление redirect будет обратным.

Не настраивайте оба направления одновременно.

Проверка DNS основного домена

dig +short example.com

Ожидается IP Nginx-сервера.

Проверьте:

getent hosts example.com

Проверка DNS для www

dig +short www.example.com

Допустимые варианты:

A-запись на SERVER_IP

или:

CNAME на example.com

Оба имени должны приводить клиента на сервер, который выполняет redirect.

Пример A-записей

example.com      A      SERVER_IP
www.example.com  A      SERVER_IP

Пример CNAME

www.example.com  CNAME  example.com

Для корневого домена CNAME обычно не используется в классическом DNS.

Проверка HTTP-доступа

curl -I http://example.com
curl -I http://www.example.com

Оба имени должны достигать Nginx.

Проверка HTTPS

curl -I https://example.com
curl -I https://www.example.com

Если сертификат не включает www.example.com, браузер получит TLS-ошибку ещё до обработки HTTP redirect.

Почему сертификат должен включать www

При HTTPS сначала выполняется TLS handshake, и только потом Nginx возвращает 301.

Поэтому сертификат должен быть действителен для:

www.example.com

даже если этот адрес сразу перенаправляется.

Проверка SAN сертификата

openssl s_client   -connect www.example.com:443   -servername www.example.com   </dev/null 2>/dev/null |
openssl x509   -noout   -ext subjectAltName

В списке должен быть:

DNS:www.example.com

Проверка Certbot

sudo certbot certificates

Убедитесь, что сертификат включает:

example.com
www.example.com

Добавление www в сертификат

Если сертификат выпущен только для основного домена:

sudo certbot   --nginx   --cert-name example.com   -d example.com   -d www.example.com   --expand

Перед выполнением DNS-запись www должна указывать на этот сервер.

Создание резервной копии

sudo cp   /etc/nginx/sites-available/example.com   /etc/nginx/sites-available/example.com.before-www-redirect

Проверьте:

ls -l   /etc/nginx/sites-available/example.com*

Правильная структура конфигурации

Для основного домена и redirect лучше использовать отдельные server block:

server block 1 — основной сайт
server block 2 — перенаправление www

Это проще и надёжнее, чем смешивать оба поведения в одном блоке.

Основной HTTPS server block

Пример:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name example.com;

    ssl_certificate         /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key         /etc/letsencrypt/live/example.com/privkey.pem;

    root /var/www/example.com/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

HTTPS redirect с www

Добавьте отдельный блок:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name www.example.com;

    ssl_certificate         /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key         /etc/letsencrypt/live/example.com/privkey.pem;

    return 301 https://example.com$request_uri;
}

Как работает request_uri

Переменная:

$request_uri

содержит исходный путь и query string.

Пример:

/products/item?id=10

Поэтому:

return 301 https://example.com$request_uri;

сохраняет:

  • путь;
  • имя файла;
  • параметры запроса;
  • URL-encoding.

HTTP redirect для обоих имён

Отдельный блок для порта 80:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    return 301 https://example.com$request_uri;
}

В результате:

http://example.com/path

и:

http://www.example.com/path

перенаправляются на:

https://example.com/path

Полная конфигурация

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name www.example.com;

    ssl_certificate         /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key         /etc/letsencrypt/live/example.com/privkey.pem;

    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name example.com;

    ssl_certificate         /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key         /etc/letsencrypt/live/example.com/privkey.pem;

    root /var/www/example.com/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Redirect для reverse proxy

Основной блок может проксировать приложение:

server {
    listen 443 ssl;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For             $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Redirect-блок остаётся таким же:

server {
    listen 443 ssl;
    server_name www.example.com;

    return 301 https://example.com$request_uri;
}

Почему лучше использовать return

Для простого перенаправления предпочтительнее:

return 301 ...

Вместо:

rewrite ...

return:

  • проще;
  • понятнее;
  • быстрее;
  • снижает риск ошибки регулярного выражения;
  • точно подходит для смены hostname.

Код 301

301 Moved Permanently

означает постоянное перенаправление.

Браузеры и поисковые системы могут кэшировать такой redirect.

До окончательной проверки можно временно использовать:

return 302 https://example.com$request_uri;

После успешного теста заменить 302 на 301.

Разница между 301 и 302

301

Постоянное перенаправление.

Используется для окончательно выбранного canonical domain.

302

Временное перенаправление.

Удобно для тестирования и временных изменений.

Проверка конфигурации

sudo nginx -t

Ожидаемый результат:

syntax is ok
test is successful

Применение

sudo systemctl reload nginx

Проверьте:

systemctl status nginx --no-pager

Проверка www по HTTPS

curl -I https://www.example.com

Ожидается:

HTTP/2 301
Location: https://example.com/

Проверка пути

curl -I   https://www.example.com/catalog/item

Ожидается:

Location: https://example.com/catalog/item

Проверка query string

curl -I   'https://www.example.com/search?q=nginx&page=2'

Ожидается:

Location: https://example.com/search?q=nginx&page=2

Проверка всей цепочки

curl -IL   'http://www.example.com/search?q=nginx'

Ожидаемая цепочка:

http://www.example.com
→ https://example.com
→ 200 OK

Желательно избегать лишней цепочки:

http://www
→ https://www
→ https://example.com

Лучше сразу перенаправлять HTTP на финальный canonical URL.

Проверка без DNS-кэша

curl -I   --resolve www.example.com:443:SERVER_IP   https://www.example.com/test

Для HTTPS имя в URL и SNI остаётся www.example.com, а соединение направляется на указанный IP.

Проверка основного домена

curl -I https://example.com

Основной домен не должен перенаправляться обратно на www.

Проверка активных server block

sudo nginx -T |
grep -nE   'server_name|return 301|listen 443'

Для конкретного домена:

sudo nginx -T |
grep -n -A15 -B5   'server_name www.example.com'

Проверка журнала доступа

Если redirect использует общий журнал:

sudo tail -50 /var/log/nginx/access.log

Можно добавить отдельный журнал:

access_log     /var/log/nginx/www.example.com.redirect.log;

Отдельный журнал redirect

Пример:

server {
    listen 443 ssl;
    server_name www.example.com;

    access_log         /var/log/nginx/www.example.com.redirect.log;

    return 301 https://example.com$request_uri;
}

Проверка:

sudo tail -f   /var/log/nginx/www.example.com.redirect.log

Циклическое перенаправление

Цикл возникает, если:

www → без www

и одновременно:

без www → www

Проверьте все конфигурации:

sudo grep -RniE   'return 30[128]|rewrite'   /etc/nginx/

Пример ошибочного цикла

В одном блоке:

return 301 https://example.com$request_uri;

В другом:

return 301 https://www.example.com$request_uri;

Оставьте только одно направление.

Redirect на неправильную схему

Ошибочный вариант:

return 301 http://example.com$request_uri;

Для production-сайта с TLS используйте:

return 301 https://example.com$request_uri;

Потеря query string

Плохой вариант:

return 301 https://example.com;

Он отправит все запросы только на главную страницу.

Правильный вариант:

return 301 https://example.com$request_uri;

Использование uri вместо request_uri

Переменная:

$uri

может быть нормализована и не содержит исходный query string.

Для полного сохранения запроса используйте:

$request_uri

Порт в redirect

Не добавляйте:

:443

без необходимости.

Достаточно:

https://example.com$request_uri

Порт 443 подразумевается автоматически.

Redirect для нестандартного HTTPS-порта

Если сайт действительно работает на нестандартном порту:

return 301   https://example.com:8443$request_uri;

Для обычного публичного сайта это не требуется.

Несколько дополнительных имён

Можно перенаправлять несколько alias:

server_name     www.example.com     old.example.com     legacy.example.com;

return 301 https://example.com$request_uri;

Все имена должны:

  • указывать на Nginx;
  • иметь действующий сертификат;
  • действительно принадлежать владельцу сайта.

Отдельные сертификаты

Redirect может использовать отдельный сертификат:

ssl_certificate     /etc/letsencrypt/live/www.example.com/fullchain.pem;

Но обычно удобнее один SAN-сертификат для:

example.com
www.example.com

HSTS

Если на основном домене настроен HSTS:

add_header Strict-Transport-Security   "max-age=31536000"   always;

не забывайте, что includeSubDomains распространяет HTTPS-политику и на www.

Поскольку www обслуживается по HTTPS, это нормальная схема.

Cookies

После перехода на основной домен приложение должно создавать cookies для правильного hostname.

Проверьте:

  • Domain;
  • Secure;
  • SameSite;
  • session callback URL;
  • allowed origins.

Nginx redirect не исправляет настройки cookies приложения автоматически.

OAuth и callback URL

Если приложение использует OAuth или SSO, обновите callback URL:

https://example.com/callback

Старый адрес с www может оставаться зарегистрированным у внешнего провайдера и вызывать ошибки.

CORS

Проверьте allowed origins приложения.

После выбора canonical domain разрешённым origin должен быть:

https://example.com

Redirect не заменяет корректную CORS-конфигурацию.

Sitemap и canonical

В HTML желательно использовать:

<link rel="canonical" href="https://example.com/page">

Также обновите:

  • sitemap;
  • robots.txt;
  • абсолютные ссылки;
  • Open Graph URL;
  • RSS;
  • API documentation.

Это уже уровень приложения, а не Nginx.

Ошибка сертификата на www

Проверьте SAN:

openssl s_client   -connect www.example.com:443   -servername www.example.com   </dev/null 2>/dev/null |
openssl x509   -noout   -ext subjectAltName

Если www отсутствует, перевыпустите сертификат.

Redirect не срабатывает

Проверьте:

  • DNS;
  • active server block;
  • server_name;
  • символическую ссылку в sites-enabled;
  • выполнен ли reload;
  • не перехватывает ли запрос другой default server.

Команда:

sudo nginx -T |
grep -n 'www.example.com'

Открывается другой сайт

Проверьте:

curl -vkI https://www.example.com

Проверьте SNI, сертификат и server_name.

Убедитесь, что www.example.com не указан в другом server block.

Возвращается 200 вместо 301

Вероятно, www.example.com включён в основной server block:

server_name example.com www.example.com;

Уберите www из блока, который обслуживает контент, и создайте отдельный redirect block.

Возвращается 404

Проверьте, что запрос попал в правильный server block.

Redirect block не должен содержать root или proxy_pass.

Слишком длинная цепочка redirect

Проверьте:

curl -IL http://www.example.com

Оптимально получить один redirect сразу на финальный URL.

Кэшированный 301 в браузере

Браузер может помнить старый redirect.

Для диагностики используйте:

curl -I

или приватное окно браузера.

Во время настройки используйте 302.

Проверка через разные протоколы

curl -I http://example.com
curl -I http://www.example.com
curl -I https://example.com
curl -I https://www.example.com

Ожидаемо:

HTTP example.com      → HTTPS example.com
HTTP www.example.com  → HTTPS example.com
HTTPS example.com     → 200
HTTPS www.example.com → HTTPS example.com

Откат конфигурации

Восстановите резервную копию:

sudo cp   /etc/nginx/sites-available/example.com.before-www-redirect   /etc/nginx/sites-available/example.com

Проверьте:

sudo nginx -t

Примените:

sudo systemctl reload nginx

Удаление redirect block

Удалите только блок:

server_name www.example.com;

с директивой:

return 301 ...

Не удаляйте основной server block.

Безопасный порядок настройки

  1. Выбрать canonical domain.
  2. Проверить DNS обоих имён.
  3. Проверить сертификат для обоих имён.
  4. Создать резервную копию.
  5. Временно настроить 302.
  6. Выполнить nginx -t.
  7. Выполнить reload.
  8. Проверить путь и query string.
  9. Проверить отсутствие цикла.
  10. Заменить 302 на 301.
  11. Повторно проверить.
  12. Обновить настройки приложения.

Быстрый пример

HTTP:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    return 301 https://example.com$request_uri;
}

HTTPS для www:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name www.example.com;

    ssl_certificate         /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key         /etc/letsencrypt/live/example.com/privkey.pem;

    return 301 https://example.com$request_uri;
}

Быстрая проверка

sudo nginx -t &&
sudo systemctl reload nginx
curl -IL   'http://www.example.com/test?id=10'

Итог

После выполнения инструкции:

  • выбран основной домен;
  • проверены DNS-записи;
  • проверен TLS-сертификат для www;
  • создан отдельный redirect server block;
  • сохранены URI и query string;
  • настроен постоянный код 301;
  • исключена лишняя цепочка перенаправлений;
  • разобраны циклы и типичные ошибки.

У сайта должен быть один основной публичный адрес, а дополнительные имена должны перенаправляться непосредственно на него.

← Предыдущая статья Настройка rate limiting в Nginx Следующая статья → Настройка ротации логов Nginx в Ubuntu