Ввод сервера VPN в эксплуатацию¶
Обзор¶
Полный цикл: закупка → настройка Ansible → создание inbounds → регистрация в MySQL → активация.
Детальный чеклист: docs/guides/SERVER-SETUP-CHECKLIST.md (monorepo shivavpn).
Автоматизация: scripts/commission_servers.py.
1. Добавить сервер в Ansible инвентарь¶
Файл: ansible/shiva-ansible/inventory/servers.ini → группа [vpn]
2. Настройка сервера через Ansible¶
cd ansible/shiva-ansible
ansible-playbook -i inventory/servers.ini playbooks/vpn.yml \
--limit vpn-provider-country-01 --vault-password-file .vault_pass
Что выполняется:
- SSH hardening — ключи, StrictModes, MaxStartups 200:30:300, MaxSessions 50
- UFW — порты 22, 80, 443, 1443, xray_port (33081), 9100
- sysctl — BBR, TCP буферы, somaxconn 65535, file-max 1M
- nginx — reverse proxy для 3x-ui, dhparam, certbot
- node_exporter — мониторинг с basic auth
- 3x-ui — Docker контейнер с xray
После запуска зайти в 3x-ui: https://<IP>:1443 — сменить пароль admin.
3. Создание inbounds¶
Автоматический (рекомендуется)¶
Создаёт 2 inbound: - Inbound 1: порт 2087, XHTTP+Reality (google.com SNI) - Inbound 2: порт 853, TCP+Reality (google.com SNI)
Ручной¶
Через 3x-ui панель (https://<IP>:1443). Параметры inbounds — см. docs/guides/SERVER-SETUP-CHECKLIST.md.
Обязательно:
- Reality security (не TLS, не none)
- sockopt:
tcpKeepAliveIdle=300,tcpKeepAliveInterval=15,tcpMptcp=true,tcpCongestion=bbr,tcpMaxSeg=1440 - Правильные SNI destinations
4. Добавить сервер в MySQL¶
INSERT INTO server (id, name, ip, latitude, longitude, city_id, is_active, is_recommended, is_allocated)
VALUES (UUID_TO_BIN(UUID()), 'AT #8', '<SERVER_IP>',
48.2082, 16.3738,
(SELECT id FROM city WHERE name_key = 'vienna'),
FALSE, TRUE, TRUE);
is_active=FALSE
Сервер создаётся неактивным. Активация — после проверки inbounds и мониторинга.
Проверить города: SELECT country.code, country.name_key, city.name_key FROM country JOIN city ON country.id = city.country_id;
5. Добавить в мониторинг¶
Файл: ansible/shiva-ansible/roles/grafana/files/prometheus.yml
Добавить target <IP>:9100 в соответствующий job.
Пароли
Пароль node_exporter basic auth хранится в ansible-vault (vault_node_exporter_basic_auth_password).
Prometheus config ссылается на vault-переменные.
6. Активация¶
Каскад (5-70 мин): Backend event → NodeSyncWorker → VCS mysql_sync → full_sync → fill_configs.
Детали: docs/guides/SERVER-CONFIG-FLOW.md.
7. Проверка¶
# Health check
python3 scripts/check_vpn_server.py <IP>
# Проверить что конфиги доставлены
./scripts/ssh-internal.sh 10.99.87.249 "docker exec vpn-config-service python -c \"from app.db import get_db; ...\""
Docker log rotation¶
Обязательно на каждом VPN сервере (без этого логи растут до 10-20 ГБ):
// /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
SSH-ключ vpn-config-service¶
На каждом VPN-сервере обязательно два ключа в /root/.ssh/authorized_keys:
id_ed25519_shivavpn— ручной доступ + Ansibleid_ed25519_vpn_config_service— для VPN Config Service worker (fill_configs, full_sync)
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGtLzyxtfv8RJaU/PsW9YPPAmMT1jYh2/iM89d0uogux vpn-config-service@shivavpn
Без второго ключа worker получит "Permission denied" при попытке добавить клиентов или прочитать SQLite.
Inbounds: требования¶
| Inbound ID | Порт | Протокол | Назначение |
|---|---|---|---|
| 1 | 2087 | VLESS XHTTP | Основной (XHTTP + Reality) |
| 2 | 853 | VLESS TCP | Fallback + WL proxy target |
| 3 | 10443 | VLESS TCP | Stealth (опционально) |
Обязательные настройки:
fingerprint:random(inbound 1, 2),qqилиedge(inbound 3 stealth)sockopt:tcpKeepAliveIdle=300,tcpKeepAliveInterval=15,tcpCongestion=bbr,tcpMptcp=true,tcpMaxSeg=1440flow: пусто для XHTTP (inbound 1),xtls-rprx-visionдля TCP (inbound 2, 3)
Без sockopt копятся zombie TCP-соединения (до 24K+), что ломает метрики.
MySQL: полная структура записей¶
XUI-сервер — 2 записи (по одной на каждый inbound)¶
-- Inbound 1: XHTTP (port 2087)
INSERT INTO server (id, name, ip, port, inbound_id, panel_type, is_active, city_id, ...)
VALUES (UUID_TO_BIN(UUID()), 'AT #8', '<IP>', 2087, 1, 'XUI', 0, ...);
-- Inbound 2: TCP (port 853)
INSERT INTO server (id, name, ip, port, inbound_id, panel_type, is_active, city_id, ...)
VALUES (UUID_TO_BIN(UUID()), 'AT #8-2', '<IP>', 853, 2, 'XUI', 0, ...);
WL-сервер (whitelist proxy)¶
Отдельная запись для проксированного маршрута. Подробнее: Добавление Whitelist-сервера.
WLS-сервер (stealth, inbound 3)¶
Требует двух записей: XUI (для fill_configs) + WHITELIST (для proxy-маршрута).
Stealth-серверы¶
Stealth inbounds маскируются под конкурентские VPN сервисы для обхода DPI. Отличаются от стандартных fingerprint'ом и SNI.
Создание stealth inbound:
Текущие stealth-серверы (6 шт):
| Code | Upstream IP | Proxy (RU) | SNI | Fingerprint |
|---|---|---|---|---|
| WLSFR | 79.127.138.202 | 212.233.123.88 | eh.vk.com | |
| WLSDE | 89.222.124.21 | 90.156.219.233 | eh.vk.com | |
| WLSNL | 45.67.231.125 | 83.166.235.99 | eh.vk.com | |
| WLSAT | 5.181.21.90 | 90.156.214.48 | eh.vk.com | |
| WLSES | 45.12.150.3 | 90.156.218.106 | ozon.ru | |
| WLSPL | 88.218.2.46 | 90.156.213.143 | stats.vk-portal.net | edge |
Отличия stealth inbound (ID=3) от стандартных:
| Параметр | Stealth (inbound 3) | Стандартный (½) |
|---|---|---|
| port | 10443 | 2087 / 853 |
| fingerprint | qq или edge |
random |
| SNI/dest | eh.vk.com, ozon.ru, stats.vk-portal.net | dl.google.com, vk.com |
| flow | xtls-rprx-vision | пусто (XHTTP) / xtls-rprx-vision (TCP) |
| network | tcp | xhttp / tcp |
| shortIds | 1-2 коротких (e.g. ["34"]) |
min 3 |
| serverNames | 1 (e.g. ["eh.vk.com"]) |
min 2 |
MySQL записи для WLS (stealth) — нужны 2 записи на каждый upstream:
panel_type='XUI',inbound_id=3— для fill_configs (добавляет клиентов)panel_type='WHITELIST',inbound_id=3— WLS proxy маршрут (change_ip= RU proxy)
Kernel Tuning (sysctl + ulimits)¶
Обязательно на каждом сервере. Без этого — мало файловых дескрипторов, маленькие TCP буферы, нет BBR, мёртвые SSH-соединения висят часами.
Source of truth: ansible/shiva-ansible/roles/sysctl/tasks/main.yml
Применяется через Ansible (playbooks/sysctl.yml) или commission_servers.py step 2.
/etc/sysctl.d/99-vpn-tuning.conf¶
# File descriptors (1M для 6000+ клиентов)
fs.file-max = 1000000
fs.nr_open = 1000000
# Network core
net.core.default_qdisc = fq
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.optmem_max = 65535
# TCP performance
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_rmem = 4096 131072 16777216
net.ipv4.tcp_wmem = 4096 131072 16777216
net.ipv4.tcp_mem = 65536 131072 262144
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 32768
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_fastopen = 0
net.ipv4.tcp_notsent_lowat = 16384
net.ipv4.ip_local_port_range = 1024 65535
# TCP keepalive
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
# IP forwarding + security
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv6.conf.all.accept_redirects = 0
# Memory
vm.swappiness = 10
vm.dirty_background_ratio = 5
vm.dirty_ratio = 15
vm.overcommit_memory = 1
vm.vfs_cache_pressure = 50
Ключевые параметры — почему наши значения отличаются от дефолтных:
| Параметр | Дефолт | Наш | Зачем |
|---|---|---|---|
net.core.somaxconn |
128 | 65535 | accept() backlog для тысяч одновременных подключений |
net.ipv4.tcp_max_syn_backlog |
128 | 8192 | SYN queue — защита от потерь при всплесках |
net.ipv4.tcp_fin_timeout |
60 | 15 | Быстрее освобождать TIME_WAIT сокеты |
net.ipv4.tcp_tw_reuse |
0 | 1 | Переиспользовать TIME_WAIT — критично при 50+ SSH |
net.core.netdev_max_backlog |
1000 | 65535 | Буфер входящих пакетов на NIC |
net.ipv4.tcp_keepalive_time |
7200 | 300 | Обнаружить мёртвое соединение через 5 мин (не 2 ч) |
net.ipv4.tcp_slow_start_after_idle |
1 | 0 | Не сбрасывать cwnd на idle соединениях |
net.ipv4.tcp_mtu_probing |
0 | 1 | MTU discovery для разных сетей |
net.ipv4.tcp_notsent_lowat |
-1 | 16384 | Меньше latency на write() — важно для VPN |
vm.swappiness |
60 | 10 | Минимизировать swap на 1-2GB RAM VPS |
net.core.default_qdisc |
pfifo_fast | fq | Fair Queue — нужно для BBR |
net.ipv4.tcp_congestion_control |
cubic | bbr | BBR — выше пропускная способность |
net.core.rmem_max / wmem_max |
212992 | 16777216 | 16MB буферы для гигабитных соединений |
fs.file-max |
~100K | 1000000 | 1M дескрипторов для 6000+ клиентов на сервер |
vm.overcommit_memory |
0 | 1 | Разрешить overcommit (xray выделяет много VMA) |
net.ipv4.tcp_fastopen |
1 | 0 | Отключено — конфликтует с Reality handshake |
/etc/security/limits.d/99-vpn.conf¶
cat > /etc/security/limits.d/99-vpn.conf << 'EOF'
* soft nofile 1000000
* hard nofile 1000000
* soft nproc 1000000
* hard nproc 1000000
root soft nofile 1000000
root hard nofile 1000000
root soft nproc 1000000
root hard nproc 1000000
EOF
systemd limits — /etc/systemd/system.conf.d/limits.conf¶
mkdir -p /etc/systemd/system.conf.d
cat > /etc/systemd/system.conf.d/limits.conf << 'EOF'
[Manager]
DefaultLimitNOFILE=1000000
DefaultLimitNPROC=1000000
EOF
systemctl daemon-reexec
Worker-машина (10.99.87.249) — дополнительно¶
На worker'е (VPN Config Service) добавить к существующим параметрам:
Применение¶
- Новые серверы:
commission_servers.pystep 2 — автоматически - Существующие:
ansible-playbook -i inventory/servers.ini vpn_performance_tuning_2025.yml
Чеклист¶
- Сервер в
servers.ini - Ansible playbook
vpn.ymlвыполнен - 3x-ui пароль admin сменён
- SSH ключ
vpn-config-serviceдобавлен вauthorized_keys - SSH daemon tuning (
MaxStartups 200:30:300,MaxSessions 50,LoginGraceTime 15,ClientAliveInterval 30) - Kernel sysctl tuning (
/etc/sysctl.d/99-vpn-tuning.conf) — BBR, somaxconn 65535, keepalive 300s - ulimits (
/etc/security/limits.d/99-vpn.conf) — nofile/nproc 1000000 - Inbound 1 (XHTTP:2087) создан с правильными sockopt и fingerprint
- Inbound 2 (TCP:853) создан с правильными sockopt и fingerprint
- UFW: порты 2087, 853, 9100 открыты
- Сервер в MySQL: 2 записи XUI (inbound 1 + 2), is_active=0
- Мониторинг (node_exporter в Prometheus)
- Docker log rotation настроен
-
is_active = 1после проверки - Ansible inventory
servers.iniобновлён
См. также: Ansible · DevOps скрипты → commission_servers · VPN Config Service · Инвентарь серверов · SSL-сертификаты