Перейти к содержимому

Справочник конфигурации сервера

Полный справочник всех полей в server.toml.


Все поля располагаются под разделом [server] или на корневом уровне (принимаются оба варианта).

ПолеТипПо умолчаниюОписание
bind_addressstring"0.0.0.0:443"Адрес и порт для прослушивания. Используйте "0.0.0.0:443" для всех интерфейсов или "127.0.0.1:8443" только для localhost (за обратным прокси).
tls_cert_filepath"certs/cert.pem"Путь к TLS-сертификату (формат PEM).
tls_key_filepath"certs/key.pem"Путь к приватному ключу TLS (формат PEM).
identity_key_filepath"server_identity.key"Путь к ключу идентификации X3DH сервера. Генерируется командой rvpn-server keygen.
websocket_pathstring"/api/v1/ws"Путь WebSocket-точки. Меняйте его для каждого развёртывания, чтобы затруднить обнаружение.
http_portinteger(отключён)Если задан, также слушает на этом порту обычный HTTP (например, 80) для ACME-испытаний и редиректов HTTP→HTTPS.
redirect_http_to_httpsbooltrueКогда задан http_port, перенаправляет все HTTP-запросы на HTTPS. Не-WebSocket запросы получают жёстко зашитую страницу 404 в стиле nginx, чтобы сливаться с обычным веб-сервером.
prekey_bundle_filepath(нет)Если задан, загружает ключи X3DH из этого файла вместо генерации новых. Используйте для восстановления сохранённой идентичности.

Управляет тем, сколько соединений может установить один IP-адрес.

ПолеТипПо умолчаниюОписание
max_connections_per_ipinteger500Максимальное количество одновременных соединений с одного IP-адреса.
max_handshakes_per_minuteinteger2000Максимальное количество новых попыток рукопожатия с одного IP в минуту. Помогает предотвратить брутфорс-зондирование.
[server.rate_limit]
max_connections_per_ip = 10
max_handshakes_per_minute = 20

Предпроверочные подсказки, используемые только для стартовых предупреждений — сервер не программирует NAT и не раздаёт IP из этого раздела. Фактические клиентские IP берутся из [server.tun].tun_ip; NAT настраивается вашим оператором (см. примеры iptables/pf в Установке сервера).

ПолеТипПо умолчаниюОписание
nat_enabledbooltrueУстановите false, чтобы подавить стартовое предупреждение «клиенты могут не достать интернет», если ваш egress обрабатывается в другом месте.
dhcp_rangestring"10.200.0.0/24"Подсеть, для которой сервер ожидает наличие правил NAT MASQUERADE. При запуске сервер грепает iptables-save в поисках правила для этой подсети и предупреждает, если не найдено.
[server.network]
nat_enabled = true
dhcp_range = "10.200.0.0/24"

Настройки для настоящего режима TUN-to-TUN. Когда включён, сервер создаёт TUN-интерфейс и направляет пакеты клиентов через него.

ПолеТипПо умолчаниюОписание
enabledboolfalseВключить TUN-режим. Когда false, сервер использует relay-режим (в стиле Brook).
tun_ipstring"10.200.0.1/24"IP-адрес TUN-интерфейса и префикс подсети. Клиентские IP выдаются из этой подсети.
mtuinteger1420MTU для TUN-интерфейса.
interface_namestring"tun0"Имя создаваемого TUN-интерфейса.
dns_serverslist["8.8.8.8"]DNS-резолверы, передаваемые TUN-клиентам через туннель.
[server.tun]
enabled = true
tun_ip = "10.200.0.1/24"
mtu = 1420
interface_name = "tun0"

Автоматические TLS-сертификаты через Let’s Encrypt — обратный прокси не нужен. Сервер сам получает и обновляет сертификаты через TLS-ALPN-01, поэтому тот же слушатель :443 обслуживает и живой трафик, и ACME-обмен. Порт :80 не нужен.

