Как настроить интернет для VPS без публичного IP через VPS-шлюз (GRE + NAT)

При размещении нескольких VPS (виртуальных серверов) часто возникает задача: публичный IPv4-адрес (от англ. Internet Protocol version 4 – адресный интернет-протокол версии 4) нужен только на одной машине (например, на веб-сервере или балансировщике), а остальные серверы работают во внутренней приватной сети и наружу напрямую выходить не должны. При этом «внутренним» VPS всё равно периодически требуется доступ в интернет – для установки пакетов, обновлений системы, обращения к внешним API (от англ. Application Programming Interface – интерфейс программирования приложения) и т. п.

Решение – организовать GRE-туннель (протокол инкапсуляции сетевых пакетов) между VPS без белого IP (идентификатор устройства в компьютерной сети) и VPS с белым IP, а на шлюзовой VPS включить NAT (MASQUERADE) (преобразование сетевых адресов). В этом случае весь исходящий трафик «внутренних» машин будет уходить в интернет через шлюз под его публичным IP-адресом.

В статье рассматриваются:

  • принцип работы схемы и роль GRE;
  • пошаговая настройка на примере Ubuntu 24.04;
  • сохранение конфигурации между перезагрузками;
  • проверка работоспособности;
  • расширение схемы на несколько VPS;
  • типовые проблемы и их диагностика.

Что такое GRE и зачем он нужен

GRE (Generic Routing Encapsulation) – это протокол туннелирования, позволяющий инкапсулировать произвольные сетевые пакеты внутрь IP. Два сервера, имеющие между собой сетевую связность (в нашем случае – по приватной сети), поднимают виртуальный интерфейс greN, через который можно маршрутизировать трафик так, как если бы серверы были соединены «напрямую».

В рассматриваемой схеме:

  • приватной сети между VPS недостаточно, чтобы попасть в интернет – у внутренних машин нет публичного IP и нет маршрута наружу;
  • через GRE-туннель внутренняя VPS получает маршрут по умолчанию, ведущий на шлюзовую VPS;
  • шлюзовая VPS с помощью iptables подменяет адрес источника на свой публичный IP (NAT) и отправляет пакеты в интернет;
  • ответные пакеты приходят на шлюз, NAT возвращает их во внутреннюю сеть через тот же туннель.

GRE выбран потому, что он прост, поддерживается ядром Linux «из коробки» и не требует дополнительного ПО.

Исходные данные

Приватная сеть Beget – 10.16.0.0/16 (может отличаться, в зависимости от региона размещения). Приватный IP-адрес каждой VPS можно посмотреть в панели управления.

Для примера:

  • VPS-GW (шлюз) – приватный IP 10.16.0.5, публичный IP есть (белый IPv4).
  • VPS-A (клиент) – приватный IP 10.16.0.7, публичного IP нет.

Для туннеля используется служебная подсеть 172.16.0.0/30:

  • 172.16.0.1 – адрес интерфейса gre1 на шлюзе;
  • 172.16.0.2 – адрес интерфейса gre1 на клиентской VPS.

Эта подсеть нигде в интернете и приватной сети не используется – она существует только внутри GRE-туннеля.

Настройка (Ubuntu 24.04)

Все команды выполняются от root (суперпользователь в UNIX-подобных операционных системах) (или через консольную утилиту sudo).

Шаг 1. Настройка шлюзовой VPS (VPS-GW)

1.1. Включить форвардинг (переадресацию) пакетов

Отредактируйте /etc/sysctl.conf и добавьте (либо раскомментируйте) строку:

net.ipv4.ip_forward=1

Примените без перезагрузки:

sysctl -p

1.2. Поднять GRE-интерфейс

Создайте systemd-юнит (конфигурационный файл, описывающий объект, которым управляет менеджер системы) /etc/systemd/system/gre1.service:

[Unit]
Description=GRE tunnel gre1
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/ip link add gre1 type gre local 10.16.0.5 remote 10.16.0.7 ttl 255
ExecStart=/sbin/ip link set gre1 up
ExecStart=/sbin/ip address add 172.16.0.1/30 dev gre1
ExecStop=/sbin/ip link del gre1

[Install]
WantedBy=multi-user.target

Активируйте:

systemctl daemon-reload
systemctl enable --now gre1.service

1.3. Настроить NAT и сохранить правила iptables

Установите пакет для хранения правил:

