В этой статье мы разберем, как настроить уже работающий сайт и его API для приема подключений по IPv6 без потери доступности по IPv4. Также мы рассмотрим, как проверить доступность IPv6 на сервере, какие изменения потребуются в конфигурации Nginx, Apache и их связки и как добавить AAAA-запись в DNS.
Что такое IPv6 и dual stack
IPv6 – версия протокола, разработанная на смену IPv4. Основное отличие – длина адреса: 128 бит вместо 32, что дает практически неограниченное адресное пространство. Адрес записывается шестнадцатеричными группами, разделенными двоеточиями, например: 2001:db8:1a2b:3c4d::1.
Dual stack – режим, при котором сервер одновременно принимает подключения по обоим протоколам. У домена настроены две записи: A с адресом IPv4 и AAAA с адресом IPv6, веб-сервер слушает оба протокола, а клиент при подключении выбирает подходящий самостоятельно.
Протоколы работают параллельно и независимо друг от друга, поэтому доступность сайта по IPv4 не меняется. Переносить данные и править код сайта не требуется – достаточно изменений в конфигурации веб-сервера и одной записи в DNS.
Проверка доступности IPv6 на сервере
Подключите IPv6 в панели управления, в настройках нужного сервера в разделе «Облако». После этого подключитесь к серверу по SSH и посмотрите адреса на сетевом интерфейсе:
ip -6 addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2001:db8:1a2b:3c4d::1/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::257:24ff:fe26:65f7/64 scope link
valid_lft forever preferred_lft foreverТребуется адрес с пометкой scope global – это публичный адрес сервера, именно он потребуется для AAAA-записи. Адреса с пометкой scope link, начинающиеся с fe80::, являются служебными и в интернете не маршрутизируются.
Префикс /64 означает, что серверу выделена целая подсеть, а не один адрес: ее хватит на все сайты и сервисы. Для публикации сайта по IPv6 достаточно адреса, который уже назначен на интерфейс.
Если сетевой интерфейс называется иначе, посмотрите список интерфейсов командой ip -6 addr show без указания устройства.
Проверьте связность по IPv6 – для этого подойдет любой публичный адрес, например, DNS-сервер Google:
ping -6 -c3 2001:4860:4860::8888
PING 2001:4860:4860::8888 (2001:4860:4860::8888) 56 data bytes
64 bytes from 2001:4860:4860::8888: icmp_seq=1 ttl=118 time=13.8 ms
64 bytes from 2001:4860:4860::8888: icmp_seq=2 ttl=118 time=13.4 ms
64 bytes from 2001:4860:4860::8888: icmp_seq=3 ttl=118 time=13.2 ms
--- 2001:4860:4860::8888 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 13.228/13.472/13.802/0.241 msЕсли адрес назначен, но ping не проходит, проверьте наличие маршрута по умолчанию:
ip -6 route show
2001:db8:1a2b:3c4d::/64 dev eth0 proto kernel metric 256 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
default via fe80::1 dev eth0 metric 1024 onlink pref mediumПри отсутствии строки, начинающейся с default via, обратитесь в поддержку: адрес выделен, но маршрутизация не настроена.
Приоритет IPv6 и IPv4 для исходящих соединений
После подключения IPv6 сервер начинает использовать его не только для приема входящих подключений, но и для собственных запросов – обращений приложений к сторонним API, загрузки обновлений, отправки почты.
Порядок определяет системная библиотека: адреса назначения сортируются по правилам RFC 6724. В стандартной конфигурации glibc встроенные правила дают IPv6 более высокий приоритет, чем IPv4. Посмотреть фактический порядок можно командой:
getent ahosts example.ruНапример:
2001:db8:1a2b:3c4d::1 STREAM example.ru
2001:db8:1a2b:3c4d::1 DGRAM
2001:db8:1a2b:3c4d::1 RAW
81.200.119.100 STREAM
81.200.119.100 DGRAM
81.200.119.100 RAWАдрес, стоящий первым, будет первым в списке адресов, переданном приложению. Гарантии, что приложение установит соединение именно с ним, это не дает: приложение может выбирать адрес самостоятельно или явно указывать протокол. Но если исходящий IPv6 работает нестабильно, часть запросов может завершаться ошибкой, притом что сайт по-прежнему открывается по IPv4.
Приоритет настраивается в файле /etc/gai.conf. По умолчанию он состоит из комментариев, поэтому используются встроенные значения. Если в файл добавить собственные правила precedence, они заменят встроенную таблицу приоритетов целиком. Поэтому при изменении таблицы остальные нужные значения необходимо указать в файле явно.
Отдельная таблица меток, которую показывает команда ip addrlabel list, относится к выбору адреса-источника и не определяет порядок адресов назначения.
Настройка касается только исходящих соединений сервера. По какому протоколу к сайту подключаются посетители, она не определяет.
Дальнейшие действия зависят от того, какой веб-сервер обслуживает сайт. Перейдите к соответствующему разделу: «Настройка Nginx», «Настройка Apache» или «Настройка связки Nginx и Apache».
Настройка Nginx
Откройте конфигурационный файл сайта, он находится в каталоге /etc/nginx/sites-available/:
sudo nano /etc/nginx/sites-available/example.confКонфигурация сайта, работающего только по IPv4, имеет примерно следующий вид:
server {
listen 80;
server_name example.ru;
root /var/www/example/public;
index index.html;
location /api/ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME /var/www/example/api/index.php;
include fastcgi_params;
}
}Добавьте в блок server строку listen [::]:80;:
server {
listen 80;
listen [::]:80;
server_name example.ru;
root /var/www/example/public;
index index.html;
location /api/ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME /var/www/example/api/index.php;
include fastcgi_params;
}
}Назначение директив:
listen 80– прием подключений по IPv4 на 80 порту, эта строка остается без изменений;listen [::]:80– прием подключений по IPv6 на том же порту;[::]– обозначение всех адресов IPv6 сервера, аналог 0.0.0.0 при подключении через IPv4. Квадратные скобки отделяют адрес от номера порта: в адресе IPv6 двоеточия уже используются как разделители групп.
Больше ничего менять не требуется: блоки location, обработка API и остальная логика остаются без изменений – они не зависят от протокола, по которому пришел запрос.
Если на сайте установлен SSL-сертификат, аналогичную строку нужно добавить и в блок, отвечающий за 443 порт:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.ru;
...
}Пропуск этого шага – частая причина ситуации, когда по IPv6 сайт открывается по HTTP, но не открывается по HTTPS.
Проверьте конфигурацию и перезапустите Nginx:
sudo nginx -t
sudo systemctl restart nginxlisten используйте restart, а не reload. Перезагрузка конфигурации не всегда пересоздает сетевые сокеты: рабочие процессы могут продолжать использовать прежние. Перезапуск занимает доли секунды, но в этот момент сайт недоступен по обоим протоколам, поэтому выполняйте его только после успешной проверки конфигурации командой nginx -t.Проверьте, что сервер слушает оба протокола:
sudo ss -tlnp | grep :80
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=44444,fd=5),("nginx",pid=44443,fd=5))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=44444,fd=6),("nginx",pid=44443,fd=6))Две строки – два сокета: 0.0.0.0:80 для IPv4 и [::]:80 для IPv6.
Проверьте работу сайта и API по обоим протоколам, указав путь к своему API вместо /api/:
curl -4 http://127.0.0.1/
curl -6 http://[::1]/
curl -4 http://127.0.0.1/api/
curl -6 http://[::1]/api/Ответы, полученные по IPv4 и IPv6, должны совпадать.
Адрес [::1] – это локальная петля IPv6, аналог 127.0.0.1. Такая проверка подтверждает, что сокет создан и приложение отвечает по обоим протоколам, но не проверяет доступность сервера извне. Доступность по публичному адресу проверяется в разделе «Настройка DNS: AAAA-запись».
Ниже приведен пример тестового сайта – гостевой книги, обслуживаемой Nginx. Приложение выводит IP-адрес, с которого осуществлялось подключение, а также протокол текущего подключения и используемый веб-сервер, а каждая запись помечается протоколом, по которому она была добавлена:

Записи, добавленные по IPv4 и IPv6, попадают в один общий список – это один и тот же сайт, доступный по двум протоколам.
Настройка Apache
В Ubuntu и Debian пакет Apache собран с поддержкой IPv4-mapped адресов: директива Listen 80 без указания конкретного адреса создает сокет, принимающий подключения по обоим протоколам. Проверьте, собран ли Apache таким образом:
sudo apache2ctl -V | grep APR
Server loaded: APR 1.7.2, APR-UTIL 1.6.3, PCRE 10.42 2022-12-11
Compiled using: APR 1.7.2, APR-UTIL 1.6.3, PCRE 10.42 2022-12-11
-D APR_HAS_SENDFILE
-D APR_HAS_MMAP
-D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)Строка IPv4-mapped addresses enabled означает, что поддержка IPv6 и IPv4 доступна. Она применяется только в том случае, если в конфигурации не указан конкретный адрес для прослушивания. Поэтому следующим шагом откройте файл ports.conf:
sudo nano /etc/apache2/ports.confСтандартное содержимое файла имеет следующий вид:
Listen 80
<IfModule ssl_module>
Listen 443
</IfModule>
<IfModule mod_gnutls.c>
Listen 443
</IfModule>Адрес в директиве Listen не указан, поэтому сокет уже принимает подключения по обоим протоколам – изменения не требуются. Убедитесь в этом:
sudo ss -tlnp -6 | grep :80
LISTEN 0 511 *:80 *:* users:(("apache2",pid=45179,fd=4),("apache2",pid=45178,fd=4),("apache2",pid=45177,fd=4),("apache2",pid=45176,fd=4),("apache2",pid=45175,fd=4),("apache2",pid=45173,fd=4))Адрес * означает, что сокет принимает подключения на всех адресах сервера. Это и есть сокет IPv6 с поддержкой IPv4-mapped адресов: он обслуживает оба протокола.
Проверьте работу сайта и API по обоим протоколам, указав путь к своему API вместо /api/:
curl -4 http://127.0.0.1/
curl -6 http://[::1]/
curl -4 http://127.0.0.1/api/
curl -6 http://[::1]/api/Ответы, полученные по IPv4 и IPv6, должны совпадать.
Пример того же тестового сайта, обслуживаемого Apache:

Если в директиве Listen указан адрес IPv4
Если в файле ports.conf адрес указан явно, сокет создается только для IPv4:
Listen 0.0.0.0:80
<IfModule ssl_module>
Listen 0.0.0.0:443
</IfModule>
<IfModule mod_gnutls.c>
Listen 443
</IfModule>В этом случае команда sudo ss -tlnp -6 | grep :80 вернет пустой результат. Добавьте директиву Listen [::]:80:
Listen 0.0.0.0:80
Listen [::]:80
<IfModule ssl_module>
Listen 0.0.0.0:443
Listen [::]:443
</IfModule>
<IfModule mod_gnutls.c>
Listen 443
</IfModule>Обратите внимание, что директива Listen [::]:443 добавлена по тому же принципу: в блоке <IfModule ssl_module> адрес указан явно, поэтому для IPv6 нужна отдельная строка. Если в вашем файле директива записана без адреса (Listen 443), она уже принимает подключения по обоим протоколам, и добавлять ничего не нужно.
Проверьте конфигурацию и перезапустите Apache:
sudo apache2ctl configtest
sudo systemctl restart apache2Убедитесь, что созданы сокеты для обоих протоколов:
sudo ss -tlnp | grep :80
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("apache2",pid=45179,fd=4))
LISTEN 0 511 [::]:80 [::]:* users:(("apache2",pid=45179,fd=6)В отличие от конфигурации с директивой Listen 80, здесь создаются два отдельных сокета – по одному на каждый протокол.
Address already in use, значит в файле указана директива Listen 80 без адреса, и сокет уже принимает оба протокола. Удалите добавленную строку Listen [::]:80.Виртуальный хост
Виртуальный хост изменять не требуется – конструкция <VirtualHost *:80> подходит для обоих протоколов, а параметры DocumentRoot, Directory и обработка API от протокола не зависят:
<VirtualHost *:80>
ServerName example.ru
DocumentRoot /var/www/example
</VirtualHost>Изменения нужны только в том случае, если в виртуальном хосте указан конкретный адрес IPv4, например: <VirtualHost 81.200.119.100:80> – в этом случае замените адрес на * либо добавьте отдельный виртуальный хост с адресом IPv6.
Настройка связки Nginx и Apache
В этой схеме Nginx принимает внешние подключения и отдает статические файлы, а Apache слушает только адрес 127.0.0.1 и обрабатывает динамические запросы. Файл /etc/apache2/sites-available/example.conf имеет примерно следующий вид:
<VirtualHost 127.0.0.1:8080>
ServerName example.ru
DocumentRoot /var/www/example/api
</VirtualHost>Поскольку Apache не принимает подключения извне, настраивать IPv6 требуется только в Nginx. Конфигурацию Apache изменять не обязательно.
Откройте конфигурационный файл сайта:
sudo nano /etc/nginx/sites-available/example.confДобавьте строку listen [::]:80; в блок server:
server {
listen 80;
listen [::]:80;
server_name example.ru;
root /var/www/example/public;
index index.html;
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}Если на сайте установлен SSL-сертификат, добавьте строку listen [::]:443 ssl; в блок, отвечающий за 443 порт. Защищенные соединения принимает Nginx, конфигурацию Apache это не затрагивает.
Проверьте конфигурацию и перезапустите Nginx:
sudo nginx -t
sudo systemctl restart nginxПроверьте, что порты распределены корректно:
sudo ss -tlnp | grep -E ':80|:8080'
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("apache2",pid=45340,fd=3),("apache2",pid=45339,fd=3),("apache2",pid=45338,fd=3),("apache2",pid=45337,fd=3),("apache2",pid=45336,fd=3),("apache2",pid=45334,fd=3))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=45392,fd=5),("nginx",pid=45391,fd=5))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=45392,fd=6),("nginx",pid=45391,fd=6))Nginx слушает оба протокола, Apache остается только на 127.0.0.1.
Проверьте работу сайта и API по обоим протоколам, указав путь к своему API вместо /api/:
curl -4 http://127.0.0.1/
curl -6 http://[::1]/
curl -4 http://127.0.0.1/api/
curl -6 http://[::1]/api/Ответы, полученные по IPv4 и IPv6, должны совпадать.
Пример того же тестового сайта, обслуживаемого связкой Nginx и Apache:

Настройка соединения Nginx и Apache по IPv6
Этот шаг необязательный. Связка работает и в текущем виде: посетители подключаются к Nginx по обоим протоколам, а до Apache запрос доходит по локальному адресу IPv4.
Если внутренний обмен тоже нужно перевести на IPv6, меняются три файла:
- адрес прослушивания в файле
/etc/apache2/ports.conf:
Listen [::1]:8080- адрес в виртуальном хосте Apache, файл
/etc/apache2/sites-available/example.conf:
<VirtualHost [::1]:8080>- адрес в директиве Nginx, файл
/etc/nginx/sites-available/example.conf:
proxy_pass http://[::1]:8080/;Перезапустите обе службы и проверьте результат – он не должен измениться:
sudo apache2ctl configtest && sudo systemctl restart apache2
sudo nginx -t && sudo systemctl restart nginx
sudo ss -tlnp | grep -E ':80|:8080'
curl -6 http://[::1]/api/Listen 127.0.0.1:8080 удалена, а не оставлена рядом с новой. Иначе Apache не запустится из-за ошибки Address already in use.Передача IP посетителя в приложение
В связке Nginx и Apache запрос доходит до приложения через Nginx. Поэтому в переменных $_SERVER['REMOTE_ADDR'] и $_SERVER['SERVER_ADDR'] оказывается локальный адрес – 127.0.0.1 или ::1, если внутренний обмен переведен на IPv6. Они описывают соединение между Nginx и Apache, а не подключение посетителя, поэтому ни его адрес, ни протокол по ним определить нельзя.
Адрес посетителя можно передать заголовком X-Forwarded-For. Дополните блок location в файле /etc/nginx/sites-available/example.conf:
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}Проверьте конфигурацию и перезапустите Nginx:
sudo nginx -t
sudo systemctl restart nginx$remote_addr содержит IP непосредственного клиента Nginx. Если Nginx принимает соединение напрямую от посетителя, в X-Forwarded-For попадет его IPv4-адрес или IPv6-адрес.
Многие приложения и фреймворки умеют работать с X-Forwarded-For. Если приложению нужно различать IPv4 и IPv6, оно определяет версию по адресу из этого заголовка.
Заголовок X-Forwarded-For можно использовать и в логах Apache. Например:
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b" proxy
CustomLog /var/log/apache2/access.log proxyВ таком формате Apache записывает IP посетителя из X-Forwarded-For, а не адрес Nginx, через который пришел запрос.
X-Forwarded-Proto передает схему исходного запроса – http или https. Он нужен, например, когда HTTPS завершается на Nginx, а до Apache запрос передается по HTTP.
Настройка DNS: AAAA-запись
Пока AAAA-запись не добавлена, сайт остается доступным только по IPv4 – это позволяет проверить настройки, не затрагивая текущих посетителей.
Проверьте доступность сайта напрямую по адресам сервера, подставив свои значения:
curl -4 http://<IPv4-адрес сервера>/
curl -6 http://[<IPv6-адрес сервера>]/Если на сайте есть API, проверьте и его, указав свой путь:
curl -4 http://<IPv4-адрес сервера>/api/
curl -6 http://[<IPv6-адрес сервера>]/api/Команды должны вернуть одинаковый результат для обоих протоколов.
После успешной проверки добавьте AAAA-запись в панели управления DNS-зоной сайта, в качестве значения укажите адрес IPv6 сервера – тот, который отображается в выводе команды ip -6 addr show с пометкой scope global.
Запись A удалять не нужно – наличие обеих записей и есть dual stack.
Проверьте, что записи отдаются:
dig A example.ru +short
dig AAAA example.ru +shortКак клиент выбирает протокол
После публикации AAAA-записи выбор протокола происходит на стороне клиента. Получив от DNS оба адреса, браузер или приложение пробует установить соединение по обоим – с небольшим приоритетом в пользу IPv6 – и использует то, которое установилось быстрее. Если соединение по IPv6 не устанавливается, клиент переключается на IPv4. Этот механизм называется Happy Eyeballs и не требует настройки ни на сайте, ни в API.
Проверьте, какой протокол выбирается, командой без указания версии протокола:
curl -v http://example.ru/ 2>&1 | grep "Connected to"
* Connected to example.ru (2001:db8:1a2b:3c4d::1) port 80Механизм Happy Eyeballs поддерживают браузеры и большинство современных библиотек, но не все клиенты. Программы, использующие простое последовательное подключение, при неисправном IPv6 будут ожидать истечения тайм-аута. Поэтому перед добавлением AAAA-записи важно убедиться, что сайт по IPv6 действительно отвечает.
Частые вопросы
В блок server, отвечающий за 443 порт, не добавлена строка listen [::]:443 ssl;. По IPv4 при этом работают оба протокола.
В файле ports.conf адрес указан явно: Listen 0.0.0.0:80. В этом случае сокет создается только для IPv4, требуется добавить директиву Listen [::]:80.
Ошибка Address already in use означает обратную ситуацию: директива Listen 80 без адреса уже создала сокет для обоих протоколов, и добавленная строка Listen [::]:80 дублирует его. Оставьте только Listen 80.
Если в ports.conf указана директива Listen 80 без адреса, создается один сокет IPv6, принимающий подключения и по IPv4. Отсутствие отдельного сокета IPv4 в этом случае не является ошибкой.
При изменении директив listen используйте restart вместо reload – перезагрузка конфигурации не всегда пересоздает сетевые сокеты.
Проверьте, что AAAA-запись для домена уже отдается. Также убедитесь, что указанный в записи адрес назначен на сетевом интерфейсе сервера – проверьте вывод команды ip -6 addr show.
При использовании обратного прокси не настроена передача заголовка X-Forwarded-For в конфигурации Nginx.
Фильтрация для IPv4 и IPv6 настраивается отдельно, и правила для IPv4 на подключения по IPv6 не действуют. Настройте для IPv6 такие же ограничения, как для IPv4.
Проверьте доступность сервера по IPv6 с внешнего устройства. Если IPv6 отвечает не всегда, клиенты без поддержки Happy Eyeballs ожидают истечения тайм-аута перед переключением на IPv4. До устранения причины AAAA-запись можно временно удалить.
Команда restart останавливает и запускает веб-сервер заново, и в этот момент соединения не обслуживаются. Выполняйте перезапуск только после успешной проверки конфигурации.
Если возникнут вопросы, напишите нам, пожалуйста, тикет из панели управления аккаунта (раздел “Помощь и поддержка”), а если вы захотите обсудить возможность подключения IPv4 или IPv6 с коллегами по цеху и сотрудниками Beget – ждем вас в нашем сообществе в Telegram.