ПолеТипПо умолчаниюОписание
enabledboolfalseВключить выдачу через ACME. Когда true, файл tls_cert_file не должен существовать — сервер отказывается угадывать, какой сертификат отдавать.
domainslist of strings[]Полные доменные имена, для которых получать сертификат. При включении обязателен хотя бы один. Первый — основной CN; остальные становятся SAN.
contactslist of strings[]Контактные URI для ACME-аккаунта, обычно ["mailto:[email protected]"]. Let’s Encrypt по ним шлёт предупреждения об истечении, если продление сломается.
cache_dirpath"acme-cache"Каталог для хранения ключа ACME-аккаунта и выпущенных сертификатов. Должен быть записываемым. Сохраняйте между рестартами, чтобы не упереться в rate limit.
stagingboolfalseИспользовать staging-директорию Let’s Encrypt. Сертификаты не будут доверенными в браузерах, но выдача не бьёт по production rate limit’ам. Используйте при отладке DNS и firewall.

Предусловия — проверьте, прежде чем ставить enabled = true:

  1. Каждое имя из domains должно резолвиться (A/AAAA) на публичный IP сервера.
  2. Входящий TCP :443 должен быть доступен валидаторам Let’s Encrypt. За NAT — пробросьте порт.
  3. cache_dir должен быть записываемым и постоянным — потеря заставляет пере-выпускать сертификат и сжирает rate-limit-бюджет.
[server]
bind_address = "0.0.0.0:443"
# tls_cert_file / tls_key_file опущены — сертификат даёт ACME.
[server.acme]
enabled = true
domains = ["hk.example.com"]
contacts = ["mailto:[email protected]"]
cache_dir = "/var/lib/rvpn/acme"

Первая загрузка задерживается на ~5–15 секунд, пока идёт ACME-обмен. Продление происходит автоматически примерно за 30 дней до истечения; фоновая задача пишет каждое событие на уровне info (ACME event: …).

Если Let’s Encrypt недоступен (сетевой сбой, кратковременная авария LE), фоновая задача логирует ошибку и повторяет — упавшее продление не убивает работающий слушатель. Следите за повторяющимися строками ACME error:, если сертификаты перестанут ротироваться.

Продления полностью автоматические, вмешательство оператора не требуется.

  • Триггер — фоновая задача следит за notAfter сертификата и запускает новый заказ примерно за 30 дней до истечения.
  • Горячая замена — новый сертификат заменяет старый в резолвере в памяти. Существующие TLS-соединения продолжают жить с тем сертификатом, с которым были установлены; новые handshake’и подхватывают свежий. Никакого рестарта, никаких обрывов.
  • Персистентность — свежий сертификат записывается в cache_dir до замены, поэтому рестарт посреди окна подгружает уже выпущенный сертификат, а не запускает новый заказ.
  • Никаких внешних таймеров — ничего вроде certbot.timer нет. Вся машина состояний живёт внутри процесса rvpn-server.

Чтобы убедиться, что продление отработало (примерно через 60 дней после первой выдачи), поглядите журнал:

Окно терминала
sudo journalctl -u rvpn-server --since "60d ago" | grep "ACME event"

Должны быть первоначальная цепочка NewAccountDeployedNewCertCertCacheStore и ещё пара DeployedNewCert / CertCacheStore от продления.

Самая болезненная точка отказа в ACME — не код, а rate limits Let’s Encrypt. Для этой интеграции важны три:

ЛимитЧто провоцируетКак долго заблокирован
Failed authorizations5 упавших TLS-ALPN-01 challenge’ей на хост в час. Криво настроенный firewall, DNS или диспетчер ALPN сожжёт лимит за минуты.60 минут с последнего фейла
Duplicate certificates5 успешных выдач для того же набора хостов в неделю. Триггерится очисткой cache_dir и повторной выдачей.168 часов с пятой выдачи
New orders300 на аккаунт за 3 часа. Актуально только если фоновая задача уходит в жёсткий retry-цикл по многим доменам.3 часа

Подробности — Let’s Encrypt rate limits.

Когда rvpn-server видит 429 от LE, он понижает лог с ERROR до WARN и печатает время следующей попытки — оператору сразу понятно, когда возвращаться:

WARN ACME rate-limited by Let's Encrypt; next retry allowed after 2026-07-11 06:01:16 UTC