apt update
apt install -y iptables-persistent

В процессе установки мастер предложит сохранить текущие правила – можно согласиться, позже мы их перезапишем.

Добавьте правило NAT:

iptables -t nat -A POSTROUTING -s 172.16.0.0/30 -o eth0 -j MASQUERADE

Сохраните правила, чтобы они применились после перезагрузки:

netfilter-persistent save

Шаг 2. Настройка клиентской VPS (VPS-A)

2.1. Поднять GRE-интерфейс

Создайте systemd-юнит /etc/systemd/system/gre1.service:

[Unit]
Description=GRE tunnel gre1
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/ip link add gre1 type gre local 10.16.0.7 remote 10.16.0.5 ttl 255
ExecStart=/sbin/ip link set gre1 up
ExecStart=/sbin/ip address add 172.16.0.2/30 dev gre1
ExecStart=/sbin/ip route add default via 172.16.0.1 dev gre1 metric 50
ExecStop=/sbin/ip link del gre1

[Install]
WantedBy=multi-user.target
Обратите внимание!
local и remote поменялись местами относительно конфигурации шлюза.

Активируйте:

systemctl daemon-reload
systemctl enable --now gre1.service

После этого маршрут по умолчанию на VPS-A будет идти через туннель на шлюз.

Если на клиентской VPS уже есть маршрут по умолчанию в приватную сеть – новый маршрут через 172.16.0.1 его не заменит автоматически. Уточнить текущие маршруты можно командой ip route. При необходимости старый default удаляется через ip route del default.

Проверка работоспособности

Выполняйте команды с клиентской VPS-A.

  1. Туннель поднят и адрес назначен:
ip -br a show gre1

Ожидаемый вывод:

gre1   UNKNOWN   172.16.0.2/30 …
  1. Шлюз доступен внутри туннеля:
ping -c 3 172.16.0.1
  1. Внешний хост (узел сети) пингуется (пинг – время, за которое отправленный в сеть запрос достигает адресата и возвращается обратно):
ping -c 3 8.8.8.8
  1. Исходящий адрес – публичный IP шлюза:
curl ifconfig.me

Ответ должен совпадать с публичным IP VPS-GW.

  1. Маршрут наружу идет через туннель:
traceroute 8.8.8.8

Первым хопом должен быть 172.16.0.1.

Дополнительно на шлюзе можно посмотреть счетчики NAT:

iptables -t nat -L POSTROUTING -v -n

Значения pkts/bytes должны увеличиваться при трафике с клиента.

Масштабирование на несколько VPS

Если выход в интернет нужен с нескольких внутренних VPS, на каждом клиенте поднимается отдельный GRE-туннель к шлюзу, а на шлюзе создается отдельный интерфейс для каждого клиента.

Для этого удобно расширить служебную подсеть до /24 – например, 172.16.0.0/24 – и выделять каждой паре туннельных интерфейсов по адресу.

Пример распределения:

  • Интерфейс gre1 на шлюзе – IP шлюза 172.16.0.1/24, IP клиента 172.16.0.2/24, клиент – приватный IP 10.16.0.7.
  • Интерфейс gre2 на шлюзе – IP шлюза 172.16.0.1/24, IP клиента 172.16.0.3/24, клиент – приватный IP 10.16.0.8.
  • Интерфейс gre3 на шлюзе – IP шлюза 172.16.0.1/24, IP клиента 172.16.0.4/24, клиент – приватный IP 10.16.0.9.

На шлюзе заводится свой systemd-юнит (gre2.service, gre3.service, …) с уникальными remote и именем интерфейса. Правила NAT при этом достаточно одного – с подсетью-источником 172.16.0.0/24:

iptables -t nat -A POSTROUTING -s 172.16.0.0/24 -o eth0 -j MASQUERADE
netfilter-persistent save

На каждом клиенте настройка аналогична одиночному случаю – меняется только свой адрес в туннеле.

Доступ к внутренним VPS извне (проброс портов)

Рассмотренная выше схема обеспечивает только исходящий трафик: внутренняя VPS может ходить в интернет, но подключиться к ней снаружи (по SSH (от англ. Secure SHell – защищенная оболочка), HTTP (от англ. Hypertext Transfer Protocol – протокол передачи гипертекста) и т. п.) по-прежнему нельзя – у нее нет публичного IP.

Чтобы сделать отдельные сервисы внутренней VPS доступными извне, на шлюзе настраивается DNAT (проброс портов). Принцип работы:

  • Пакет приходит на публичный IP шлюза на заданный порт.
  • iptables в цепочке PREROUTING подменяет адрес назначения на туннельный IP клиента.
  • Пакет уходит в gre1 и попадает на внутреннюю VPS.
  • Ответ возвращается через шлюз и уходит наружу.

Чтобы ответные пакеты гарантированно возвращались через шлюз (а не пытались уйти напрямую, чего у клиента без публичного IP всё равно не получится, но логика маршрутизации может сломать соединение), дополнительно настраивается MASQUERADE в сторону туннеля.

Все правила ниже выполняются на шлюзе (VPS-GW). После добавления правил обязательно сохраните их:

netfilter-persistent save

Кейс 1. SSH на внутреннюю VPS

Пробрасываем внешний порт 2222 шлюза на порт 22 клиента 172.16.0.2:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 2222 \
    -j DNAT --to-destination 172.16.0.2:22

iptables -t nat -A POSTROUTING -o gre1 -p tcp -d 172.16.0.2 --dport 22 \
    -j MASQUERADE

Подключение снаружи:

ssh -p 2222 user@<публичный_IP_шлюза>

Внешний порт намеренно отличается от 22, чтобы не конфликтовать с SSH самого шлюза. Если на шлюзе SSH переведен на нестандартный порт, можно пробрасывать и 22.

Кейс 2. Несколько внутренних VPS на разных внешних портах

Когда за одним шлюзом стоит несколько клиентов и у каждого нужно открыть, например, SSH, порты разносятся по номерам:

  • внешний порт шлюза 2201172.16.0.2:22;
  • внешний порт шлюза 2202172.16.0.3:22;
  • внешний порт шлюза 2203172.16.0.4:22.
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 2201 -j DNAT --to-destination 172.16.0.2:22
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 2202 -j DNAT --to-destination 172.16.0.3:22
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 2203 -j DNAT --to-destination 172.16.0.4:22

iptables -t nat -A POSTROUTING -o gre1 -d 172.16.0.2 -p tcp --dport 22 -j MASQUERADE
iptables -t nat -A POSTROUTING -o gre2 -d 172.16.0.3 -p tcp --dport 22 -j MASQUERADE
iptables -t nat -A POSTROUTING -o gre3 -d 172.16.0.4 -p tcp --dport 22 -j MASQUERADE

Кейс 3. Диапазон портов

Если нужно пробросить целый диапазон (например, для игрового сервера или пассивного FTP (от англ. File Transfer Protocol – протокол передачи файлов)):

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 30000:30100 \
    -j DNAT --to-destination 172.16.0.2

iptables -t nat -A POSTROUTING -o gre1 -d 172.16.0.2 -p tcp \
    --dport 30000:30100 -j MASQUERADE

Без указания порта после --to-destination диапазон сохраняется as-is: 30005 снаружи → 30005 внутри.

UDP

Для UDP-сервисов (от англ. User Datagram Protocol – протокол пользовательских датаграмм) правила те же, но с -p udp. Пример – проброс игрового UDP-порта 27015:

iptables -t nat -A PREROUTING -i eth0 -p udp --dport 27015 \
    -j DNAT --to-destination 172.16.0.2:27015
iptables -t nat -A POSTROUTING -o gre1 -d 172.16.0.2 -p udp --dport 27015 \
    -j MASQUERADE

Проверка проброса

Снаружи (с любой машины в интернете):

nc -vz <публичный_IP_шлюза> 2222

На шлюзе должны расти счетчики DNAT:

iptables -t nat -L PREROUTING -v -n

Если соединение не устанавливается – в первую очередь проверьте, что:

  • на внутренней VPS сервис действительно слушает нужный порт (ss -tlnp);
  • на внутренней VPS нет локального фаервола, блокирующего вход;
  • net.ipv4.ip_forward на шлюзе равен 1;
  • правило DNAT написано именно в таблице nat и цепочке PREROUTING.

Примечания по другим ОС