Сначала staging (рекомендуемый workflow для новых установок)

Заголовок раздела «Сначала staging (рекомендуемый workflow для новых установок)»

Staging-окружение Let’s Encrypt выдаёт недоверенные сертификаты, но по факту без rate limits. Используйте его, чтобы убедиться, что DNS + firewall + конфиг rvpn-server правильные, прежде чем переключаться на production:

  1. Начните со staging = true:
    [server.acme]
    enabled = true
    staging = true
    domains = ["hk.example.com"]
    contacts = ["mailto:[email protected]"]
    cache_dir = "/var/lib/rvpn/acme-staging"
  2. Перезапустите rvpn-server, ищите в журнале ACME event: DeployedNewCert, а затем ACME event: CertCacheStore. Если эта пара срабатывает — всё работает.
  3. Проверьте снаружи — браузер будет жаловаться на недоверенный staging-сертификат, это ожидаемо:
    Окно терминала
    echo | openssl s_client -connect hk.example.com:443 -servername hk.example.com 2>&1 | grep -Ei "issuer|verify return code"
    Issuer будет (STAGING) Let's Encrypt.
  4. Переключитесь на production — измените staging = false и возьмите свежий cache_dir (staging-сертификаты живут в другом аккаунте и кэше):
    staging = false
    cache_dir = "/var/lib/rvpn/acme"
  5. Перезапустите, подождите, проверьте. Issuer теперь настоящий Let's Encrypt.

На первом production-старте с пустым cache_dir rvpn-server печатает WARN, указывающий ровно на этот workflow — если увидите это предупреждение и не тестировали через staging, попробуйте сначала его, чтобы не сжечь rate-limit-бюджет.

Не чистите cache_dir без нужды. Каждое удаление + повторная выдача идёт в счёт лимита 5-в-неделю на duplicate certificates. Сохраняйте директорию между рестартами и бэкапьте вместе с остальным состоянием сервера.


[server]
bind_address = "0.0.0.0:443"
tls_cert_file = "/etc/rvpn/certs/cert.pem"
tls_key_file = "/etc/rvpn/certs/key.pem"
identity_key_file = "/etc/rvpn/server_identity.key"
websocket_path = "/api/v1/ws"
http_port = 80
redirect_http_to_https = true
[server.rate_limit]
max_connections_per_ip = 500
max_handshakes_per_minute = 2000
[server.network]
nat_enabled = true
dhcp_range = "10.200.0.0/24"
[server.tun]
enabled = true
tun_ip = "10.200.0.1/24"
mtu = 1420
interface_name = "tun0"
dns_servers = ["1.1.1.1", "8.8.8.8"]

Ключ идентификации сервера — то, что клиенты закрепляют при первом подключении. Чтобы ротовать его без ручного вмешательства уже закреплённых клиентов, сгенерируйте новую пару и опубликуйте новый prekey bundle, подписанный предыдущим ключом.

Окно терминала
# Держим старый ключ достаточно долго, чтобы подписать ротацию
mv server_identity.key old_identity.key
# Генерируем новый
rvpn-server keygen --output server_identity.key
# Публикуем v2 bundle, подписанный старым ключом
rvpn-server prekey-bundle \
--identity server_identity.key \
--output prekey-bundle.json \
--rotate-from old_identity.key \
--from-version 1

--rotate-from и --from-version должны использоваться вместе — сервер отказывается публиковать неподписанную ротацию. О том, как клиент обрабатывает полученный bundle, см. Закрепление идентификации сервера.


При работе за Caddy, nginx или другим TLS-терминирующим обратным прокси установите bind_address в localhost-порт и позвольте прокси обрабатывать TLS. Направьте WebSocket-переброс прокси на тот же путь:

[server]
bind_address = "127.0.0.1:8443"
tls_cert_file = "" # not used — proxy handles TLS
tls_key_file = ""
websocket_path = "/api/v1/ws"

Пример для Caddy:

your.domain.com {
handle /api/* {
reverse_proxy 127.0.0.1:8443
}
handle {
root * /var/www/html
file_server
}
}

Матчер пути должен быть /api/* (совпадение по префиксу), а не /api (только точное совпадение).