Общая логика настройки VPS-сервера одинакова, различаются только пакеты и способы сохранения правил.

  • Debian 13 – полностью аналогично Ubuntu 24.04: iptables-persistent, sysctl.conf, systemd-юнит для GRE.
  • CentOS 9 / AlmaLinux 9 / Rocky Linux 9 / Fedora 41 – вместо iptables-persistent по умолчанию используется firewalld либо nftables. Возможные варианты:
    • через firewalld – добавить маскарад в нужной зоне:

      firewall-cmd --permanent --add-masquerade
      firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 \
          -s 172.16.0.0/30 -o eth0 -j MASQUERADE
      firewall-cmd --reload

    • либо отключить firewalld, поставить iptables-services и сохранить правила через service iptables save.
    • Форвардинг включается тем же net.ipv4.ip_forward=1 в /etc/sysctl.conf (или файле в /etc/sysctl.d/).
    • systemd-юнит для GRE используется без изменений.

Диагностика проблем

Пропадает связь после ip route add default via 172.16.0.1

Скорее всего, потерян маршрут до самого шлюза по приватной сети и туннель «сам под собой» разваливается. Убедитесь, что в таблице маршрутизации остается конкретный маршрут до приватного IP шлюза через приватный интерфейс:

ip route get 10.16.0.5

Ответ должен идти через приватный интерфейс (eth1/ens4 и т. п.), а не через gre1.

Пинг до 172.16.0.1 не проходит

  • Проверьте, что интерфейс gre1 поднят на обеих сторонах (ip -br a show gre1).
  • Проверьте, что local и remote в конфигурациях зеркальны (на шлюзе local = IP шлюза, на клиенте local = IP клиента).
  • Убедитесь, что между VPS есть связность по приватной сети: ping 10.16.0.5 с клиента.
  • Проверьте, что GRE не режется фаерволом (iptables -L -n, nft list ruleset). Протокол GRE – это IP protocol 47, не TCP (англ. Transmission Control Protocol – протокол управления передачей) и не UDP.

ping 172.16.0.1 работает, но ping 8.8.8.8 – нет

  • На шлюзе проверьте форвардинг:
sysctl net.ipv4.ip_forward

Должно быть 1.

  • Проверьте правило NAT:
iptables -t nat -L POSTROUTING -v -n

При трафике счетчики должны расти.

  • Убедитесь, что имя внешнего интерфейса в правиле (-o eth0) совпадает с фактическим (ip -br a).

Пинги идут, а TCP/HTTPS (от англ. Hyper Text Transfer Protocol Secure – защищенный протокол передачи гипертекста) – рвутся или «залипают»

Типичный симптом проблемы с MTU (максимальный объем данных). GRE добавляет свой заголовок (24 байта), и пакеты размером в полный MTU приватной сети через туннель уже не проходят. Решение – уменьшить MTU интерфейса или включить MSS clamping (метод ограничения максимального размера) на шлюзе:

iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
    -j TCPMSS --clamp-mss-to-pmtu
netfilter-persistent save

Либо вручную на обоих концах:

ip link set gre1 mtu 1400

После перезагрузки туннель не поднимается

  • Проверьте статус юнита: systemctl status gre1.
  • Убедитесь, что он включен в автозапуск: systemctl is-enabled gre1.
  • Если юнит стартует раньше, чем появляется приватный IP, добавьте в секцию [Unit] зависимость After=network-online.target и Wants=network-online.target (как в примерах выше), а также убедитесь, что включен systemd-networkd-wait-online либо его аналог.

Правила iptables исчезают после перезагрузки

Убедитесь, что установлен iptables-persistent и правила сохранены:

netfilter-persistent save

Файлы правил лежат в /etc/iptables/rules.v4 и /etc/iptables/rules.v6.

net.ipv4.ip_forward сбрасывается после перезагрузки

Значение должно быть прописано в /etc/sysctl.conf либо в отдельном файле в /etc/sysctl.d/ (например, /etc/sysctl.d/99-forward.conf). Проверка после перезагрузки:

sysctl net.ipv4.ip_forward

Итог

После выполнения всех шагов внутренние VPS без публичного IP получают полноценный выход в интернет через шлюзовую машину. Схема легко масштабируется на произвольное количество клиентов добавлением новых GRE-интерфейсов и не требует стороннего ПО – используются только штатные средства ядра Linux и iptables.

Если возникнут вопросы, напишите нам, пожалуйста, обращение в панели управления аккаунта (раздел “Помощь и поддержка”), а если вы захотите обсудить настройку VPS-сервера или наши продукты с коллегами по цеху и сотрудниками Beget – ждем вас в нашем сообществе в Telegram.